#include #include #include #include #include #include #include #include // ================= USER SETTINGS ================= const char* ssid = "NETGEAR69_EXT"; const char* password = "aquatictomato152"; // Time Zone (EST) long gmtOffset_sec = -18000; int daylightOffset_sec = 3600; // ================= PIN DEFINITIONS ================= #define RED_LED_PIN 2 #define WHITE_LED_PIN 7 #define BLUE_LEDS_PIN 10 #define CLK_PIN 20 #define DIO_PIN 21 #define DF_RX_PIN 6 #define DF_TX_PIN 8 #define ROTARY_SW 3 #define ROTARY_DT 4 #define ROTARY_CLK 5 // ================= VARIABLES ================= TM1637Display display(CLK_PIN, DIO_PIN); HardwareSerial myDFSerial(1); DFRobotDFPlayerMini myDFPlayer; // Bluetooth BLEServer *pServer = NULL; BLECharacteristic *pCharacteristic = NULL; bool deviceConnected = false; String btMessage = ""; bool msgReceived = false; bool justConnected = false; // Time & Audio int currentHour = 12, currentMin = 0; int alarmHour = 7, alarmMinute = 30; int alarmSong = 1; int playSelection = 1; bool alarmActive = false; bool isAlarming = false; bool musicActive = false; int currentVolume = 15; // Encoder State int lastClk = HIGH; int lastBtnState = HIGH; unsigned long btnPressStart = 0; bool btnPressed = false; // Display Timers unsigned long menuIdleTimer = 0; unsigned long volumeDisplayTimer = 0; bool showVolumeMode = false; // Menu System int currentMode = 0; unsigned long lastTimeUpdate = 0; unsigned long lastBlinkTime = 0; bool ledState = true; // ================= BLUETOOTH CALLBACKS ================= class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; justConnected = true; }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; pServer->getAdvertising()->start(); } }; class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { // FIX: Use Arduino String instead of std::string String rxValue = pCharacteristic->getValue(); if (rxValue.length() > 0) { btMessage = rxValue; // Simplify assignment msgReceived = true; } } }; // ================= SETUP ================= void setup() { Serial.begin(115200); // 1. Setup LEDs pinMode(RED_LED_PIN, OUTPUT); pinMode(WHITE_LED_PIN, OUTPUT); pinMode(BLUE_LEDS_PIN, OUTPUT); digitalWrite(RED_LED_PIN, LOW); digitalWrite(WHITE_LED_PIN, LOW); digitalWrite(BLUE_LEDS_PIN, LOW); // 2. Knob Inputs pinMode(ROTARY_SW, INPUT_PULLUP); pinMode(ROTARY_DT, INPUT); pinMode(ROTARY_CLK, INPUT); lastClk = digitalRead(ROTARY_CLK); // 3. Display display.setBrightness(0x0f); uint8_t boot[] = {0x40, 0x40, 0x40, 0x40}; display.setSegments(boot); delay(500); // 4. WiFi Start WiFi.begin(ssid, password); int retry = 0; while (WiFi.status() != WL_CONNECTED && retry < 20) { delay(200); retry++; } if (WiFi.status() == WL_CONNECTED) { configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org"); uint8_t wifi[] = {0x1C, 0x10, 0x71, 0x10}; display.setSegments(wifi); delay(500); } // 5. MP3 Start digitalWrite(RED_LED_PIN, LOW); digitalWrite(WHITE_LED_PIN, LOW); digitalWrite(BLUE_LEDS_PIN, LOW); delay(200); myDFSerial.begin(9600, SERIAL_8N1, DF_RX_PIN, DF_TX_PIN); delay(500); if (myDFPlayer.begin(myDFSerial)) { Serial.println("✅ DFPlayer Online."); myDFPlayer.volume(currentVolume); myDFPlayer.play(1); } // 6. Bluetooth Start BLEDevice::init("R2D2_Final"); pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService("FFE0"); pCharacteristic = pService->createCharacteristic( "FFE1", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE_NR ); pCharacteristic->setCallbacks(new MyCallbacks()); pCharacteristic->addDescriptor(new BLE2902()); pService->start(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID("FFE0"); pAdvertising->setScanResponse(true); pAdvertising->setMinPreferred(0x06); pAdvertising->setMinPreferred(0x12); BLEDevice::startAdvertising(); // 7. FINAL POWER UP: Show "Std" digitalWrite(RED_LED_PIN, HIGH); digitalWrite(WHITE_LED_PIN, HIGH); digitalWrite(BLUE_LEDS_PIN, HIGH); uint8_t std[] = {0x6D, 0x78, 0x5E, 0x00}; display.setSegments(std); delay(2000); } // ================= MAIN LOOP ================= void loop() { handleKnob(); if (currentMode == 0) { if (showVolumeMode) { if (millis() - volumeDisplayTimer > 2000) showVolumeMode = false; } else { updateTime(); } checkAlarm(); handleBluetooth(); handleLEDs(); } else { blinkMenu(); if (millis() - menuIdleTimer > 15000) currentMode = 0; } } // ================= KNOB LOGIC ================= void handleKnob() { int newClk = digitalRead(ROTARY_CLK); if (newClk != lastClk && newClk == LOW) { int dtValue = digitalRead(ROTARY_DT); int direction = (dtValue != newClk) ? -1 : 1; menuIdleTimer = millis(); if (currentMode == 0) { // Volume currentVolume += direction; if (currentVolume > 30) currentVolume = 30; if (currentVolume < 0) currentVolume = 0; myDFPlayer.volume(currentVolume); display.showNumberDec(currentVolume, false, 2, 2); uint8_t SEG_UO[] = {0x3E, 0x3F}; display.setSegments(SEG_UO, 2, 0); showVolumeMode = true; volumeDisplayTimer = millis(); } else if (currentMode == 1) { currentHour += direction; if (currentHour > 23) currentHour = 0; if (currentHour < 0) currentHour = 23; } else if (currentMode == 2) { currentMin += direction; if (currentMin > 59) currentMin = 0; if (currentMin < 0) currentMin = 59; } else if (currentMode == 3) { alarmHour += direction; if (alarmHour > 23) alarmHour = 0; if (alarmHour < 0) alarmHour = 23; } else if (currentMode == 4) { alarmMinute += direction; if (alarmMinute > 59) alarmMinute = 0; if (alarmMinute < 0) alarmMinute = 59; } else if (currentMode == 5) { alarmSong += direction; if (alarmSong < 1) alarmSong = 1; if (alarmSong > 99) alarmSong = 1; } else if (currentMode == 6) { playSelection += direction; if (playSelection < 1) playSelection = 1; if (playSelection > 99) playSelection = 1; } } lastClk = newClk; // BUTTON int btnState = digitalRead(ROTARY_SW); if (btnState == LOW && lastBtnState == HIGH) { btnPressStart = millis(); btnPressed = true; } if (btnState == HIGH && lastBtnState == LOW && btnPressed) { btnPressed = false; unsigned long duration = millis() - btnPressStart; menuIdleTimer = millis(); if (duration > 50) { if (isAlarming || musicActive) { stopAlarm(); lastBtnState = btnState; return; } if (duration > 1000) { if (currentMode == 0) { currentMode = 1; showVolumeMode = false; } else { currentMode = 0; } } else { if (currentMode == 0) { alarmActive = !alarmActive; uint8_t SEG_ON[] = {0x3F, 0x54, 0x00, 0x00}; uint8_t SEG_OFF[] = {0x3F, 0x71, 0x71, 0x00}; display.setSegments(alarmActive ? SEG_ON : SEG_OFF); delay(1000); } else { if (currentMode == 6) { myDFPlayer.volume(currentVolume); myDFPlayer.play(playSelection); musicActive = true; currentMode = 0; return; } currentMode++; if (currentMode > 6) currentMode = 0; } } } } lastBtnState = btnState; } // ================= HELPERS ================= void updateTime() { if (millis() - lastTimeUpdate > 1000) { lastTimeUpdate = millis(); struct tm timeinfo; if (getLocalTime(&timeinfo)) { currentHour = timeinfo.tm_hour; currentMin = timeinfo.tm_min; } int dispH = currentHour; bool isPM = (dispH >= 12); if (dispH == 0) dispH = 12; if (dispH > 12) dispH -= 12; bool blink = (millis() / 500) % 2 == 0; display.showNumberDecEx((dispH * 100) + currentMin, (isPM || blink) ? 0x40 : 0x00, true); } } void blinkMenu() { bool on = (millis() / 300) % 2 == 0; int val = 0; bool isSetPM = false; if (currentMode == 1 || currentMode == 2) { val = (currentHour * 100) + currentMin; if (currentHour >= 12) isSetPM = true; } else if (currentMode == 3 || currentMode == 4) { val = (alarmHour * 100) + alarmMinute; if (alarmHour >= 12) isSetPM = true; } else if (currentMode == 5) { if (on) { display.showNumberDec(alarmSong, true, 2, 2); uint8_t SEG_SO[] = {0x6D, 0x5C}; display.setSegments(SEG_SO, 2, 0); } else display.clear(); return; } else if (currentMode == 6) { if (on) { display.showNumberDec(playSelection, true, 2, 2); uint8_t SEG_PL[] = {0x73, 0x38}; display.setSegments(SEG_PL, 2, 0); } else display.clear(); return; } int dispH = (currentMode == 3 || currentMode == 4) ? alarmHour : currentHour; if (dispH == 0) dispH = 12; if (dispH > 12) dispH -= 12; val = (dispH * 100) + ((currentMode == 3 || currentMode == 4) ? alarmMinute : currentMin); uint8_t pmMask = isSetPM ? 0x40 : 0x00; if (on) { display.showNumberDecEx(val, pmMask, true); } else { if (currentMode == 1 || currentMode == 3) display.showNumberDec(val % 100, true, 2, 2); else display.showNumberDec(val / 100, true, 2, 0); } } void checkAlarm() { if (alarmActive && !isAlarming) { struct tm timeinfo; bool match = false; if (getLocalTime(&timeinfo)) { if (timeinfo.tm_hour == alarmHour && timeinfo.tm_min == alarmMinute && timeinfo.tm_sec == 0) match = true; } else { if (currentHour == alarmHour && currentMin == alarmMinute && millis() % 60000 < 1000) match = true; } if (match) { isAlarming = true; musicActive = true; myDFPlayer.volume(currentVolume); myDFPlayer.loop(alarmSong); } } } void stopAlarm() { isAlarming = false; musicActive = false; myDFPlayer.pause(); digitalWrite(RED_LED_PIN, HIGH); digitalWrite(WHITE_LED_PIN, HIGH); digitalWrite(BLUE_LEDS_PIN, HIGH); } void handleLEDs() { if (isAlarming) { if (millis() - lastBlinkTime > 100) { lastBlinkTime = millis(); ledState = !ledState; digitalWrite(RED_LED_PIN, ledState); digitalWrite(WHITE_LED_PIN, ledState); digitalWrite(BLUE_LEDS_PIN, ledState); } } else { digitalWrite(RED_LED_PIN, HIGH); digitalWrite(WHITE_LED_PIN, HIGH); digitalWrite(BLUE_LEDS_PIN, HIGH); } } void handleBluetooth() { if (justConnected) { uint8_t cnct[] = {0x39, 0x54, 0x58, 0x78}; display.setSegments(cnct); delay(1500); justConnected = false; } if (msgReceived) { uint8_t recd[] = {0x50, 0x79, 0x58, 0x5E}; display.setSegments(recd); delay(1000); msgReceived = false; } if (btMessage != "") { btMessage.toUpperCase(); if (btMessage.startsWith("STOP")) stopAlarm(); else if (btMessage.startsWith("ALARM ")) { String t = btMessage.substring(6); int split = t.indexOf(':'); if (split > 0) { alarmHour = t.substring(0, split).toInt(); alarmMinute = t.substring(split+1).toInt(); alarmActive = true; display.showNumberDecEx((alarmHour*100)+alarmMinute, 0x40, true); delay(2000); } } else if (btMessage.startsWith("PLAY ")) { int s = btMessage.substring(5).toInt(); myDFPlayer.play(s); musicActive = true; } else if (btMessage.toInt() > 0) { int s = btMessage.toInt(); myDFPlayer.play(s); musicActive = true; } btMessage = ""; } }