/**
* ESP32 NimBLE 7-Segment LED Studio Firmware
* Dependencies: NimBLE-Arduino, FastLED
*/
#include <Arduino.h>
#include <NimBLEDevice.h>
#include <FastLED.h>
#define LED_PIN 33
#define NUM_LEDS 28 // 2 digits * 14 LEDs per digit (2 LEDs per segment)
#define BRIGHTNESS 200
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
#define SERVICE_UUID "4fafc201-75d5-4b8d-8aab-ec2d81606064"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
bool deviceConnected = false;
int currentValue = 0;
CRGB currentColor = CRGB::White;
String currentMode = "STATIC";
NimBLECharacteristic* pGlobalCharacteristic = nullptr;
void notifyClient(String msg) {
if (deviceConnected && pGlobalCharacteristic != nullptr) {
pGlobalCharacteristic->setValue(msg.c_str());
pGlobalCharacteristic->notify();
}
}
const bool DIGITS[10][7] = {
{ 1, 1, 1, 1, 1, 1, 0 }, // 0
{ 0, 0, 1, 1, 0, 0, 0 }, // 1
{ 0, 1, 1, 0, 1, 1, 1 }, // 2
{ 0, 1, 1, 1, 1, 0, 1 }, // 3
{ 1, 0, 1, 1, 0, 0, 1 }, // 4
{ 1, 1, 0, 1, 1, 0, 1 }, // 5
{ 1, 1, 0, 1, 1, 1, 1 }, // 6
{ 0, 1, 1, 1, 0, 0, 0 }, // 7
{ 1, 1, 1, 1, 1, 1, 1 }, // 8
{ 1, 1, 1, 1, 1, 0, 1 } // 9
};
void updateDisplay(int val);
class MyServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer) {
deviceConnected = true;
Serial.println("\n[BLE EVENT] Client Connected successfully!");
notifyClient("[ESP32] Connected successfully to Web Studio!");
};
void onDisconnect(NimBLEServer* pServer) {
deviceConnected = false;
Serial.println("\n[BLE EVENT] Client Disconnected. Restarting advertising...");
NimBLEDevice::startAdvertising();
}
};
class MyCallbacks: public NimBLECharacteristicCallbacks {
void onWrite(NimBLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("\n[BLE WRITE RECEIVED] Raw length: ");
Serial.println(value.length());
if (value.length() > 0) {
String cmd = String(value.c_str());
cmd.trim();
Serial.print("[BLE PARSED COMMAND]: '");
Serial.print(cmd);
Serial.println("'");
notifyClient("[ESP32 ACK] Received: " + cmd);
if (cmd.equalsIgnoreCase("OFF")) {
currentMode = "OFF";
FastLED.clear();
FastLED.show();
Serial.println("[ACTION] Display turned OFF");
} else if (cmd.equalsIgnoreCase("UP")) {
currentMode = "UP";
Serial.println("[ACTION] Mode: Count Up");
} else if (cmd.equalsIgnoreCase("DOWN")) {
currentMode = "DOWN";
Serial.println("[ACTION] Mode: Count Down");
} else if (cmd.equalsIgnoreCase("RAINBOW")) {
currentMode = "RAINBOW";
Serial.println("[ACTION] Mode: Rainbow");
} else if (cmd.equalsIgnoreCase("BREATHE")) {
currentMode = "BREATHE";
Serial.println("[ACTION] Mode: Breathing");
} else if (cmd.startsWith("BRIGHT:")) {
int b = cmd.substring(7).toInt();
b = constrain(b, 0, 255);
FastLED.setBrightness(b);
FastLED.show();
Serial.print("[ACTION] Brightness set to: ");
Serial.println(b);
} else if (cmd.startsWith("#")) {
long number = strtol(cmd.c_str() + 1, NULL, 16);
currentColor = CRGB((number >> 16) & 0xFF, (number >> 8) & 0xFF, number & 0xFF);
currentMode = "STATIC";
updateDisplay(currentValue);
Serial.print("[ACTION] Color set to Hex: ");
Serial.println(cmd);
} else {
int val = cmd.toInt();
if (val >= 0 && val <= 99) {
currentValue = val;
currentMode = "STATIC";
updateDisplay(currentValue);
Serial.print("[ACTION] Value set to: ");
Serial.println(currentValue);
} else {
Serial.print("[WARNING] Unknown command: ");
Serial.println(cmd);
}
}
}
}
};
void drawDigit(int startIdx, int digitVal, CRGB color) {
for (int i = 0; i < 7; i++) {
bool on = DIGITS[digitVal][i];
CRGB c = on ? color : CRGB::Black;
leds[startIdx + (i * 2)] = c;
leds[startIdx + (i * 2) + 1] = c;
}
}
void updateDisplay(int val) {
FastLED.clear();
int tens = constrain(val / 10, 0, 9);
int units = constrain(val % 10, 0, 9);
drawDigit(0, tens, currentColor);
drawDigit(14, units, currentColor);
FastLED.show();
}
void setup() {
Serial.begin(115200);
delay(1500);
Serial.println("\n\n==================================================");
Serial.println("ESP32 NimBLE 7-Segment LED Studio Firmware Starting");
Serial.println("==================================================");
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(BRIGHTNESS);
FastLED.clear();
FastLED.show();
NimBLEDevice::init("ESP32_LED");
NimBLEServer *pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
NimBLEService *pService = pServer->createService(SERVICE_UUID);
pGlobalCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::NOTIFY
);
pGlobalCharacteristic->setCallbacks(new MyCallbacks());
pService->start();
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->enableScanResponse(true);
pAdvertising->setPreferredParams(0x06, 0x12);
pAdvertising->start();
Serial.println("[SYSTEM READY] ESP32 BLE Server advertising as 'ESP32_LED' on GPIO 33!");
updateDisplay(currentValue);
}
void loop() {
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate > 500) {
lastUpdate = millis();
if (currentMode == "UP") {
currentValue = (currentValue + 1) % 100;
updateDisplay(currentValue);
} else if (currentMode == "DOWN") {
currentValue = (currentValue - 1 + 100) % 100;
updateDisplay(currentValue);
} else if (currentMode == "RAINBOW") {
static uint8_t hue = 0;
hue += 5;
currentColor = CHSV(hue, 255, 255);
updateDisplay(currentValue);
} else if (currentMode == "BREATHE") {
static uint8_t breathVal = 40;
static int8_t delta = 5;
breathVal += delta;
if (breathVal <= 20 || breathVal >= 250) delta = -delta;
FastLED.setBrightness(breathVal);
updateDisplay(currentValue);
}
}
}