Smart NeoPixel LED Strip

Overview

General Idea

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.

Hardware Components Used

  • ESP32-WROOM-32: 1 x ESP32 Sparrow development board (purchased from here)
  • NeoPixel LED Strip: 1 x 5m NeoPixel LED Strip (became 4.5m after an accident =)) (300 NeoPixel LEDs for 5m or 270 NeoPixel LEDs for 4.5m)
  • Breadboard: 1 x 830 holes Breadboard
  • OLED Display: 1 x 0.91inch OLED Display
  • Connection Cables
  • USB-A to MicroUSB Cable

Software

  • Arduino IDE for code development and Serial Monitor
  • BLE Scanner Android App for connecting my phone to the ESP32 board via BLE

Libraries needed

  • Adafruit_BusIO
  • Adafruit_GFX_Library
  • Adafruit_NeoPixel
  • Adafruit_SSD1306
  • ArduinoBLE

Architecture

Physical Architecture

Diagram

Real Hardware

Software Architecture

Setup de BLE Service

// 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...");
}

Get the commands from the phone via the created service

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;
  }
}

Choose the lighting mode

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();
}

Display setup and printing

// 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();
}

How it works

Main service page

Command for a static color

Command for splitting the strip in 2 colors

Command for cycling through colors

Command for having a rainbow show

Command for having a snake-like moving

Command for lighting-up random LEDs with random colors

Challenges

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.

Next Steps

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.

Resources

  • IoT Laboratories
  • Lots of sites, especially StackOverflow (for debugging ;-))
iothings/proiecte/2023sric/smartled.txt · Last modified: 2024/05/30 01:56 by dragos.petre
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