From ca7798d39b9865f2f1e0a5d2986b42e59ac8d7c5 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 13:41:44 +0000 Subject: [PATCH 01/17] Create FLUX_CAPACITOR.ino - v1 Modified daylight saving to occur on last Sundays in March and October. Various other changes compared to the original. --- FLUX_CAPACITOR.ino | 518 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 FLUX_CAPACITOR.ino diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino new file mode 100644 index 0000000..3901267 --- /dev/null +++ b/FLUX_CAPACITOR.ino @@ -0,0 +1,518 @@ + +#include "WiFiManager.h" // https://github.com/tzapu/WiFiManager +#include "NTPClient.h" // https://github.com/arduino-libraries/NTPClient +#include "TM1637Display.h" // https://github.com/avishorp/TM1637 +#include "DFRobotDFPlayerMini.h" // https://github.com/DFRobot/DFRobotDFPlayerMini +#include "ESP32_WS2812_Lib.h" +#include + + +bool enableAudio = false; + +//========================USEFUL VARIABLES============================= +int UTC = 1; //Set your time zone ex: france = UTC+2, UK = UTC+1 +uint16_t notification_volume = 25; //Set volume value. From 0 to 30 +int Display_backlight = 3; // Set displays brightness 0 to 7; +int snooze = 5; // Snooze time in minute +//===================================================================== + +#define DISPLAY_CLK 21 +#define DISPLAY_DIO 22 +#define SET_STOP_BUTTON 34 +#define HOUR_BUTTON 33 +#define MINUTE_BUTTON 32 +#define FPSerial Serial1 +#define LEDS_PIN 4 +#define LEDS_COUNT 24 +#define EEPROM_SIZE 12 +#define CHANNEL 0 + + +const byte RXD2 = 16; // Connects to module's TX => 16 +const byte TXD2 = 17; // Connects to module's RX => 17 + +DFRobotDFPlayerMini myDFPlayer; +void printDetail(uint8_t type, int value); + +float counter = 0; +int hours = 0; +int minutes = 0; +int flag_alarm = 0; +int alarm_on_off = 1; +int h = 0; +const long utcOffsetInSeconds = 0; // GMT +int Play_finished = 0; +int easter_egg = 0; +bool res; + +// Define NTP Client to get time +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds *UTC); +TM1637Display red1(DISPLAY_CLK, DISPLAY_DIO); +//Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); +ESP32_WS2812 pixels = ESP32_WS2812(LEDS_COUNT, LEDS_PIN, CHANNEL, TYPE_GRB); + +void setup() { + + pinMode(SET_STOP_BUTTON, INPUT); + pinMode(HOUR_BUTTON, INPUT); + pinMode(MINUTE_BUTTON, INPUT); + pinMode(26, OUTPUT); + pinMode(LEDS_PIN, OUTPUT); + + red1.setBrightness(Display_backlight); + pixels.begin(); + pixels.setBrightness(0); + pixels.show(); + Serial.begin(9600); + + //Init EEPROM + EEPROM.begin(EEPROM_SIZE); + minutes = EEPROM.read(0); + hours = EEPROM.read(1); + + + WiFiManager manager; + + manager.setConnectTimeout(10); + manager.setConnectRetries(5); + if (manager.getWiFiIsSaved()) { + manager.setEnableConfigPortal(false); + if (!manager.autoConnect("FLUX_CAPACITOR", "password")) { + manager.setEnableConfigPortal(true); + manager.setTimeout(60); + if (!manager.autoConnect("FLUX_CAPACITOR", "password")) { + Serial.println("Connection failed, restarting..."); + ESP.restart(); // Reset and try again + } + } + } + delay(3000); + + + // Refer to this https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/ + + // WiFi.mode(WIFI_STA); + // WiFi.begin("WLAN4", "MARZIPAN"); + + // while ( WiFi.status() != WL_CONNECTED ) { + // delay ( 500 ); + // Serial.print ( "." ); + // } + + + + timeClient.begin(); + + if (enableAudio) { + FPSerial.begin(9600, SERIAL_8N1, RXD2, TXD2); + Serial.println(); + Serial.println(F("DFRobot DFPlayer Mini Demo")); + Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); + + if (!myDFPlayer.begin(FPSerial, /*isACK = */ true, /*doReset = */ true)) { //Use serial to communicate with mp3. + Serial.println(F("Unable to begin:")); + Serial.println(F("1.Please recheck the connection!")); + Serial.println(F("2.Please insert the SD card!")); + while (true) { + delay(0); // Code to compatible with ESP8266 watch dog. + } + } + + Serial.println(F("DFPlayer Mini online.")); + + myDFPlayer.volume(notification_volume); + myDFPlayer.play(10); //Play the first mp3 + delay(100); + Play_finished = 0; + } + for (int v = 0; v < 30; v++) { + //pixels.clear(); + for (int i = 0; i < 8; i++) { + pixels.setLedColorData(i, 100, 100, 100); + pixels.setLedColorData((i + 8), 100, 100, 100); + pixels.setLedColorData((i + 16), 100, 100, 100); + delay(10); + pixels.setBrightness(20 + v); + pixels.show(); + } + } + + for (int t = 50; t > -1; t--) { + pixels.setBrightness(t); + delay(40); + pixels.show(); + } + + Serial.println("\n Starting"); + if (enableAudio) { + myDFPlayer.play(14); + } +} + +void loop() { + + //pixels.clear(); + show_hour(); + h = 1; + Play_finished = 0; + pixels.setBrightness(0); + + timeClient.update(); + Serial.print("Time: "); + Serial.println(timeClient.getFormattedTime()); + unsigned long epochTime = timeClient.getEpochTime(); + struct tm *ptm = gmtime((time_t *)&epochTime); + int currentYear = ptm->tm_year + 1900; + Serial.print("Year: "); + Serial.println(currentYear); + + int monthDay = ptm->tm_mday; + Serial.print("Month day: "); + Serial.println(monthDay); + + int currentMonth = ptm->tm_mon + 1; + Serial.print("Month: "); + Serial.println(currentMonth); + + if (enableAudio) { + if (myDFPlayer.available()) { + printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. + } + } + + if (digitalRead(SET_STOP_BUTTON) == true) { + Setup_alarm(); + if (enableAudio) myDFPlayer.stop(); + digitalWrite(26, LOW); + } + + if (hours == timeClient.getHours() && flag_alarm == 0 && alarm_on_off == 1) { + if (minutes == timeClient.getMinutes()) { + flag_alarm = 1; + alarm(); + Serial.println("Sortie alarm"); + } + } + + if (alarm_on_off == 1) { + digitalWrite(26, HIGH); + } else digitalWrite(26, LOW); + + if (minutes != timeClient.getMinutes()) { flag_alarm = 0; } + + if (easter_egg == 1) { + //pixels.clear(); + for (int i = 0; i < 8; i++) { + pixels.setLedColorData(i, 255, 110, 14); + pixels.setLedColorData((i + 8), 255, 110, 14); + pixels.setLedColorData((i + 16), 255, 110, 14); + delay(20); + pixels.setBrightness(23); + pixels.show(); + } + } + + if (digitalRead(MINUTE_BUTTON) == true && digitalRead(HOUR_BUTTON) == true) // Push Min and Hour simultanetly to switch ON or OFF the alarm + { + if (alarm_on_off == 1) { + alarm_on_off = 0; + } else alarm_on_off = 1; + + red1.showNumberDecEx(00, 0b01000000, true, 2, 0); + red1.showNumberDecEx(alarm_on_off, 0b01000000, true, 2, 2); + delay(1000); + + if (digitalRead(MINUTE_BUTTON) == true) // Push Min and Hour simultanetly to switch ON or OFF the alarm + { + if (easter_egg == 0) { + easter_egg = 1; + if (enableAudio) myDFPlayer.play(12); + } else { + easter_egg = 0; + pixels.setBrightness(0); + if (enableAudio) myDFPlayer.play(13); + pixels.show(); + } + } + } + + // if((currentMonth*30 + monthDay) >= 121 && (currentMonth*30 + monthDay) < 331){ + // timeClient.setTimeOffset(utcOffsetInSeconds*UTC);} // Change daylight saving time - Summer - change 31/03 at 00:00 + // else {timeClient.setTimeOffset((utcOffsetInSeconds*UTC) - 3600);} // Change daylight saving time - Winter - change 31/10 at 00:00 + + // Check if DST is active for the current date + if (isDST(currentMonth, monthDay, currentYear)) { + // Summer - DST is in effect (last Sunday of March to last Sunday of October) + timeClient.setTimeOffset(utcOffsetInSeconds + (UTC * 3600)); // Add 1 hour for DST + Serial.println("DST is active, UTC+1"); + } else { + // Winter - DST is not in effect + timeClient.setTimeOffset(utcOffsetInSeconds); // Standard time + Serial.println("DST is not active, UTC"); + } +} + +void alarm() { + // count 0 to 88 m/h + for (int u = 0; u < 89; u++) { + delay(12 - (u / 8)); + red1.showNumberDecEx(00, 0b01000000, true, 2, 0); + red1.showNumberDecEx(u, 0b01000000, true, 2, 2); + + //Flux capacitor acceleration + //pixels.clear(); + for (int i = 0; i < 8; i++) { + pixels.setLedColorData(i, 255, (110 + h), 13 + h); + pixels.setLedColorData((i + 8), 255, (110 + h), 13 + h); + pixels.setLedColorData((i + 16), 255, (110 + h), 13 + h); + delay(12 - (h / 8)); + pixels.setBrightness(20 + h); + pixels.show(); + + if (digitalRead(SET_STOP_BUTTON) == true) { u = 89; } + + if (digitalRead(MINUTE_BUTTON) == true || digitalRead(HOUR_BUTTON) == true) // Snooze if you push MIN or HOUR button + { + Snooze(); + } + } + h = h + 1; + } + + if (enableAudio) myDFPlayer.play(random(1, 9)); //Playing the alarm sound + delay(1500); + + + while (digitalRead(SET_STOP_BUTTON) == false) { + h = 1; + show_hour(); + // Serial.println("dans la boucle"); + digitalWrite(26, HIGH); + + // If you're not wake-up at the first song, it plays the next one + if (enableAudio) { + if (myDFPlayer.available()) { + printDetail(myDFPlayer.readType(), myDFPlayer.read()); + if (Play_finished == 1) { + Play_finished = 0; + Serial.println("Next song"); + myDFPlayer.play(random(1, 9)); //Playing the alarm sound + } + } + } + + //That's bzzzz the Neopixel + //pixels.clear(); + for (int i = 0; i < 8; i++) { + pixels.setLedColorData(i, 255, 200, 105); + pixels.setLedColorData((i + 8), 255, 200, 105); + pixels.setLedColorData((i + 16), 255, 200, 105); + delay(5); + pixels.setBrightness(110); + pixels.show(); + //Serial.println("Boucle neopixel"); + } + + if (digitalRead(MINUTE_BUTTON) == true || digitalRead(HOUR_BUTTON) == true) // Snooze if you push MIN or HOUR button + { + Snooze(); + } + } + pixels.setBrightness(0); + pixels.show(); +} + +void show_hour() { + timeClient.update(); + red1.showNumberDecEx(timeClient.getHours(), 0b01000000, true, 2, 0); + red1.showNumberDecEx(timeClient.getMinutes(), 0b01000000, true, 2, 2); +} + +// Snooze if you'd like to sleep few minutes more +// You can set the snooze time with the "snooze" variable at the beginning of the code +void Snooze() { + + if (enableAudio) myDFPlayer.stop(); + pixels.setBrightness(5); + pixels.show(); + + for (int i = 0; i < (snooze * 600); i++) { + waitMilliseconds(68); + show_hour(); + Serial.println("snooze"); + Serial.println(i); + if (digitalRead(SET_STOP_BUTTON) == true) { i = snooze * 600; } + } + + alarm(); + Play_finished = 0; +} + +void Setup_alarm() { + + while (digitalRead(SET_STOP_BUTTON) == true) { + if (digitalRead(MINUTE_BUTTON) == true) { + minutes = minutes + 1; + } + + if (digitalRead(HOUR_BUTTON) == true) { + hours = hours + 1; + } + + red1.showNumberDecEx(hours, 0b01000000, true, 2, 0); + red1.showNumberDecEx(minutes, 0b01000000, true, 2, 2); + + delay(100); + + if (minutes > 59) { minutes = 0; } + if (hours > 23) { hours = 0; } + } + EEPROM.write(0, minutes); + EEPROM.write(1, hours); + EEPROM.commit(); +} + +void printDetail(uint8_t type, int value) { + switch (type) { + case TimeOut: + Serial.println(F("Time Out!")); + break; + case WrongStack: + Serial.println(F("Stack Wrong!")); + break; + case DFPlayerCardInserted: + Serial.println(F("Card Inserted!")); + break; + case DFPlayerCardRemoved: + Serial.println(F("Card Removed!")); + break; + case DFPlayerCardOnline: + Serial.println(F("Card Online!")); + break; + case DFPlayerUSBInserted: + Serial.println("USB Inserted!"); + break; + case DFPlayerUSBRemoved: + Serial.println("USB Removed!"); + break; + case DFPlayerPlayFinished: + Serial.print(F("Number:")); + Serial.print(value); + Serial.println(F(" Play Finished!")); + Play_finished = 1; + break; + case DFPlayerError: + Serial.print(F("DFPlayerError:")); + switch (value) { + case Busy: + Serial.println(F("Card not found")); + break; + case Sleeping: + Serial.println(F("Sleeping")); + break; + case SerialWrongStack: + Serial.println(F("Get Wrong Stack")); + break; + case CheckSumNotMatch: + Serial.println(F("Check Sum Not Match")); + break; + case FileIndexOut: + Serial.println(F("File Index Out of Bound")); + break; + case FileMismatch: + Serial.println(F("Cannot Find File")); + break; + case Advertise: + Serial.println(F("In Advertise")); + break; + default: + break; + } + break; + default: + break; + } +} + +void waitMilliseconds(uint16_t msWait) { + uint32_t start = millis(); + + while ((millis() - start) < msWait) { + // calling mp3.loop() periodically allows for notifications + // to be handled without interrupts + delay(1); + } +} + + + +// Function to check if the year is a leap year +bool isLeapYear(int year) { + // Leap year if divisible by 4, but not divisible by 100 unless also divisible by 400 + return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); +} + +// Function to calculate the day of the year (1 = Jan 1st, 365 = Dec 31st) +int getDayOfYear(int currentMonth, int monthDay, int year) { + int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // Normal year + + // Adjust February for leap year + if (isLeapYear(year)) { + daysInMonth[1] = 29; // February has 29 days in a leap year + } + + int dayOfYear = 0; + + // Add the days of the previous months + for (int i = 0; i < currentMonth - 1; i++) { + dayOfYear += daysInMonth[i]; + } + + // Add the current month's days + dayOfYear += monthDay; + + return dayOfYear; +} + +// Function to calculate if a given year has daylight saving time (DST) +bool isDST(int currentMonth, int monthDay, int year) { + // Get the last Sunday in March (DST starts in the UK) + int lastSundayInMarch = getLastSundayOfMonth(3, year); + + // Get the last Sunday in October (DST ends in the UK) + int lastSundayInOctober = getLastSundayOfMonth(10, year); + + // Check if the current date is between the last Sunday in March and the last Sunday in October + int dayOfYear = getDayOfYear(currentMonth, monthDay, year); + + // If we're between March and October, DST is in effect + return dayOfYear >= lastSundayInMarch && dayOfYear <= lastSundayInOctober; +} + +// Function to calculate the last Sunday of a given month in a given year +int getLastSundayOfMonth(int month, int year) { + // Get the last day of the month (e.g., 31 for March, 30 for April, etc.) + int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + if (isLeapYear(year)) { + daysInMonth[1] = 29; // February has 29 days in a leap year + } + int lastDay = daysInMonth[month - 1]; + + // Find the day of the week for the last day of the month + tm timeinfo; + timeinfo.tm_year = year - 1900; // Year since 1900 + timeinfo.tm_mon = month - 1; // Month (0-11) + timeinfo.tm_mday = lastDay; + timeinfo.tm_hour = 12; // Set to noon to avoid DST issues + timeinfo.tm_min = 0; + timeinfo.tm_sec = 0; + mktime(&timeinfo); // Normalize tm struct + + // Get the weekday (0 = Sunday, 1 = Monday, ..., 6 = Saturday) + int weekday = timeinfo.tm_wday; + + // Calculate the last Sunday of the month + int lastSunday = lastDay - weekday; + return getDayOfYear(month, lastSunday, year); // Convert to day of the year +} From aff67af0782ee3db36ec099836af357a01dc7069 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 13:43:33 +0000 Subject: [PATCH 02/17] Update FLUX_CAPACITOR.ino - v2 Ported clock code from https://github.com/howarthcd/BTTF_LAMP_AND_CLOCK/blob/main/BTTF_LAMP_CLOCK.ino --- FLUX_CAPACITOR.ino | 232 ++++++++++++++++++++++++--------------------- 1 file changed, 124 insertions(+), 108 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index 3901267..03cdbc2 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -1,3 +1,4 @@ +// v2 - Ported code from BTTF logo & clock unit #include "WiFiManager.h" // https://github.com/tzapu/WiFiManager #include "NTPClient.h" // https://github.com/arduino-libraries/NTPClient @@ -5,14 +6,14 @@ #include "DFRobotDFPlayerMini.h" // https://github.com/DFRobot/DFRobotDFPlayerMini #include "ESP32_WS2812_Lib.h" #include +#include //https://playground.arduino.cc/Code/Time/ bool enableAudio = false; //========================USEFUL VARIABLES============================= -int UTC = 1; //Set your time zone ex: france = UTC+2, UK = UTC+1 uint16_t notification_volume = 25; //Set volume value. From 0 to 30 -int Display_backlight = 3; // Set displays brightness 0 to 7; +int clockBrightness = 2; // Set displays brightness 0 to 7; int snooze = 5; // Snooze time in minute //===================================================================== @@ -27,6 +28,8 @@ int snooze = 5; // Snooze time in minute #define EEPROM_SIZE 12 #define CHANNEL 0 +#define UTC_OFFSET 1 + const byte RXD2 = 16; // Connects to module's TX => 16 const byte TXD2 = 17; // Connects to module's RX => 17 @@ -45,33 +48,56 @@ int Play_finished = 0; int easter_egg = 0; bool res; -// Define NTP Client to get time -WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds *UTC); -TM1637Display red1(DISPLAY_CLK, DISPLAY_DIO); -//Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); +int currentMinutes = 0, currentHours = 0, currentYear = 0, currentMonth = 0, monthDay = 0, previousMinutes = 0; +long epochTime = 0; +unsigned long lastColonToggleTime = 0; +bool colonVisible = true; // Start with the colon visible +const unsigned long COLON_FLASH_INTERVAL = 1000; // Interval for flashing (1000ms) + ESP32_WS2812 pixels = ESP32_WS2812(LEDS_COUNT, LEDS_PIN, CHANNEL, TYPE_GRB); +TM1637Display red1(DISPLAY_CLK, DISPLAY_DIO); + +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds* UTC_OFFSET); + +int timerCount = 5; // Set the timerCount artificially high so that the first display update happens immediately. +bool skipTimerLogic = false; + +hw_timer_t* myTimer = NULL; +// timer interrupt ISR +void IRAM_ATTR onTimer() { + // Toggle the colon + colonVisible = !colonVisible; + // Only update if we have previously retrieved the time + // and we are not in the middle of processing a button press. + if (currentYear > 0 && !skipTimerLogic) updateTimeDisplay(); + + epochTime += 1; + + // Keep track of how many times we have got here. + timerCount += 1; +} + void setup() { + Serial.begin(9600); + pinMode(SET_STOP_BUTTON, INPUT); pinMode(HOUR_BUTTON, INPUT); pinMode(MINUTE_BUTTON, INPUT); pinMode(26, OUTPUT); pinMode(LEDS_PIN, OUTPUT); - red1.setBrightness(Display_backlight); pixels.begin(); pixels.setBrightness(0); pixels.show(); - Serial.begin(9600); //Init EEPROM EEPROM.begin(EEPROM_SIZE); minutes = EEPROM.read(0); hours = EEPROM.read(1); - WiFiManager manager; manager.setConnectTimeout(10); @@ -89,19 +115,6 @@ void setup() { } delay(3000); - - // Refer to this https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/ - - // WiFi.mode(WIFI_STA); - // WiFi.begin("WLAN4", "MARZIPAN"); - - // while ( WiFi.status() != WL_CONNECTED ) { - // delay ( 500 ); - // Serial.print ( "." ); - // } - - - timeClient.begin(); if (enableAudio) { @@ -126,6 +139,25 @@ void setup() { delay(100); Play_finished = 0; } + + // Get the current time, retry if unsuccessful + for (int i = 0; i <= 20; i++) { + timeClient.update(); + epochTime = timeClient.getEpochTime(); + if (epochTime > 0) break; + Serial.print("Could not retrieve time from NTP server..."); + delay(50); + } + + if (epochTime == 0) ESP.restart(); // Reset and try again + + // Define a timer. The timer will be used to toggle the time + // colon on/off every 1s. + uint64_t alarmLimit = 1000000; + myTimer = timerBegin(1000000); // timer frequency + timerAttachInterrupt(myTimer, &onTimer); + timerAlarm(myTimer, alarmLimit, true, 0); + for (int v = 0; v < 30; v++) { //pixels.clear(); for (int i = 0; i < 8; i++) { @@ -153,34 +185,34 @@ void setup() { void loop() { //pixels.clear(); - show_hour(); + //show_hour(); h = 1; Play_finished = 0; pixels.setBrightness(0); - timeClient.update(); - Serial.print("Time: "); - Serial.println(timeClient.getFormattedTime()); - unsigned long epochTime = timeClient.getEpochTime(); - struct tm *ptm = gmtime((time_t *)&epochTime); - int currentYear = ptm->tm_year + 1900; - Serial.print("Year: "); - Serial.println(currentYear); - - int monthDay = ptm->tm_mday; - Serial.print("Month day: "); - Serial.println(monthDay); - - int currentMonth = ptm->tm_mon + 1; - Serial.print("Month: "); - Serial.println(currentMonth); - - if (enableAudio) { - if (myDFPlayer.available()) { - printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. + // Check to see if the time displays need to be updated. + if (timerCount > 0 && colonVisible) { + previousMinutes = currentMinutes; + setTime(epochTime); + currentYear = year(); + currentMonth = month(); + monthDay = day(); + currentHours = hour(); + currentMinutes = minute(); + if (currentYear >= 2025 && previousMinutes != currentMinutes) { + updateTimeDisplay(); + red1.setBrightness(clockBrightness); + checkDSTAndSetOffset(); + timerCount = 0; } } + // if (enableAudio) { + // if (myDFPlayer.available()) { + // printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. + // } + // } + if (digitalRead(SET_STOP_BUTTON) == true) { Setup_alarm(); if (enableAudio) myDFPlayer.stop(); @@ -201,17 +233,17 @@ void loop() { if (minutes != timeClient.getMinutes()) { flag_alarm = 0; } - if (easter_egg == 1) { - //pixels.clear(); - for (int i = 0; i < 8; i++) { - pixels.setLedColorData(i, 255, 110, 14); - pixels.setLedColorData((i + 8), 255, 110, 14); - pixels.setLedColorData((i + 16), 255, 110, 14); - delay(20); - pixels.setBrightness(23); - pixels.show(); - } - } + // if (easter_egg == 1) { + // //pixels.clear(); + // for (int i = 0; i < 8; i++) { + // pixels.setLedColorData(i, 255, 110, 14); + // pixels.setLedColorData((i + 8), 255, 110, 14); + // pixels.setLedColorData((i + 16), 255, 110, 14); + // delay(20); + // pixels.setBrightness(23); + // pixels.show(); + // } + // } if (digitalRead(MINUTE_BUTTON) == true && digitalRead(HOUR_BUTTON) == true) // Push Min and Hour simultanetly to switch ON or OFF the alarm { @@ -236,20 +268,31 @@ void loop() { } } } +} + + +void updateTimeDisplay() { + + // For the time, add the flashing colon logic + uint8_t hour = currentHours; + uint8_t minute = currentMinutes; - // if((currentMonth*30 + monthDay) >= 121 && (currentMonth*30 + monthDay) < 331){ - // timeClient.setTimeOffset(utcOffsetInSeconds*UTC);} // Change daylight saving time - Summer - change 31/03 at 00:00 - // else {timeClient.setTimeOffset((utcOffsetInSeconds*UTC) - 3600);} // Change daylight saving time - Winter - change 31/10 at 00:00 + if (colonVisible) { + red1.showNumberDecEx(hour, 0b01000000, true, 2, 0); // Display hours with colon + red1.showNumberDecEx(minute, 0b01000000, true, 2, 2); // Display minutes with colon + } else { + red1.showNumberDecEx(hour, 0b00000000, true, 2, 0); // Display hours without colon + red1.showNumberDecEx(minute, 0b00000000, true, 2, 2); // Display minutes without colon + } +} - // Check if DST is active for the current date +void checkDSTAndSetOffset() { if (isDST(currentMonth, monthDay, currentYear)) { - // Summer - DST is in effect (last Sunday of March to last Sunday of October) - timeClient.setTimeOffset(utcOffsetInSeconds + (UTC * 3600)); // Add 1 hour for DST - Serial.println("DST is active, UTC+1"); + adjustTime(utcOffsetInSeconds + (UTC_OFFSET * 3600)); // Apply DST + Serial.println("DST active, UTC+1"); } else { - // Winter - DST is not in effect - timeClient.setTimeOffset(utcOffsetInSeconds); // Standard time - Serial.println("DST is not active, UTC"); + adjustTime(utcOffsetInSeconds); // Standard time + Serial.println("DST inactive, UTC"); } } @@ -286,7 +329,7 @@ void alarm() { while (digitalRead(SET_STOP_BUTTON) == false) { h = 1; - show_hour(); + updateTimeDisplay(); // Serial.println("dans la boucle"); digitalWrite(26, HIGH); @@ -323,23 +366,18 @@ void alarm() { pixels.show(); } -void show_hour() { - timeClient.update(); - red1.showNumberDecEx(timeClient.getHours(), 0b01000000, true, 2, 0); - red1.showNumberDecEx(timeClient.getMinutes(), 0b01000000, true, 2, 2); -} // Snooze if you'd like to sleep few minutes more // You can set the snooze time with the "snooze" variable at the beginning of the code void Snooze() { - if (enableAudio) myDFPlayer.stop(); + if (enableAudio) myDFPlayer.stop(); pixels.setBrightness(5); pixels.show(); for (int i = 0; i < (snooze * 600); i++) { waitMilliseconds(68); - show_hour(); + updateTimeDisplay(); Serial.println("snooze"); Serial.println(i); if (digitalRead(SET_STOP_BUTTON) == true) { i = snooze * 600; } @@ -445,61 +483,39 @@ void waitMilliseconds(uint16_t msWait) { } } - - -// Function to check if the year is a leap year -bool isLeapYear(int year) { - // Leap year if divisible by 4, but not divisible by 100 unless also divisible by 400 - return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); +bool isDST(int currentMonth, int monthDay, int currentYear) { + int lastSundayInMarch = getLastSundayOfMonth(3, currentYear); + int lastSundayInOctober = getLastSundayOfMonth(10, currentYear); + int dayOfYear = getDayOfYear(currentMonth, monthDay, currentYear); + return (dayOfYear >= lastSundayInMarch && dayOfYear < lastSundayInOctober); } -// Function to calculate the day of the year (1 = Jan 1st, 365 = Dec 31st) int getDayOfYear(int currentMonth, int monthDay, int year) { - int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // Normal year - - // Adjust February for leap year + int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (isLeapYear(year)) { - daysInMonth[1] = 29; // February has 29 days in a leap year + daysInMonth[1] = 29; // Leap year adjustment } int dayOfYear = 0; - - // Add the days of the previous months for (int i = 0; i < currentMonth - 1; i++) { dayOfYear += daysInMonth[i]; } - - // Add the current month's days - dayOfYear += monthDay; - - return dayOfYear; + return dayOfYear + monthDay; } -// Function to calculate if a given year has daylight saving time (DST) -bool isDST(int currentMonth, int monthDay, int year) { - // Get the last Sunday in March (DST starts in the UK) - int lastSundayInMarch = getLastSundayOfMonth(3, year); - - // Get the last Sunday in October (DST ends in the UK) - int lastSundayInOctober = getLastSundayOfMonth(10, year); - - // Check if the current date is between the last Sunday in March and the last Sunday in October - int dayOfYear = getDayOfYear(currentMonth, monthDay, year); - - // If we're between March and October, DST is in effect - return dayOfYear >= lastSundayInMarch && dayOfYear <= lastSundayInOctober; +bool isLeapYear(int year) { + return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); } -// Function to calculate the last Sunday of a given month in a given year int getLastSundayOfMonth(int month, int year) { - // Get the last day of the month (e.g., 31 for March, 30 for April, etc.) + // Array with number of days per month int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (isLeapYear(year)) { daysInMonth[1] = 29; // February has 29 days in a leap year } - int lastDay = daysInMonth[month - 1]; + int lastDay = daysInMonth[month - 1]; // Last day of the month - // Find the day of the week for the last day of the month + // Time structure to hold information about the last day of the month tm timeinfo; timeinfo.tm_year = year - 1900; // Year since 1900 timeinfo.tm_mon = month - 1; // Month (0-11) From a8288b43a2c42adc574040cb51c74cfc5de5d9e7 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 13:44:42 +0000 Subject: [PATCH 03/17] Update FLUX_CAPACITOR.ino - v3 - Prevent display switching to time and back when buttons are pressed. - Clear the neopixels correctly. - Removed snooze code for the time being. - Don't update the display colon when the alarm is in progress to avoid gaps in between neopixel updates. --- FLUX_CAPACITOR.ino | 228 ++++++++++++++++++++++++--------------------- 1 file changed, 124 insertions(+), 104 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index 03cdbc2..8c9b20d 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -1,4 +1,8 @@ -// v2 - Ported code from BTTF logo & clock unit +// v2 - Ported code from BTTF logo & clock unit. +// v3 - Prevent display switching to time and back when buttons are pressed. +// - Clear the neopixels correctly. +// - Removed snooze code for the time being. +// - Don't update the display colon when the alarm is in progress to avoid gaps in between neopixel updates. #include "WiFiManager.h" // https://github.com/tzapu/WiFiManager #include "NTPClient.h" // https://github.com/arduino-libraries/NTPClient @@ -38,8 +42,8 @@ DFRobotDFPlayerMini myDFPlayer; void printDetail(uint8_t type, int value); float counter = 0; -int hours = 0; -int minutes = 0; +int alarmHours = 0; +int alarmMinutes = 0; int flag_alarm = 0; int alarm_on_off = 1; int h = 0; @@ -48,7 +52,7 @@ int Play_finished = 0; int easter_egg = 0; bool res; -int currentMinutes = 0, currentHours = 0, currentYear = 0, currentMonth = 0, monthDay = 0, previousMinutes = 0; +int currentMinutes = 0, currentHours = 0, currentYear = 0, currentMonth = 0, currentDay = 0, previousMinutes = 0; long epochTime = 0; unsigned long lastColonToggleTime = 0; bool colonVisible = true; // Start with the colon visible @@ -69,10 +73,13 @@ hw_timer_t* myTimer = NULL; void IRAM_ATTR onTimer() { // Toggle the colon colonVisible = !colonVisible; + // Only update if we have previously retrieved the time - // and we are not in the middle of processing a button press. + // and we are not in the middle of processing a button press or + // other time-sensitive action. if (currentYear > 0 && !skipTimerLogic) updateTimeDisplay(); + // Increment the time by 1s. epochTime += 1; // Keep track of how many times we have got here. @@ -95,8 +102,8 @@ void setup() { //Init EEPROM EEPROM.begin(EEPROM_SIZE); - minutes = EEPROM.read(0); - hours = EEPROM.read(1); + alarmMinutes = EEPROM.read(0); + alarmHours = EEPROM.read(1); WiFiManager manager; @@ -159,7 +166,11 @@ void setup() { timerAlarm(myTimer, alarmLimit, true, 0); for (int v = 0; v < 30; v++) { - //pixels.clear(); + // clear the pixels, equivalent to pixels.clear(); + for (int i = 0; i < 24; i++) { + pixels.setLedColorData(i, 0, 0, 0); + } + for (int i = 0; i < 8; i++) { pixels.setLedColorData(i, 100, 100, 100); pixels.setLedColorData((i + 8), 100, 100, 100); @@ -191,36 +202,28 @@ void loop() { pixels.setBrightness(0); // Check to see if the time displays need to be updated. - if (timerCount > 0 && colonVisible) { - previousMinutes = currentMinutes; - setTime(epochTime); - currentYear = year(); - currentMonth = month(); - monthDay = day(); - currentHours = hour(); - currentMinutes = minute(); - if (currentYear >= 2025 && previousMinutes != currentMinutes) { - updateTimeDisplay(); - red1.setBrightness(clockBrightness); - checkDSTAndSetOffset(); - timerCount = 0; - } + maybeUpdateClock(); + + if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON) || digitalRead(SET_STOP_BUTTON)) { + skipTimerLogic = true; + } else { + skipTimerLogic = false; } - // if (enableAudio) { - // if (myDFPlayer.available()) { - // printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. - // } - // } + if (enableAudio) { + if (myDFPlayer.available()) { + printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. + } + } - if (digitalRead(SET_STOP_BUTTON) == true) { + if (digitalRead(SET_STOP_BUTTON)) { Setup_alarm(); if (enableAudio) myDFPlayer.stop(); digitalWrite(26, LOW); } - if (hours == timeClient.getHours() && flag_alarm == 0 && alarm_on_off == 1) { - if (minutes == timeClient.getMinutes()) { + if (alarmHours == currentHours && flag_alarm == 0 && alarm_on_off == 1) { + if (alarmMinutes == currentMinutes) { flag_alarm = 1; alarm(); Serial.println("Sortie alarm"); @@ -231,21 +234,9 @@ void loop() { digitalWrite(26, HIGH); } else digitalWrite(26, LOW); - if (minutes != timeClient.getMinutes()) { flag_alarm = 0; } - - // if (easter_egg == 1) { - // //pixels.clear(); - // for (int i = 0; i < 8; i++) { - // pixels.setLedColorData(i, 255, 110, 14); - // pixels.setLedColorData((i + 8), 255, 110, 14); - // pixels.setLedColorData((i + 16), 255, 110, 14); - // delay(20); - // pixels.setBrightness(23); - // pixels.show(); - // } - // } - - if (digitalRead(MINUTE_BUTTON) == true && digitalRead(HOUR_BUTTON) == true) // Push Min and Hour simultanetly to switch ON or OFF the alarm + if (alarmMinutes != currentMinutes) { flag_alarm = 0; } + + if (digitalRead(MINUTE_BUTTON) && digitalRead(HOUR_BUTTON)) // Push Min and Hour simultanetly to switch ON or OFF the alarm { if (alarm_on_off == 1) { alarm_on_off = 0; @@ -255,22 +246,37 @@ void loop() { red1.showNumberDecEx(alarm_on_off, 0b01000000, true, 2, 2); delay(1000); - if (digitalRead(MINUTE_BUTTON) == true) // Push Min and Hour simultanetly to switch ON or OFF the alarm + if (digitalRead(MINUTE_BUTTON)) // Push Min and Hour simultanetly to switch ON or OFF the alarm { - if (easter_egg == 0) { - easter_egg = 1; - if (enableAudio) myDFPlayer.play(12); - } else { - easter_egg = 0; - pixels.setBrightness(0); - if (enableAudio) myDFPlayer.play(13); - pixels.show(); - } + easter_egg = 0; + pixels.setBrightness(0); + if (enableAudio) myDFPlayer.play(13); + pixels.show(); } } } +void maybeUpdateClock() { + if (timerCount > 0 && colonVisible && !digitalRead(MINUTE_BUTTON) && !digitalRead(HOUR_BUTTON) && !digitalRead(SET_STOP_BUTTON)) { + + Serial.println('Entered time display update...'); + previousMinutes = currentMinutes; + setTime(epochTime); + currentYear = year(); + currentMonth = month(); + currentDay = day(); + currentHours = hour(); + currentMinutes = minute(); + if (currentYear >= 2025 && previousMinutes != currentMinutes) { + updateTimeDisplay(); + red1.setBrightness(clockBrightness); + checkDSTAndSetOffset(); + timerCount = 0; + } + } +} + void updateTimeDisplay() { // For the time, add the flashing colon logic @@ -287,7 +293,7 @@ void updateTimeDisplay() { } void checkDSTAndSetOffset() { - if (isDST(currentMonth, monthDay, currentYear)) { + if (isDST(currentMonth, currentDay, currentYear)) { adjustTime(utcOffsetInSeconds + (UTC_OFFSET * 3600)); // Apply DST Serial.println("DST active, UTC+1"); } else { @@ -297,14 +303,19 @@ void checkDSTAndSetOffset() { } void alarm() { + skipTimerLogic = true; // count 0 to 88 m/h for (int u = 0; u < 89; u++) { delay(12 - (u / 8)); red1.showNumberDecEx(00, 0b01000000, true, 2, 0); red1.showNumberDecEx(u, 0b01000000, true, 2, 2); - //Flux capacitor acceleration - //pixels.clear(); + // Flux capacitor acceleration section. + // Clear the pixels, equivalent to pixels.clear(); + for (int i = 0; i < 24; i++) { + pixels.setLedColorData(i, 0, 0, 0); + } + for (int i = 0; i < 8; i++) { pixels.setLedColorData(i, 255, (110 + h), 13 + h); pixels.setLedColorData((i + 8), 255, (110 + h), 13 + h); @@ -313,23 +324,27 @@ void alarm() { pixels.setBrightness(20 + h); pixels.show(); - if (digitalRead(SET_STOP_BUTTON) == true) { u = 89; } + if (digitalRead(SET_STOP_BUTTON)) { u = 89; } - if (digitalRead(MINUTE_BUTTON) == true || digitalRead(HOUR_BUTTON) == true) // Snooze if you push MIN or HOUR button - { - Snooze(); - } + // if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) // Snooze if you push MIN or HOUR button + // { + // //Snooze(); + // // Implement snooze here. + // } } h = h + 1; } + //skipTimerLogic = false; if (enableAudio) myDFPlayer.play(random(1, 9)); //Playing the alarm sound delay(1500); + colonVisible = true; + updateTimeDisplay(); - while (digitalRead(SET_STOP_BUTTON) == false) { + while (!digitalRead(SET_STOP_BUTTON)) { h = 1; - updateTimeDisplay(); + maybeUpdateClock(); // Serial.println("dans la boucle"); digitalWrite(26, HIGH); @@ -346,7 +361,9 @@ void alarm() { } //That's bzzzz the Neopixel - //pixels.clear(); + for (int i = 0; i < 24; i++) { + pixels.setLedColorData(i, 0, 0, 0); + } for (int i = 0; i < 8; i++) { pixels.setLedColorData(i, 255, 200, 105); pixels.setLedColorData((i + 8), 255, 200, 105); @@ -357,57 +374,60 @@ void alarm() { //Serial.println("Boucle neopixel"); } - if (digitalRead(MINUTE_BUTTON) == true || digitalRead(HOUR_BUTTON) == true) // Snooze if you push MIN or HOUR button - { - Snooze(); - } + // if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON) ) // Snooze if you push MIN or HOUR button + // { + // //Snooze(); + // // Implement snooze here. + // } } pixels.setBrightness(0); pixels.show(); + skipTimerLogic = false; } -// Snooze if you'd like to sleep few minutes more -// You can set the snooze time with the "snooze" variable at the beginning of the code -void Snooze() { +// // Snooze if you'd like to sleep few minutes more +// // You can set the snooze time with the "snooze" variable at the beginning of the code +// void Snooze() { +// skipTimerLogic = false; - if (enableAudio) myDFPlayer.stop(); - pixels.setBrightness(5); - pixels.show(); +// if (enableAudio) myDFPlayer.stop(); +// pixels.setBrightness(5); +// pixels.show(); - for (int i = 0; i < (snooze * 600); i++) { - waitMilliseconds(68); - updateTimeDisplay(); - Serial.println("snooze"); - Serial.println(i); - if (digitalRead(SET_STOP_BUTTON) == true) { i = snooze * 600; } - } +// for (int i = 0; i < (snooze * 600); i++) { +// waitMilliseconds(68); +// updateTimeDisplay(); +// Serial.println("snooze"); +// Serial.println(i); +// if (digitalRead(SET_STOP_BUTTON)) { i = snooze * 600; } +// } - alarm(); - Play_finished = 0; -} +// alarm(); +// Play_finished = 0; +// } void Setup_alarm() { - while (digitalRead(SET_STOP_BUTTON) == true) { - if (digitalRead(MINUTE_BUTTON) == true) { - minutes = minutes + 1; + while (digitalRead(SET_STOP_BUTTON)) { + if (digitalRead(MINUTE_BUTTON)) { + alarmMinutes = alarmMinutes + 1; } - if (digitalRead(HOUR_BUTTON) == true) { - hours = hours + 1; + if (digitalRead(HOUR_BUTTON)) { + alarmHours = alarmHours + 1; } - red1.showNumberDecEx(hours, 0b01000000, true, 2, 0); - red1.showNumberDecEx(minutes, 0b01000000, true, 2, 2); + red1.showNumberDecEx(alarmHours, 0b01000000, true, 2, 0); + red1.showNumberDecEx(alarmMinutes, 0b01000000, true, 2, 2); delay(100); - if (minutes > 59) { minutes = 0; } - if (hours > 23) { hours = 0; } + if (alarmMinutes > 59) { alarmMinutes = 0; } + if (alarmHours > 23) { alarmHours = 0; } } - EEPROM.write(0, minutes); - EEPROM.write(1, hours); + EEPROM.write(0, alarmMinutes); + EEPROM.write(1, alarmHours); EEPROM.commit(); } @@ -483,24 +503,24 @@ void waitMilliseconds(uint16_t msWait) { } } -bool isDST(int currentMonth, int monthDay, int currentYear) { - int lastSundayInMarch = getLastSundayOfMonth(3, currentYear); - int lastSundayInOctober = getLastSundayOfMonth(10, currentYear); - int dayOfYear = getDayOfYear(currentMonth, monthDay, currentYear); +bool isDST(int month, int day, int year) { + int lastSundayInMarch = getLastSundayOfMonth(3, year); + int lastSundayInOctober = getLastSundayOfMonth(10, year); + int dayOfYear = getDayOfYear(month, day, year); return (dayOfYear >= lastSundayInMarch && dayOfYear < lastSundayInOctober); } -int getDayOfYear(int currentMonth, int monthDay, int year) { +int getDayOfYear(int month, int day, int year) { int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (isLeapYear(year)) { daysInMonth[1] = 29; // Leap year adjustment } int dayOfYear = 0; - for (int i = 0; i < currentMonth - 1; i++) { + for (int i = 0; i < month - 1; i++) { dayOfYear += daysInMonth[i]; } - return dayOfYear + monthDay; + return dayOfYear + day; } bool isLeapYear(int year) { From d97a7d0148a08e9a40cc3d46c1fdf2363adefe15 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 13:49:24 +0000 Subject: [PATCH 04/17] Update FLUX_CAPACITOR.ino - v4 - Rework and re-implement snooze function. - Commented out all audio-related code for troubleshooting. - Added more serial output debugging statements. --- FLUX_CAPACITOR.ino | 280 +++++++++++++++++++++++++-------------------- 1 file changed, 153 insertions(+), 127 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index 8c9b20d..d790f03 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -3,6 +3,14 @@ // - Clear the neopixels correctly. // - Removed snooze code for the time being. // - Don't update the display colon when the alarm is in progress to avoid gaps in between neopixel updates. +// v4 - Rework and re-implement snooze function. +// Commented out all audio-related code for troubleshooting. +// Added more serial output debugging statements. +// +// +// +// TODO: Make use of EEPROM to record alarm on/off state and snooze status in case of accidental restart. +// TODO: Regular NTP server syncs to ensure that the clock doesn't drift too far. #include "WiFiManager.h" // https://github.com/tzapu/WiFiManager #include "NTPClient.h" // https://github.com/arduino-libraries/NTPClient @@ -17,8 +25,8 @@ bool enableAudio = false; //========================USEFUL VARIABLES============================= uint16_t notification_volume = 25; //Set volume value. From 0 to 30 -int clockBrightness = 2; // Set displays brightness 0 to 7; -int snooze = 5; // Snooze time in minute +int clockBrightness = 1; // Set displays brightness 0 to 7; +int snoozeLengthMinutes = 1; // Snooze time in minute //===================================================================== #define DISPLAY_CLK 21 @@ -38,7 +46,7 @@ int snooze = 5; // Snooze time in minute const byte RXD2 = 16; // Connects to module's TX => 16 const byte TXD2 = 17; // Connects to module's RX => 17 -DFRobotDFPlayerMini myDFPlayer; +//DFRobotDFPlayerMini myDFPlayer; void printDetail(uint8_t type, int value); float counter = 0; @@ -49,11 +57,13 @@ int alarm_on_off = 1; int h = 0; const long utcOffsetInSeconds = 0; // GMT int Play_finished = 0; -int easter_egg = 0; bool res; int currentMinutes = 0, currentHours = 0, currentYear = 0, currentMonth = 0, currentDay = 0, previousMinutes = 0; -long epochTime = 0; +long epochTimeCurrent = 0; +long epochTimeSnoozed = 0; +long epochTimeNTP = 0; +bool snoozed = false; unsigned long lastColonToggleTime = 0; bool colonVisible = true; // Start with the colon visible const unsigned long COLON_FLASH_INTERVAL = 1000; // Interval for flashing (1000ms) @@ -80,7 +90,7 @@ void IRAM_ATTR onTimer() { if (currentYear > 0 && !skipTimerLogic) updateTimeDisplay(); // Increment the time by 1s. - epochTime += 1; + epochTimeCurrent += 1; // Keep track of how many times we have got here. timerCount += 1; @@ -89,6 +99,7 @@ void IRAM_ATTR onTimer() { void setup() { Serial.begin(9600); + Serial.println("Initialising."); pinMode(SET_STOP_BUTTON, INPUT); pinMode(HOUR_BUTTON, INPUT); @@ -101,12 +112,14 @@ void setup() { pixels.show(); //Init EEPROM + Serial.println("Retrieving the alarm time from EEPROM."); EEPROM.begin(EEPROM_SIZE); alarmMinutes = EEPROM.read(0); alarmHours = EEPROM.read(1); WiFiManager manager; + Serial.println("Connecting to WiFi."); manager.setConnectTimeout(10); manager.setConnectRetries(5); if (manager.getWiFiIsSaved()) { @@ -120,51 +133,65 @@ void setup() { } } } + + Serial.println("Successfully connected to WiFi."); delay(3000); timeClient.begin(); - if (enableAudio) { - FPSerial.begin(9600, SERIAL_8N1, RXD2, TXD2); - Serial.println(); - Serial.println(F("DFRobot DFPlayer Mini Demo")); - Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); - - if (!myDFPlayer.begin(FPSerial, /*isACK = */ true, /*doReset = */ true)) { //Use serial to communicate with mp3. - Serial.println(F("Unable to begin:")); - Serial.println(F("1.Please recheck the connection!")); - Serial.println(F("2.Please insert the SD card!")); - while (true) { - delay(0); // Code to compatible with ESP8266 watch dog. - } - } - - Serial.println(F("DFPlayer Mini online.")); - - myDFPlayer.volume(notification_volume); - myDFPlayer.play(10); //Play the first mp3 - delay(100); - Play_finished = 0; - } + // if (enableAudio) { + // FPSerial.begin(9600, SERIAL_8N1, RXD2, TXD2); + // Serial.println(); + // Serial.println(F("DFRobot DFPlayer Mini Demo")); + // Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); + + // if (!myDFPlayer.begin(FPSerial, /*isACK = */ true, /*doReset = */ true)) { //Use serial to communicate with mp3. + // Serial.println(F("Unable to begin:")); + // Serial.println(F("1.Please recheck the connection!")); + // Serial.println(F("2.Please insert the SD card!")); + // while (true) { + // delay(0); // Code to compatible with ESP8266 watch dog. + // } + // } + + // Serial.println(F("DFPlayer Mini online.")); + + // myDFPlayer.volume(notification_volume); + // myDFPlayer.play(10); //Play the first mp3 + // delay(100); + // Play_finished = 0; + // } // Get the current time, retry if unsuccessful + Serial.println("Getting the time from the NTP server."); for (int i = 0; i <= 20; i++) { timeClient.update(); - epochTime = timeClient.getEpochTime(); - if (epochTime > 0) break; - Serial.print("Could not retrieve time from NTP server..."); + epochTimeNTP = timeClient.getEpochTime(); + if (epochTimeNTP > 0) { + epochTimeCurrent = epochTimeNTP; + break; + } + Serial.print("Could not retrieve time from NTP server."); delay(50); } - if (epochTime == 0) ESP.restart(); // Reset and try again + if (epochTimeCurrent == 0) { + Serial.println("Could not retrieve time from NTP server, restarting..."); + ESP.restart(); // Reset and try again + } else { + Serial.print("Epoch time from NTP server: "); + Serial.println(epochTimeNTP); + } // Define a timer. The timer will be used to toggle the time // colon on/off every 1s. + Serial.println("Defining the 1000ms timer."); uint64_t alarmLimit = 1000000; myTimer = timerBegin(1000000); // timer frequency timerAttachInterrupt(myTimer, &onTimer); timerAlarm(myTimer, alarmLimit, true, 0); + Serial.println("Flux capacitor startup flash."); for (int v = 0; v < 30; v++) { // clear the pixels, equivalent to pixels.clear(); for (int i = 0; i < 24; i++) { @@ -187,10 +214,10 @@ void setup() { pixels.show(); } - Serial.println("\n Starting"); - if (enableAudio) { - myDFPlayer.play(14); - } + Serial.println("Starting main loop."); + // if (enableAudio) { + // myDFPlayer.play(14); + // } } void loop() { @@ -210,23 +237,25 @@ void loop() { skipTimerLogic = false; } - if (enableAudio) { - if (myDFPlayer.available()) { - printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. - } - } + // if (enableAudio) { + // if (myDFPlayer.available()) { + // printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. + // } + // } if (digitalRead(SET_STOP_BUTTON)) { + Serial.println("Entering alarm setup."); Setup_alarm(); - if (enableAudio) myDFPlayer.stop(); + // if (enableAudio) myDFPlayer.stop(); digitalWrite(26, LOW); } if (alarmHours == currentHours && flag_alarm == 0 && alarm_on_off == 1) { if (alarmMinutes == currentMinutes) { + Serial.println("Triggering the alarm."); flag_alarm = 1; alarm(); - Serial.println("Sortie alarm"); + Serial.println("Exit alarm"); } } @@ -236,39 +265,51 @@ void loop() { if (alarmMinutes != currentMinutes) { flag_alarm = 0; } - if (digitalRead(MINUTE_BUTTON) && digitalRead(HOUR_BUTTON)) // Push Min and Hour simultanetly to switch ON or OFF the alarm + if (digitalRead(MINUTE_BUTTON) && digitalRead(HOUR_BUTTON)) // Push Min and Hour simultaneouslyly to switch ON or OFF the alarm { if (alarm_on_off == 1) { + Serial.println("Setting the alarm off."); alarm_on_off = 0; - } else alarm_on_off = 1; + snoozed = 0; + } else { + Serial.println("Setting the alarm on."); + alarm_on_off = 1; + } - red1.showNumberDecEx(00, 0b01000000, true, 2, 0); - red1.showNumberDecEx(alarm_on_off, 0b01000000, true, 2, 2); + red1.showNumberDecEx(00, 0b00000000, true, 2, 0); + red1.showNumberDecEx(alarm_on_off, 0b00000000, true, 2, 2); delay(1000); if (digitalRead(MINUTE_BUTTON)) // Push Min and Hour simultanetly to switch ON or OFF the alarm { - easter_egg = 0; pixels.setBrightness(0); - if (enableAudio) myDFPlayer.play(13); + // if (enableAudio) myDFPlayer.play(13); pixels.show(); } } + + // Check whether we are snoozed and the snooze duration has been passed + if (snoozed && epochTimeCurrent - epochTimeSnoozed >= snoozeLengthMinutes * 60 && alarm_on_off == 1) { + Serial.println("Snooze duration expired, triggering alarm."); + alarm(); + Serial.println("Exit alarm"); + } } void maybeUpdateClock() { if (timerCount > 0 && colonVisible && !digitalRead(MINUTE_BUTTON) && !digitalRead(HOUR_BUTTON) && !digitalRead(SET_STOP_BUTTON)) { - Serial.println('Entered time display update...'); + //Serial.println("Entered time display update..."); previousMinutes = currentMinutes; - setTime(epochTime); + setTime(epochTimeCurrent); currentYear = year(); currentMonth = month(); currentDay = day(); currentHours = hour(); currentMinutes = minute(); if (currentYear >= 2025 && previousMinutes != currentMinutes) { + Serial.println("Updating time display."); updateTimeDisplay(); red1.setBrightness(clockBrightness); checkDSTAndSetOffset(); @@ -304,6 +345,10 @@ void checkDSTAndSetOffset() { void alarm() { skipTimerLogic = true; + snoozed = false; + + Serial.println("Count to 88."); + // count 0 to 88 m/h for (int u = 0; u < 89; u++) { delay(12 - (u / 8)); @@ -326,87 +371,76 @@ void alarm() { if (digitalRead(SET_STOP_BUTTON)) { u = 89; } - // if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) // Snooze if you push MIN or HOUR button - // { - // //Snooze(); - // // Implement snooze here. - // } + if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) // Snooze if you push MIN or HOUR button + { + Serial.println("Snoozing."); + snoozed = true; + epochTimeSnoozed = epochTimeCurrent; + break; + } } + + if (snoozed) break; + h = h + 1; } - //skipTimerLogic = false; - if (enableAudio) myDFPlayer.play(random(1, 9)); //Playing the alarm sound - delay(1500); - - colonVisible = true; - updateTimeDisplay(); + if (!snoozed) { + //skipTimerLogic = false; + // if (enableAudio) myDFPlayer.play(random(1, 9)); //Playing the alarm sound + delay(1500); + + colonVisible = true; + updateTimeDisplay(); + + while (!digitalRead(SET_STOP_BUTTON) && !snoozed) { + h = 1; + maybeUpdateClock(); + digitalWrite(26, HIGH); + + // If you're not wake-up at the first song, it plays the next one + // if (enableAudio) { + // if (myDFPlayer.available()) { + // printDetail(myDFPlayer.readType(), myDFPlayer.read()); + // if (Play_finished == 1) { + // Play_finished = 0; + // Serial.println("Next song"); + // myDFPlayer.play(random(1, 9)); //Playing the alarm sound + // } + // } + // } - while (!digitalRead(SET_STOP_BUTTON)) { - h = 1; - maybeUpdateClock(); - // Serial.println("dans la boucle"); - digitalWrite(26, HIGH); + //That's bzzzz the Neopixel + for (int i = 0; i < 24; i++) { + pixels.setLedColorData(i, 0, 0, 0); + } + for (int i = 0; i < 8; i++) { + pixels.setLedColorData(i, 255, 200, 105); + pixels.setLedColorData((i + 8), 255, 200, 105); + pixels.setLedColorData((i + 16), 255, 200, 105); + delay(5); + pixels.setBrightness(110); + pixels.show(); + //Serial.println("Boucle neopixel"); + } - // If you're not wake-up at the first song, it plays the next one - if (enableAudio) { - if (myDFPlayer.available()) { - printDetail(myDFPlayer.readType(), myDFPlayer.read()); - if (Play_finished == 1) { - Play_finished = 0; - Serial.println("Next song"); - myDFPlayer.play(random(1, 9)); //Playing the alarm sound - } + if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) // Snooze if you push MIN or HOUR button + { + Serial.println("Snoozing."); + snoozed = true; + epochTimeSnoozed = epochTimeCurrent; } } + } - //That's bzzzz the Neopixel - for (int i = 0; i < 24; i++) { - pixels.setLedColorData(i, 0, 0, 0); - } - for (int i = 0; i < 8; i++) { - pixels.setLedColorData(i, 255, 200, 105); - pixels.setLedColorData((i + 8), 255, 200, 105); - pixels.setLedColorData((i + 16), 255, 200, 105); - delay(5); - pixels.setBrightness(110); - pixels.show(); - //Serial.println("Boucle neopixel"); - } + // if (enableAudio) myDFPlayer.stop(); + // Play_finished = 0; - // if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON) ) // Snooze if you push MIN or HOUR button - // { - // //Snooze(); - // // Implement snooze here. - // } - } pixels.setBrightness(0); pixels.show(); skipTimerLogic = false; } - -// // Snooze if you'd like to sleep few minutes more -// // You can set the snooze time with the "snooze" variable at the beginning of the code -// void Snooze() { -// skipTimerLogic = false; - -// if (enableAudio) myDFPlayer.stop(); -// pixels.setBrightness(5); -// pixels.show(); - -// for (int i = 0; i < (snooze * 600); i++) { -// waitMilliseconds(68); -// updateTimeDisplay(); -// Serial.println("snooze"); -// Serial.println(i); -// if (digitalRead(SET_STOP_BUTTON)) { i = snooze * 600; } -// } - -// alarm(); -// Play_finished = 0; -// } - void Setup_alarm() { while (digitalRead(SET_STOP_BUTTON)) { @@ -426,6 +460,8 @@ void Setup_alarm() { if (alarmMinutes > 59) { alarmMinutes = 0; } if (alarmHours > 23) { alarmHours = 0; } } + + Serial.println("Saving the alarm time to EEPROM."); EEPROM.write(0, alarmMinutes); EEPROM.write(1, alarmHours); EEPROM.commit(); @@ -493,16 +529,6 @@ void printDetail(uint8_t type, int value) { } } -void waitMilliseconds(uint16_t msWait) { - uint32_t start = millis(); - - while ((millis() - start) < msWait) { - // calling mp3.loop() periodically allows for notifications - // to be handled without interrupts - delay(1); - } -} - bool isDST(int month, int day, int year) { int lastSundayInMarch = getLastSundayOfMonth(3, year); int lastSundayInOctober = getLastSundayOfMonth(10, year); From b5c6c834ec4488b38be64d1ca301795a564e20a0 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 13:50:19 +0000 Subject: [PATCH 05/17] Update FLUX_CAPACITOR.ino - v5 Activate sounds. --- FLUX_CAPACITOR.ino | 190 ++++++++++++++++++++++++++++++++------------- 1 file changed, 137 insertions(+), 53 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index d790f03..fa44c4b 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -6,27 +6,31 @@ // v4 - Rework and re-implement snooze function. // Commented out all audio-related code for troubleshooting. // Added more serial output debugging statements. +// v5 - Moved NTP server time retrieval to a function. +// Implement regular NTP server syncs to ensure that the clock doesn't drift too far. +// Only write the alarm time to EEPROM if necessary to avoid wear and tear. +// Only update the alarm on/off LED if there is a state change. +// Save the alarm enabled status in the EEPROM and retrieve at startup. +// v5 - Activate sounds. // -// -// -// TODO: Make use of EEPROM to record alarm on/off state and snooze status in case of accidental restart. -// TODO: Regular NTP server syncs to ensure that the clock doesn't drift too far. +// TODO: Make use of EEPROM to snooze status in case of accidental restart. +// TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. #include "WiFiManager.h" // https://github.com/tzapu/WiFiManager #include "NTPClient.h" // https://github.com/arduino-libraries/NTPClient #include "TM1637Display.h" // https://github.com/avishorp/TM1637 #include "DFRobotDFPlayerMini.h" // https://github.com/DFRobot/DFRobotDFPlayerMini -#include "ESP32_WS2812_Lib.h" +#include "ESP32_WS2812_Lib.h" // https://github.com/Zhentao-Lin/ESP32_WS2812_Lib #include #include //https://playground.arduino.cc/Code/Time/ - bool enableAudio = false; //========================USEFUL VARIABLES============================= -uint16_t notification_volume = 25; //Set volume value. From 0 to 30 -int clockBrightness = 1; // Set displays brightness 0 to 7; -int snoozeLengthMinutes = 1; // Snooze time in minute +uint16_t notification_volume = 25; //Set volume value. From 0 to 30 +int clockBrightness = 1; // Set displays brightness 0 to 7 +int snoozeLengthMinutes = 1; // Snooze time in minute +int refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time syncs from the NTP server. //===================================================================== #define DISPLAY_CLK 21 @@ -39,30 +43,30 @@ int snoozeLengthMinutes = 1; // Snooze time in minute #define LEDS_COUNT 24 #define EEPROM_SIZE 12 #define CHANNEL 0 - #define UTC_OFFSET 1 - +#define ALARM_ON_OFF_LED 26 const byte RXD2 = 16; // Connects to module's TX => 16 const byte TXD2 = 17; // Connects to module's RX => 17 -//DFRobotDFPlayerMini myDFPlayer; +const long utcOffsetInSeconds = 0; // GMT + +DFRobotDFPlayerMini myDFPlayer; + void printDetail(uint8_t type, int value); -float counter = 0; int alarmHours = 0; int alarmMinutes = 0; int flag_alarm = 0; int alarm_on_off = 1; int h = 0; -const long utcOffsetInSeconds = 0; // GMT -int Play_finished = 0; -bool res; +int audioFinished = 0; int currentMinutes = 0, currentHours = 0, currentYear = 0, currentMonth = 0, currentDay = 0, previousMinutes = 0; long epochTimeCurrent = 0; long epochTimeSnoozed = 0; long epochTimeNTP = 0; +long epochTimeLocalLastRefreshFromNTP = 0; //Tracks the local calculated time that the last NTP refresh was attempted. bool snoozed = false; unsigned long lastColonToggleTime = 0; bool colonVisible = true; // Start with the colon visible @@ -104,22 +108,53 @@ void setup() { pinMode(SET_STOP_BUTTON, INPUT); pinMode(HOUR_BUTTON, INPUT); pinMode(MINUTE_BUTTON, INPUT); - pinMode(26, OUTPUT); + pinMode(ALARM_ON_OFF_LED, OUTPUT); pinMode(LEDS_PIN, OUTPUT); pixels.begin(); pixels.setBrightness(0); pixels.show(); + // Switch off the time display. + Serial.println("Switching off the time display."); + red1.setBrightness(0, false); + red1.showNumberDecEx(0, 0b00000000, true); + //Init EEPROM - Serial.println("Retrieving the alarm time from EEPROM."); + Serial.println("Retrieving the alarm time and status from EEPROM."); EEPROM.begin(EEPROM_SIZE); - alarmMinutes = EEPROM.read(0); - alarmHours = EEPROM.read(1); + alarmMinutes = EEPROM.read(0); + alarmHours = EEPROM.read(1); + alarm_on_off = EEPROM.read(2); + + Serial.print("Alarm time: "); + Serial.print(alarmHours); + Serial.print(":"); + Serial.println(alarmMinutes); + + Serial.print("Alarm enabled status: "); + Serial.println(alarm_on_off); + + // Check for out of range values and reset to 00:00 if found. + if (alarmMinutes > 59 || alarmHours > 23) { + Serial.println("Out of range value found for alarm time, resetting to 00:00."); + EEPROM.write(0, 0); + EEPROM.write(1, 0); + EEPROM.commit(); + alarmMinutes = 0; + alarmHours = 0; + } - WiFiManager manager; + if (alarm_on_off > 1) { + Serial.println("Out of range value found for alarm on/off, resetting to ON as a precaution."); + EEPROM.write(2, 1); + EEPROM.commit(); + alarm_on_off = 1; + } + Serial.println("Connecting to WiFi."); + WiFiManager manager; manager.setConnectTimeout(10); manager.setConnectRetries(5); if (manager.getWiFiIsSaved()) { @@ -133,7 +168,6 @@ void setup() { } } } - Serial.println("Successfully connected to WiFi."); delay(3000); @@ -159,29 +193,21 @@ void setup() { // myDFPlayer.volume(notification_volume); // myDFPlayer.play(10); //Play the first mp3 // delay(100); - // Play_finished = 0; + // audioFinished = 0; // } - // Get the current time, retry if unsuccessful - Serial.println("Getting the time from the NTP server."); - for (int i = 0; i <= 20; i++) { - timeClient.update(); - epochTimeNTP = timeClient.getEpochTime(); - if (epochTimeNTP > 0) { - epochTimeCurrent = epochTimeNTP; - break; - } - Serial.print("Could not retrieve time from NTP server."); - delay(50); - } - if (epochTimeCurrent == 0) { + Serial.println("Getting the time from the NTP server."); + epochTimeNTP = getEpochTimeFromNTPServer(); + if (epochTimeNTP == 0) { Serial.println("Could not retrieve time from NTP server, restarting..."); ESP.restart(); // Reset and try again } else { Serial.print("Epoch time from NTP server: "); Serial.println(epochTimeNTP); } + epochTimeLocalLastRefreshFromNTP = epochTimeNTP; + epochTimeCurrent = epochTimeNTP; // Define a timer. The timer will be used to toggle the time // colon on/off every 1s. @@ -214,6 +240,10 @@ void setup() { pixels.show(); } + if (alarm_on_off == 1) { + digitalWrite(ALARM_ON_OFF_LED, HIGH); + } else digitalWrite(ALARM_ON_OFF_LED, LOW); + Serial.println("Starting main loop."); // if (enableAudio) { // myDFPlayer.play(14); @@ -225,9 +255,31 @@ void loop() { //pixels.clear(); //show_hour(); h = 1; - Play_finished = 0; + audioFinished = 0; pixels.setBrightness(0); + // Maybe update the time from the NTP server. + // TODO: Check seconds so as not to set the time backwards if the local timekeeper has advanced too much. + if (epochTimeCurrent - epochTimeLocalLastRefreshFromNTP > refreshTimeFromNTPIntervalMinutes * 60) { + Serial.println("Getting the time from the NTP server."); + epochTimeNTP = 0; + epochTimeNTP = getEpochTimeFromNTPServer(); + if (epochTimeNTP > 0) { + epochTimeCurrent = epochTimeNTP; + } + + if (epochTimeNTP == 0) { + Serial.println("Could not retrieve time from NTP server, try again next time."); + } else { + Serial.print("Epoch time from NTP server: "); + Serial.println(epochTimeNTP); + } + + // Store the current time so that we don't get stuck in a loop + // if the NTP server was unavailable. + epochTimeLocalLastRefreshFromNTP = epochTimeCurrent; + } + // Check to see if the time displays need to be updated. maybeUpdateClock(); @@ -247,7 +299,7 @@ void loop() { Serial.println("Entering alarm setup."); Setup_alarm(); // if (enableAudio) myDFPlayer.stop(); - digitalWrite(26, LOW); + Serial.println("Exit alarm setup."); } if (alarmHours == currentHours && flag_alarm == 0 && alarm_on_off == 1) { @@ -259,9 +311,9 @@ void loop() { } } - if (alarm_on_off == 1) { - digitalWrite(26, HIGH); - } else digitalWrite(26, LOW); + // if (alarm_on_off == 1) { + // digitalWrite(ALARM_ON_OFF_LED, HIGH); + // } else digitalWrite(ALARM_ON_OFF_LED, LOW); if (alarmMinutes != currentMinutes) { flag_alarm = 0; } @@ -270,17 +322,23 @@ void loop() { if (alarm_on_off == 1) { Serial.println("Setting the alarm off."); alarm_on_off = 0; + digitalWrite(ALARM_ON_OFF_LED, LOW); snoozed = 0; } else { Serial.println("Setting the alarm on."); alarm_on_off = 1; + digitalWrite(ALARM_ON_OFF_LED, HIGH); } + Serial.println("Writing new alarm enabled status to EEPROM."); + EEPROM.write(2, alarm_on_off); + EEPROM.commit(); + red1.showNumberDecEx(00, 0b00000000, true, 2, 0); red1.showNumberDecEx(alarm_on_off, 0b00000000, true, 2, 2); delay(1000); - if (digitalRead(MINUTE_BUTTON)) // Push Min and Hour simultanetly to switch ON or OFF the alarm + if (digitalRead(MINUTE_BUTTON)) // Push Min and Hour simultaneously to switch ON or OFF the alarm { pixels.setBrightness(0); // if (enableAudio) myDFPlayer.play(13); @@ -371,7 +429,9 @@ void alarm() { if (digitalRead(SET_STOP_BUTTON)) { u = 89; } - if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) // Snooze if you push MIN or HOUR button + // Check for snooze. If requested then record the snooze request time (so that we can trigger the alarm + // again at the end of the snooze period), and then exit the loop. + if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) { Serial.println("Snoozing."); snoozed = true; @@ -379,12 +439,14 @@ void alarm() { break; } } - + + // If snooze has been requested then exit the loop. if (snoozed) break; h = h + 1; } + // If we haven't yet snoozed the continue. if (!snoozed) { //skipTimerLogic = false; // if (enableAudio) myDFPlayer.play(random(1, 9)); //Playing the alarm sound @@ -393,17 +455,18 @@ void alarm() { colonVisible = true; updateTimeDisplay(); + while (!digitalRead(SET_STOP_BUTTON) && !snoozed) { h = 1; maybeUpdateClock(); - digitalWrite(26, HIGH); + //digitalWrite(ALARM_ON_OFF_LED, HIGH); // If you're not wake-up at the first song, it plays the next one // if (enableAudio) { // if (myDFPlayer.available()) { // printDetail(myDFPlayer.readType(), myDFPlayer.read()); - // if (Play_finished == 1) { - // Play_finished = 0; + // if (audioFinished == 1) { + // audioFinished = 0; // Serial.println("Next song"); // myDFPlayer.play(random(1, 9)); //Playing the alarm sound // } @@ -424,6 +487,8 @@ void alarm() { //Serial.println("Boucle neopixel"); } + // If snooze has been requested then set variables to exit the WHILE loop. + // Record the snooze request time so that we can trigger the alarm again at the end of the snooze period. if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) // Snooze if you push MIN or HOUR button { Serial.println("Snoozing."); @@ -434,7 +499,7 @@ void alarm() { } // if (enableAudio) myDFPlayer.stop(); - // Play_finished = 0; + // audioFinished = 0; pixels.setBrightness(0); pixels.show(); @@ -442,6 +507,8 @@ void alarm() { } void Setup_alarm() { + int alarmHoursOriginal = alarmHours; + int alarmMinutesOriginal = alarmMinutes; while (digitalRead(SET_STOP_BUTTON)) { if (digitalRead(MINUTE_BUTTON)) { @@ -461,10 +528,13 @@ void Setup_alarm() { if (alarmHours > 23) { alarmHours = 0; } } - Serial.println("Saving the alarm time to EEPROM."); - EEPROM.write(0, alarmMinutes); - EEPROM.write(1, alarmHours); - EEPROM.commit(); + // Only write to EEPROM if necessary. + if (alarmHoursOriginal != alarmHours || alarmMinutesOriginal != alarmMinutes) { + Serial.println("Saving the alarm time to EEPROM."); + EEPROM.write(0, alarmMinutes); + EEPROM.write(1, alarmHours); + EEPROM.commit(); + } } void printDetail(uint8_t type, int value) { @@ -494,7 +564,7 @@ void printDetail(uint8_t type, int value) { Serial.print(F("Number:")); Serial.print(value); Serial.println(F(" Play Finished!")); - Play_finished = 1; + audioFinished = 1; break; case DFPlayerError: Serial.print(F("DFPlayerError:")); @@ -578,3 +648,17 @@ int getLastSundayOfMonth(int month, int year) { int lastSunday = lastDay - weekday; return getDayOfYear(month, lastSunday, year); // Convert to day of the year } + +long getEpochTimeFromNTPServer() { + long epochTime = 0; + // Get the current time, retry if unsuccessful + for (int i = 0; i <= 20; i++) { + timeClient.update(); + epochTime = timeClient.getEpochTime(); + if (epochTime > 0) { + break; + } + delay(50); + } + return epochTime; +} From 499bc2316b391f3dd58aea20a2ff2c5f033774b4 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 13:51:43 +0000 Subject: [PATCH 06/17] Update FLUX_CAPACITOR.ino - v6 Modified to get the sounds to work. Use the playMp3Folder function to guarantee which file plays when requested by file number, e.g. not by the order added to the SD card. --- FLUX_CAPACITOR.ino | 106 +++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index fa44c4b..36ea5a2 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -11,7 +11,9 @@ // Only write the alarm time to EEPROM if necessary to avoid wear and tear. // Only update the alarm on/off LED if there is a state change. // Save the alarm enabled status in the EEPROM and retrieve at startup. -// v5 - Activate sounds. +// Activate sounds. +// v6 - Modified to get the sounds to work. +// Use the playMp3Folder function to guarantee which file plays when requested by file number, e.g. not by the order added to the SD card. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. @@ -24,10 +26,10 @@ #include #include //https://playground.arduino.cc/Code/Time/ -bool enableAudio = false; +bool enableAudio = true; //========================USEFUL VARIABLES============================= -uint16_t notification_volume = 25; //Set volume value. From 0 to 30 +uint16_t notification_volume = 10; //Set volume value. From 0 to 30 int clockBrightness = 1; // Set displays brightness 0 to 7 int snoozeLengthMinutes = 1; // Snooze time in minute int refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time syncs from the NTP server. @@ -38,7 +40,7 @@ int refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time s #define SET_STOP_BUTTON 34 #define HOUR_BUTTON 33 #define MINUTE_BUTTON 32 -#define FPSerial Serial1 +//#define FPSerial Serial1 #define LEDS_PIN 4 #define LEDS_COUNT 24 #define EEPROM_SIZE 12 @@ -46,13 +48,13 @@ int refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time s #define UTC_OFFSET 1 #define ALARM_ON_OFF_LED 26 +HardwareSerial FPSerial(1); const byte RXD2 = 16; // Connects to module's TX => 16 const byte TXD2 = 17; // Connects to module's RX => 17 const long utcOffsetInSeconds = 0; // GMT DFRobotDFPlayerMini myDFPlayer; - void printDetail(uint8_t type, int value); int alarmHours = 0; @@ -173,28 +175,30 @@ void setup() { timeClient.begin(); - // if (enableAudio) { - // FPSerial.begin(9600, SERIAL_8N1, RXD2, TXD2); - // Serial.println(); - // Serial.println(F("DFRobot DFPlayer Mini Demo")); - // Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); + // Initialise the audio player. + if (enableAudio) { + FPSerial.begin(9600, SERIAL_8N1, RXD2, TXD2); + Serial.println(); + Serial.println(F("DFRobot DFPlayer Mini Demo")); + Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); + + if (!myDFPlayer.begin(FPSerial, /*isACK = */ true, /*doReset = */ true)) { //Use serial to communicate with mp3. + Serial.println(F("Unable to begin:")); + Serial.println(F("1.Please recheck the connection!")); + Serial.println(F("2.Please insert the SD card!")); + while (true) { + delay(0); // Code to compatible with ESP8266 watch dog. + } + } - // if (!myDFPlayer.begin(FPSerial, /*isACK = */ true, /*doReset = */ true)) { //Use serial to communicate with mp3. - // Serial.println(F("Unable to begin:")); - // Serial.println(F("1.Please recheck the connection!")); - // Serial.println(F("2.Please insert the SD card!")); - // while (true) { - // delay(0); // Code to compatible with ESP8266 watch dog. - // } - // } + Serial.println(F("DFPlayer Mini online.")); - // Serial.println(F("DFPlayer Mini online.")); + myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms - // myDFPlayer.volume(notification_volume); - // myDFPlayer.play(10); //Play the first mp3 - // delay(100); - // audioFinished = 0; - // } + myDFPlayer.volume(notification_volume); + myDFPlayer.EQ(DFPLAYER_EQ_NORMAL); + myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); + } Serial.println("Getting the time from the NTP server."); @@ -217,6 +221,12 @@ void setup() { timerAttachInterrupt(myTimer, &onTimer); timerAlarm(myTimer, alarmLimit, true, 0); + + //Play the first mp3 + myDFPlayer.playMp3Folder(10); + delay(100); + audioFinished = 0; + Serial.println("Flux capacitor startup flash."); for (int v = 0; v < 30; v++) { // clear the pixels, equivalent to pixels.clear(); @@ -245,9 +255,9 @@ void setup() { } else digitalWrite(ALARM_ON_OFF_LED, LOW); Serial.println("Starting main loop."); - // if (enableAudio) { - // myDFPlayer.play(14); - // } + if (enableAudio) { + myDFPlayer.playMp3Folder(14); + } } void loop() { @@ -289,16 +299,16 @@ void loop() { skipTimerLogic = false; } - // if (enableAudio) { - // if (myDFPlayer.available()) { - // printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. - // } - // } + if (enableAudio) { + if (myDFPlayer.available()) { + printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. + } + } if (digitalRead(SET_STOP_BUTTON)) { Serial.println("Entering alarm setup."); Setup_alarm(); - // if (enableAudio) myDFPlayer.stop(); + if (enableAudio) myDFPlayer.stop(); Serial.println("Exit alarm setup."); } @@ -341,7 +351,7 @@ void loop() { if (digitalRead(MINUTE_BUTTON)) // Push Min and Hour simultaneously to switch ON or OFF the alarm { pixels.setBrightness(0); - // if (enableAudio) myDFPlayer.play(13); + if (enableAudio) myDFPlayer.playMp3Folder(13); pixels.show(); } } @@ -449,7 +459,7 @@ void alarm() { // If we haven't yet snoozed the continue. if (!snoozed) { //skipTimerLogic = false; - // if (enableAudio) myDFPlayer.play(random(1, 9)); //Playing the alarm sound + if (enableAudio) myDFPlayer.playMp3Folder(random(1, 9)); //Playing the alarm sound delay(1500); colonVisible = true; @@ -461,17 +471,17 @@ void alarm() { maybeUpdateClock(); //digitalWrite(ALARM_ON_OFF_LED, HIGH); - // If you're not wake-up at the first song, it plays the next one - // if (enableAudio) { - // if (myDFPlayer.available()) { - // printDetail(myDFPlayer.readType(), myDFPlayer.read()); - // if (audioFinished == 1) { - // audioFinished = 0; - // Serial.println("Next song"); - // myDFPlayer.play(random(1, 9)); //Playing the alarm sound - // } - // } - // } + //If you're not wake-up at the first song, it plays the next one + if (enableAudio) { + if (myDFPlayer.available()) { + printDetail(myDFPlayer.readType(), myDFPlayer.read()); + if (audioFinished == 1) { + audioFinished = 0; + Serial.println("Next song"); + myDFPlayer.playMp3Folder(random(1, 9)); //Playing the alarm sound + } + } + } //That's bzzzz the Neopixel for (int i = 0; i < 24; i++) { @@ -498,8 +508,8 @@ void alarm() { } } - // if (enableAudio) myDFPlayer.stop(); - // audioFinished = 0; + if (enableAudio) myDFPlayer.stop(); + audioFinished = 0; pixels.setBrightness(0); pixels.show(); From dd1b71207bfd4889368e23ff1f5a68de3a922395 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 13:53:19 +0000 Subject: [PATCH 07/17] Update FLUX_CAPACITOR.ino - v7 --- FLUX_CAPACITOR.ino | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index 36ea5a2..f12a7cf 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -11,9 +11,10 @@ // Only write the alarm time to EEPROM if necessary to avoid wear and tear. // Only update the alarm on/off LED if there is a state change. // Save the alarm enabled status in the EEPROM and retrieve at startup. -// Activate sounds. -// v6 - Modified to get the sounds to work. +// v6 - Activate sounds. +// Modified to get the sounds to work. // Use the playMp3Folder function to guarantee which file plays when requested by file number, e.g. not by the order added to the SD card. +// v7 - Add additional WiFi settings to try to solve occasional connection failures. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. @@ -156,6 +157,8 @@ void setup() { Serial.println("Connecting to WiFi."); + WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); + WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); WiFiManager manager; manager.setConnectTimeout(10); manager.setConnectRetries(5); From f8a0c63e78aa16e34b8651d9087b997eb981fd1d Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 13:57:12 +0000 Subject: [PATCH 08/17] Update FLUX_CAPACITOR.ino - v8 Reduced volume. Changed EQ setting to add more bass. Increased snooze length. --- FLUX_CAPACITOR.ino | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index f12a7cf..5c94ebc 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -15,9 +15,13 @@ // Modified to get the sounds to work. // Use the playMp3Folder function to guarantee which file plays when requested by file number, e.g. not by the order added to the SD card. // v7 - Add additional WiFi settings to try to solve occasional connection failures. +// v8 - Reduced volume. +// Changed EQ setting to add more bass. +// Increased snooze length. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. +// TODO: Make the volume configurable by using the hour/minute buttons. #include "WiFiManager.h" // https://github.com/tzapu/WiFiManager #include "NTPClient.h" // https://github.com/arduino-libraries/NTPClient @@ -30,9 +34,9 @@ bool enableAudio = true; //========================USEFUL VARIABLES============================= -uint16_t notification_volume = 10; //Set volume value. From 0 to 30 +uint16_t notification_volume = 12; //Set volume value. From 0 to 30 int clockBrightness = 1; // Set displays brightness 0 to 7 -int snoozeLengthMinutes = 1; // Snooze time in minute +int snoozeLengthMinutes = 5; // Snooze time in minute int refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time syncs from the NTP server. //===================================================================== @@ -199,7 +203,7 @@ void setup() { myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms myDFPlayer.volume(notification_volume); - myDFPlayer.EQ(DFPLAYER_EQ_NORMAL); + myDFPlayer.EQ(DFPLAYER_EQ_BASS); myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); } From 5a2f052e96165a2f73e41f7adfc30a3d7fd88a5f Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 14:59:17 +0000 Subject: [PATCH 09/17] Update FLUX_CAPACITOR.ino - v9 Amended actions taken when various combinations of buttons are pressed. --- FLUX_CAPACITOR.ino | 148 ++++++++++++++++++++++++++++++++------------- 1 file changed, 107 insertions(+), 41 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index 5c94ebc..e970a48 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -18,10 +18,13 @@ // v8 - Reduced volume. // Changed EQ setting to add more bass. // Increased snooze length. +// V9 - Amended actions taken when combinations of buttons are pressed. +// 1. Hour pressed and held, then set/stop button pressed toggles alarm status. +// 2. Hour pressed and held, then minute button pressed increments volume level, loops around to level 1. +// 3. Minute pressed and held, then hour buttom pressed increments display brightness, loops around to level 1. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. -// TODO: Make the volume configurable by using the hour/minute buttons. #include "WiFiManager.h" // https://github.com/tzapu/WiFiManager #include "NTPClient.h" // https://github.com/arduino-libraries/NTPClient @@ -34,7 +37,6 @@ bool enableAudio = true; //========================USEFUL VARIABLES============================= -uint16_t notification_volume = 12; //Set volume value. From 0 to 30 int clockBrightness = 1; // Set displays brightness 0 to 7 int snoozeLengthMinutes = 5; // Snooze time in minute int refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time syncs from the NTP server. @@ -53,7 +55,7 @@ int refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time s #define UTC_OFFSET 1 #define ALARM_ON_OFF_LED 26 -HardwareSerial FPSerial(1); +HardwareSerial FPSerial(1); const byte RXD2 = 16; // Connects to module's TX => 16 const byte TXD2 = 17; // Connects to module's RX => 17 @@ -67,6 +69,8 @@ int alarmMinutes = 0; int flag_alarm = 0; int alarm_on_off = 1; int h = 0; + +int notification_volume = 5; int audioFinished = 0; int currentMinutes = 0, currentHours = 0, currentYear = 0, currentMonth = 0, currentDay = 0, previousMinutes = 0; @@ -130,18 +134,22 @@ void setup() { //Init EEPROM Serial.println("Retrieving the alarm time and status from EEPROM."); EEPROM.begin(EEPROM_SIZE); - alarmMinutes = EEPROM.read(0); - alarmHours = EEPROM.read(1); - alarm_on_off = EEPROM.read(2); + alarmMinutes = EEPROM.read(0); + alarmHours = EEPROM.read(1); + alarm_on_off = EEPROM.read(2); + notification_volume = EEPROM.read(3); Serial.print("Alarm time: "); Serial.print(alarmHours); Serial.print(":"); Serial.println(alarmMinutes); - + Serial.print("Alarm enabled status: "); Serial.println(alarm_on_off); + Serial.print("Motification volume: "); + Serial.println(notification_volume); + // Check for out of range values and reset to 00:00 if found. if (alarmMinutes > 59 || alarmHours > 23) { Serial.println("Out of range value found for alarm time, resetting to 00:00."); @@ -150,7 +158,7 @@ void setup() { EEPROM.commit(); alarmMinutes = 0; alarmHours = 0; - } + } if (alarm_on_off > 1) { Serial.println("Out of range value found for alarm on/off, resetting to ON as a precaution."); @@ -158,7 +166,19 @@ void setup() { EEPROM.commit(); alarm_on_off = 1; } - + + if (notification_volume < 1 || notification_volume > 15) { + Serial.println("Out of range value found for notification volume, resetting to level 5 as a precaution."); + EEPROM.write(3, 5); + EEPROM.commit(); + notification_volume = 5; + } + + clockBrightness = EEPROM.read(4); + + // Try to retrieve the saved clockBrightness, default to 3 if out of range. + if (clockBrightness > 4) clockBrightness = 3; + if (clockBrightness < 0) clockBrightness = 3; Serial.println("Connecting to WiFi."); WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); @@ -200,7 +220,7 @@ void setup() { Serial.println(F("DFPlayer Mini online.")); - myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms + myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms myDFPlayer.volume(notification_volume); myDFPlayer.EQ(DFPLAYER_EQ_BASS); @@ -328,41 +348,89 @@ void loop() { } } - // if (alarm_on_off == 1) { - // digitalWrite(ALARM_ON_OFF_LED, HIGH); - // } else digitalWrite(ALARM_ON_OFF_LED, LOW); - if (alarmMinutes != currentMinutes) { flag_alarm = 0; } - if (digitalRead(MINUTE_BUTTON) && digitalRead(HOUR_BUTTON)) // Push Min and Hour simultaneouslyly to switch ON or OFF the alarm - { - if (alarm_on_off == 1) { - Serial.println("Setting the alarm off."); - alarm_on_off = 0; - digitalWrite(ALARM_ON_OFF_LED, LOW); - snoozed = 0; - } else { - Serial.println("Setting the alarm on."); - alarm_on_off = 1; - digitalWrite(ALARM_ON_OFF_LED, HIGH); + // Push Hour button and then and set/stop button to switch ON or OFF the alarm + if (digitalRead(HOUR_BUTTON) && !digitalRead(SET_STOP_BUTTON) && !digitalRead(MINUTE_BUTTON)) { + // Disable the colon update for the duration of the button handler + skipTimerLogic = true; + while (digitalRead(HOUR_BUTTON)) { + + if (digitalRead(SET_STOP_BUTTON)) { + if (alarm_on_off == 1) { + Serial.println("Setting the alarm off."); + alarm_on_off = 0; + digitalWrite(ALARM_ON_OFF_LED, LOW); + snoozed = 0; + } else { + Serial.println("Setting the alarm on."); + alarm_on_off = 1; + digitalWrite(ALARM_ON_OFF_LED, HIGH); + } + + Serial.println("Writing new alarm enabled status to EEPROM."); + EEPROM.write(2, alarm_on_off); + EEPROM.commit(); + + red1.showNumberDecEx(00, 0b00000000, true, 2, 0); + red1.showNumberDecEx(alarm_on_off, 0b00000000, true, 2, 2); + delay(750); + + if (digitalRead(MINUTE_BUTTON)) { + pixels.setBrightness(0); + if (enableAudio) myDFPlayer.playMp3Folder(13); + pixels.show(); + } + } + + if (digitalRead(MINUTE_BUTTON)) { + notification_volume = notification_volume + 1; + if (notification_volume > 15) { + notification_volume = 1; + } + + Serial.println("Writing new notification volume level to EEPROM."); + EEPROM.write(3, notification_volume); + EEPROM.commit(); + + myDFPlayer.volume(notification_volume); + red1.showNumberDecEx(00, 0b00000000, true, 2, 0); + red1.showNumberDecEx(notification_volume, 0b00000000, true, 2, 2); + if (enableAudio) { + myDFPlayer.playMp3Folder(14); + } + delay(750); + } } + skipTimerLogic = false; + } - Serial.println("Writing new alarm enabled status to EEPROM."); - EEPROM.write(2, alarm_on_off); - EEPROM.commit(); + // Push Minute button and then and Hour button to increment the clock brightness + if (digitalRead(MINUTE_BUTTON) && !digitalRead(HOUR_BUTTON)) { + // Disable the colon update for the duration of the button handler + skipTimerLogic = true; + while (digitalRead(MINUTE_BUTTON)) { + if (digitalRead(HOUR_BUTTON)) { + clockBrightness = (clockBrightness + 1) % 4; // Cycle through 0-4 - red1.showNumberDecEx(00, 0b00000000, true, 2, 0); - red1.showNumberDecEx(alarm_on_off, 0b00000000, true, 2, 2); - delay(1000); + Serial.println("Writing new clock brightness level to EEPROM."); + EEPROM.write(4, clockBrightness); + EEPROM.commit(); - if (digitalRead(MINUTE_BUTTON)) // Push Min and Hour simultaneously to switch ON or OFF the alarm - { - pixels.setBrightness(0); - if (enableAudio) myDFPlayer.playMp3Folder(13); - pixels.show(); + red1.setBrightness(clockBrightness); + + red1.showNumberDecEx(00, 0b00000000, true, 2, 0); + red1.showNumberDecEx(clockBrightness + 1, 0b00000000, true, 2, 2); + delay(750); // Delay to allow display showing the new brightness level to be seen + } } + skipTimerLogic = false; } + + + + // Check whether we are snoozed and the snooze duration has been passed if (snoozed && epochTimeCurrent - epochTimeSnoozed >= snoozeLengthMinutes * 60 && alarm_on_off == 1) { Serial.println("Snooze duration expired, triggering alarm."); @@ -371,7 +439,6 @@ void loop() { } } - void maybeUpdateClock() { if (timerCount > 0 && colonVisible && !digitalRead(MINUTE_BUTTON) && !digitalRead(HOUR_BUTTON) && !digitalRead(SET_STOP_BUTTON)) { @@ -448,15 +515,14 @@ void alarm() { // Check for snooze. If requested then record the snooze request time (so that we can trigger the alarm // again at the end of the snooze period), and then exit the loop. - if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) - { + if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) { Serial.println("Snoozing."); snoozed = true; epochTimeSnoozed = epochTimeCurrent; break; } } - + // If snooze has been requested then exit the loop. if (snoozed) break; @@ -472,7 +538,7 @@ void alarm() { colonVisible = true; updateTimeDisplay(); - + while (!digitalRead(SET_STOP_BUTTON) && !snoozed) { h = 1; maybeUpdateClock(); From 1fa219dfa0da2d8031f3b3d701fbd6eb1b459d71 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 9 Mar 2025 15:04:36 +0000 Subject: [PATCH 10/17] Update FLUX_CAPACITOR.ino - v10 Pause the timer logic when the set/stop button is pressed. --- FLUX_CAPACITOR.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index e970a48..e7c4e1f 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -22,6 +22,7 @@ // 1. Hour pressed and held, then set/stop button pressed toggles alarm status. // 2. Hour pressed and held, then minute button pressed increments volume level, loops around to level 1. // 3. Minute pressed and held, then hour buttom pressed increments display brightness, loops around to level 1. +// v10 - Pause the timer logic when the set/stop button is pressed. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. @@ -333,10 +334,12 @@ void loop() { } if (digitalRead(SET_STOP_BUTTON)) { + skipTimerLogic = true; Serial.println("Entering alarm setup."); Setup_alarm(); if (enableAudio) myDFPlayer.stop(); Serial.println("Exit alarm setup."); + skipTimerLogic = false; } if (alarmHours == currentHours && flag_alarm == 0 && alarm_on_off == 1) { From 22ba3015a81cacb2cd93f8a064fa176ff0d4a5da Mon Sep 17 00:00:00 2001 From: howarthcd Date: Mon, 10 Mar 2025 21:40:53 +0000 Subject: [PATCH 11/17] Update FLUX_CAPACITOR.ino - V11 - Made the date display format configurable - supports MMDD or DDMM. - Optimised variable types. - Allowed hard-coded WiFi credentials. If not specified then the config portal will launch. - Removed enableAudio variable, it was only used during testing when waiting for audio board to arrive. --- FLUX_CAPACITOR.ino | 280 ++++++++++++++++++++++----------------------- 1 file changed, 137 insertions(+), 143 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index e7c4e1f..b4cc60a 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -23,63 +23,57 @@ // 2. Hour pressed and held, then minute button pressed increments volume level, loops around to level 1. // 3. Minute pressed and held, then hour buttom pressed increments display brightness, loops around to level 1. // v10 - Pause the timer logic when the set/stop button is pressed. +// v11 - Made the date display format configurable - supports MMDD or DDMM. +// - Optimised variable types. +// - Allowed hard-coded WiFi credentials. If not specified then the config portal will launch. +// - Removed enableAudio variable, it was only used during testing when waiting for audio board to arrive. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. -#include "WiFiManager.h" // https://github.com/tzapu/WiFiManager -#include "NTPClient.h" // https://github.com/arduino-libraries/NTPClient -#include "TM1637Display.h" // https://github.com/avishorp/TM1637 -#include "DFRobotDFPlayerMini.h" // https://github.com/DFRobot/DFRobotDFPlayerMini -#include "ESP32_WS2812_Lib.h" // https://github.com/Zhentao-Lin/ESP32_WS2812_Lib +#include // https://github.com/tzapu/WiFiManager +#include // https://github.com/arduino-libraries/NTPClient +#include // https://github.com/avishorp/TM1637 +#include // https://github.com/Zhentao-Lin/ESP32_WS2812_Lib +#include // https://playground.arduino.cc/Code/Time/ +#include // https://github.com/DFRobot/DFRobotDFPlayerMini #include -#include //https://playground.arduino.cc/Code/Time/ - -bool enableAudio = true; - -//========================USEFUL VARIABLES============================= -int clockBrightness = 1; // Set displays brightness 0 to 7 -int snoozeLengthMinutes = 5; // Snooze time in minute -int refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time syncs from the NTP server. -//===================================================================== #define DISPLAY_CLK 21 #define DISPLAY_DIO 22 #define SET_STOP_BUTTON 34 #define HOUR_BUTTON 33 #define MINUTE_BUTTON 32 -//#define FPSerial Serial1 #define LEDS_PIN 4 #define LEDS_COUNT 24 -#define EEPROM_SIZE 12 +#define EEPROM_SIZE 5 #define CHANNEL 0 #define UTC_OFFSET 1 #define ALARM_ON_OFF_LED 26 +#define EEPROM_ALARM_MINUTES 0 +#define EEPROM_ALARM_HOURS 1 +#define EEPROM_ALARM_STATE 2 +#define EEPROM_NOTIFICATION_VOLUME 3 +#define EEPROM_CLOCK_BRIGHTNESS 4 -HardwareSerial FPSerial(1); -const byte RXD2 = 16; // Connects to module's TX => 16 -const byte TXD2 = 17; // Connects to module's RX => 17 - -const long utcOffsetInSeconds = 0; // GMT - -DFRobotDFPlayerMini myDFPlayer; -void printDetail(uint8_t type, int value); - -int alarmHours = 0; -int alarmMinutes = 0; -int flag_alarm = 0; -int alarm_on_off = 1; -int h = 0; +//========================USEFUL VARIABLES============================= +int snoozeLengthMinutes = 5; // Snooze time in minutes +int utcOffsetInSeconds = 0; // Non-DST Offset in seconds +byte dateDisplayFormat = 2; // Date display format, 1 = MMDD, 2 = DDMM +bool clock24h = true; // If false then 12H, if true then 24H. +const char* ssid = ""; // YOUR SSID HERE - leave empty to use WiFi management portal +const char* password = ""; // WIFI PASSWORD HERE +//===================================================================== -int notification_volume = 5; -int audioFinished = 0; +byte refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time syncs from the NTP server. +byte clockBrightness; -int currentMinutes = 0, currentHours = 0, currentYear = 0, currentMonth = 0, currentDay = 0, previousMinutes = 0; +byte currentMinutes = 0, currentHours = 0, currentMonth = 0, currentDay = 0, previousMinutes = 0; +int currentYear = 0; long epochTimeCurrent = 0; long epochTimeSnoozed = 0; long epochTimeNTP = 0; long epochTimeLocalLastRefreshFromNTP = 0; //Tracks the local calculated time that the last NTP refresh was attempted. -bool snoozed = false; unsigned long lastColonToggleTime = 0; bool colonVisible = true; // Start with the colon visible const unsigned long COLON_FLASH_INTERVAL = 1000; // Interval for flashing (1000ms) @@ -112,9 +106,24 @@ void IRAM_ATTR onTimer() { timerCount += 1; } +HardwareSerial FPSerial(1); +const byte RXD2 = 16; // Connects to module's TX => 16 +const byte TXD2 = 17; // Connects to module's RX => 17 + +DFRobotDFPlayerMini myDFPlayer; +void printDetail(uint8_t type, int value); + +byte alarmHours = 0, alarmMinutes = 0; +int flag_alarm = 0; +int alarm_on_off = 1; +bool snoozed = false; + +byte notification_volume; +byte audioFinished = 0; + void setup() { - Serial.begin(9600); + Serial.begin(9600); // Start Serial for Debugging Serial.println("Initialising."); pinMode(SET_STOP_BUTTON, INPUT); @@ -123,6 +132,7 @@ void setup() { pinMode(ALARM_ON_OFF_LED, OUTPUT); pinMode(LEDS_PIN, OUTPUT); + // Pin initialization pixels.begin(); pixels.setBrightness(0); pixels.show(); @@ -133,12 +143,13 @@ void setup() { red1.showNumberDecEx(0, 0b00000000, true); //Init EEPROM - Serial.println("Retrieving the alarm time and status from EEPROM."); + Serial.println("Retrieving the saved setting values from EEPROM."); EEPROM.begin(EEPROM_SIZE); - alarmMinutes = EEPROM.read(0); - alarmHours = EEPROM.read(1); - alarm_on_off = EEPROM.read(2); - notification_volume = EEPROM.read(3); + alarmMinutes = EEPROM.read(EEPROM_ALARM_MINUTES); + alarmHours = EEPROM.read(EEPROM_ALARM_HOURS); + alarm_on_off = EEPROM.read(EEPROM_ALARM_STATE); + notification_volume = EEPROM.read(EEPROM_NOTIFICATION_VOLUME); + clockBrightness = EEPROM.read(EEPROM_CLOCK_BRIGHTNESS); Serial.print("Alarm time: "); Serial.print(alarmHours); @@ -148,86 +159,98 @@ void setup() { Serial.print("Alarm enabled status: "); Serial.println(alarm_on_off); - Serial.print("Motification volume: "); + Serial.print("Notification volume: "); Serial.println(notification_volume); + Serial.print("Clock brightness: "); + Serial.println(clockBrightness); + // Check for out of range values and reset to 00:00 if found. if (alarmMinutes > 59 || alarmHours > 23) { Serial.println("Out of range value found for alarm time, resetting to 00:00."); - EEPROM.write(0, 0); - EEPROM.write(1, 0); - EEPROM.commit(); alarmMinutes = 0; alarmHours = 0; + EEPROM.write(EEPROM_ALARM_MINUTES, alarmMinutes); + EEPROM.write(EEPROM_ALARM_HOURS, alarmHours); + EEPROM.commit(); } if (alarm_on_off > 1) { Serial.println("Out of range value found for alarm on/off, resetting to ON as a precaution."); - EEPROM.write(2, 1); - EEPROM.commit(); alarm_on_off = 1; + EEPROM.write(EEPROM_ALARM_STATE, alarm_on_off); + EEPROM.commit(); } if (notification_volume < 1 || notification_volume > 15) { Serial.println("Out of range value found for notification volume, resetting to level 5 as a precaution."); - EEPROM.write(3, 5); - EEPROM.commit(); notification_volume = 5; + EEPROM.write(EEPROM_NOTIFICATION_VOLUME, notification_volume); + EEPROM.commit(); } - clockBrightness = EEPROM.read(4); - - // Try to retrieve the saved clockBrightness, default to 3 if out of range. - if (clockBrightness > 4) clockBrightness = 3; - if (clockBrightness < 0) clockBrightness = 3; + if (clockBrightness < 0 || clockBrightness > 3) { + Serial.println("Out of range value found for clock brightness, resetting to level 2 as a precaution."); + clockBrightness = 2; + EEPROM.write(EEPROM_CLOCK_BRIGHTNESS, clockBrightness); + EEPROM.commit(); + } + // Connect to WiFi. Attempt to connect to saved details first. Serial.println("Connecting to WiFi."); WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); - WiFiManager manager; - manager.setConnectTimeout(10); - manager.setConnectRetries(5); - if (manager.getWiFiIsSaved()) { - manager.setEnableConfigPortal(false); - if (!manager.autoConnect("FLUX_CAPACITOR", "password")) { - manager.setEnableConfigPortal(true); - manager.setTimeout(60); + + if (strlen(ssid) == 0) { + Serial.println("Using credentials saved via portal."); + WiFiManager manager; + manager.setConnectTimeout(10); + manager.setConnectRetries(5); + if (manager.getWiFiIsSaved()) { + manager.setEnableConfigPortal(false); if (!manager.autoConnect("FLUX_CAPACITOR", "password")) { - Serial.println("Connection failed, restarting..."); - ESP.restart(); // Reset and try again + manager.setEnableConfigPortal(true); + manager.setTimeout(180); + if (!manager.autoConnect("FLUX_CAPACITOR", "password")) { + Serial.println("Connection failed, restarting..."); + ESP.restart(); // Reset and try again + } } } + } else { + Serial.println("Using hard-coded credentials."); + WiFi.begin(ssid, password); } - Serial.println("Successfully connected to WiFi."); - delay(3000); - timeClient.begin(); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println("Successfully connected to WiFi."); // Initialise the audio player. - if (enableAudio) { - FPSerial.begin(9600, SERIAL_8N1, RXD2, TXD2); - Serial.println(); - Serial.println(F("DFRobot DFPlayer Mini Demo")); - Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); - - if (!myDFPlayer.begin(FPSerial, /*isACK = */ true, /*doReset = */ true)) { //Use serial to communicate with mp3. - Serial.println(F("Unable to begin:")); - Serial.println(F("1.Please recheck the connection!")); - Serial.println(F("2.Please insert the SD card!")); - while (true) { - delay(0); // Code to compatible with ESP8266 watch dog. - } + FPSerial.begin(9600, SERIAL_8N1, RXD2, TXD2); + Serial.println(); + Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); + + if (!myDFPlayer.begin(FPSerial, /*isACK = */ true, /*doReset = */ true)) { //Use serial to communicate with mp3. + Serial.println(F("Unable to begin:")); + Serial.println(F("1.Please recheck the connection!")); + Serial.println(F("2.Please insert the SD card!")); + while (true) { + delay(0); // Code to compatible with ESP8266 watch dog. } + } - Serial.println(F("DFPlayer Mini online.")); - - myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms + Serial.println(F("DFPlayer Mini online.")); - myDFPlayer.volume(notification_volume); - myDFPlayer.EQ(DFPLAYER_EQ_BASS); - myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); - } + myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms + myDFPlayer.volume(notification_volume); + myDFPlayer.EQ(DFPLAYER_EQ_BASS); + myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); + timeClient.begin(); Serial.println("Getting the time from the NTP server."); epochTimeNTP = getEpochTimeFromNTPServer(); @@ -249,8 +272,7 @@ void setup() { timerAttachInterrupt(myTimer, &onTimer); timerAlarm(myTimer, alarmLimit, true, 0); - - //Play the first mp3 + // Play the startup mp3 myDFPlayer.playMp3Folder(10); delay(100); audioFinished = 0; @@ -283,16 +305,11 @@ void setup() { } else digitalWrite(ALARM_ON_OFF_LED, LOW); Serial.println("Starting main loop."); - if (enableAudio) { - myDFPlayer.playMp3Folder(14); - } + myDFPlayer.playMp3Folder(14); } void loop() { - //pixels.clear(); - //show_hour(); - h = 1; audioFinished = 0; pixels.setBrightness(0); @@ -321,23 +338,15 @@ void loop() { // Check to see if the time displays need to be updated. maybeUpdateClock(); - if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON) || digitalRead(SET_STOP_BUTTON)) { - skipTimerLogic = true; - } else { - skipTimerLogic = false; - } - - if (enableAudio) { - if (myDFPlayer.available()) { - printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. - } + if (myDFPlayer.available()) { + printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. } if (digitalRead(SET_STOP_BUTTON)) { skipTimerLogic = true; Serial.println("Entering alarm setup."); Setup_alarm(); - if (enableAudio) myDFPlayer.stop(); + myDFPlayer.stop(); Serial.println("Exit alarm setup."); skipTimerLogic = false; } @@ -353,7 +362,7 @@ void loop() { if (alarmMinutes != currentMinutes) { flag_alarm = 0; } - // Push Hour button and then and set/stop button to switch ON or OFF the alarm + // Push and hold Hour button and then and press set/stop button to switch ON or OFF the alarm if (digitalRead(HOUR_BUTTON) && !digitalRead(SET_STOP_BUTTON) && !digitalRead(MINUTE_BUTTON)) { // Disable the colon update for the duration of the button handler skipTimerLogic = true; @@ -372,7 +381,7 @@ void loop() { } Serial.println("Writing new alarm enabled status to EEPROM."); - EEPROM.write(2, alarm_on_off); + EEPROM.write(EEPROM_ALARM_STATE, alarm_on_off); EEPROM.commit(); red1.showNumberDecEx(00, 0b00000000, true, 2, 0); @@ -381,7 +390,7 @@ void loop() { if (digitalRead(MINUTE_BUTTON)) { pixels.setBrightness(0); - if (enableAudio) myDFPlayer.playMp3Folder(13); + myDFPlayer.playMp3Folder(13); pixels.show(); } } @@ -393,22 +402,20 @@ void loop() { } Serial.println("Writing new notification volume level to EEPROM."); - EEPROM.write(3, notification_volume); + EEPROM.write(EEPROM_NOTIFICATION_VOLUME, notification_volume); EEPROM.commit(); myDFPlayer.volume(notification_volume); red1.showNumberDecEx(00, 0b00000000, true, 2, 0); red1.showNumberDecEx(notification_volume, 0b00000000, true, 2, 2); - if (enableAudio) { - myDFPlayer.playMp3Folder(14); - } + myDFPlayer.playMp3Folder(14); delay(750); } } skipTimerLogic = false; } - // Push Minute button and then and Hour button to increment the clock brightness + // Push and hold Minute button and then press Hour button to increment the clock brightness if (digitalRead(MINUTE_BUTTON) && !digitalRead(HOUR_BUTTON)) { // Disable the colon update for the duration of the button handler skipTimerLogic = true; @@ -417,7 +424,7 @@ void loop() { clockBrightness = (clockBrightness + 1) % 4; // Cycle through 0-4 Serial.println("Writing new clock brightness level to EEPROM."); - EEPROM.write(4, clockBrightness); + EEPROM.write(EEPROM_CLOCK_BRIGHTNESS, clockBrightness); EEPROM.commit(); red1.setBrightness(clockBrightness); @@ -430,10 +437,6 @@ void loop() { skipTimerLogic = false; } - - - - // Check whether we are snoozed and the snooze duration has been passed if (snoozed && epochTimeCurrent - epochTimeSnoozed >= snoozeLengthMinutes * 60 && alarm_on_off == 1) { Serial.println("Snooze duration expired, triggering alarm."); @@ -464,17 +467,13 @@ void maybeUpdateClock() { } void updateTimeDisplay() { - // For the time, add the flashing colon logic - uint8_t hour = currentHours; - uint8_t minute = currentMinutes; - if (colonVisible) { - red1.showNumberDecEx(hour, 0b01000000, true, 2, 0); // Display hours with colon - red1.showNumberDecEx(minute, 0b01000000, true, 2, 2); // Display minutes with colon + red1.showNumberDecEx(currentHours, 0b01000000, true, 2, 0); // Display hours with colon + red1.showNumberDecEx(currentMinutes, 0b01000000, true, 2, 2); // Display minutes with colon } else { - red1.showNumberDecEx(hour, 0b00000000, true, 2, 0); // Display hours without colon - red1.showNumberDecEx(minute, 0b00000000, true, 2, 2); // Display minutes without colon + red1.showNumberDecEx(currentHours, 0b00000000, true, 2, 0); // Display hours without colon + red1.showNumberDecEx(currentMinutes, 0b00000000, true, 2, 2); // Display minutes without colon } } @@ -489,6 +488,7 @@ void checkDSTAndSetOffset() { } void alarm() { + byte h = 1; skipTimerLogic = true; snoozed = false; @@ -532,30 +532,25 @@ void alarm() { h = h + 1; } - // If we haven't yet snoozed the continue. + // If we haven't yet snoozed then continue. if (!snoozed) { //skipTimerLogic = false; - if (enableAudio) myDFPlayer.playMp3Folder(random(1, 9)); //Playing the alarm sound + myDFPlayer.playMp3Folder(random(1, 9)); //Playing the alarm sound delay(1500); colonVisible = true; updateTimeDisplay(); - while (!digitalRead(SET_STOP_BUTTON) && !snoozed) { - h = 1; maybeUpdateClock(); - //digitalWrite(ALARM_ON_OFF_LED, HIGH); //If you're not wake-up at the first song, it plays the next one - if (enableAudio) { - if (myDFPlayer.available()) { - printDetail(myDFPlayer.readType(), myDFPlayer.read()); - if (audioFinished == 1) { - audioFinished = 0; - Serial.println("Next song"); - myDFPlayer.playMp3Folder(random(1, 9)); //Playing the alarm sound - } + if (myDFPlayer.available()) { + printDetail(myDFPlayer.readType(), myDFPlayer.read()); + if (audioFinished == 1) { + audioFinished = 0; + Serial.println("Next song"); + myDFPlayer.playMp3Folder(random(1, 9)); //Playing the alarm sound } } @@ -570,7 +565,6 @@ void alarm() { delay(5); pixels.setBrightness(110); pixels.show(); - //Serial.println("Boucle neopixel"); } // If snooze has been requested then set variables to exit the WHILE loop. @@ -584,7 +578,7 @@ void alarm() { } } - if (enableAudio) myDFPlayer.stop(); + myDFPlayer.stop(); audioFinished = 0; pixels.setBrightness(0); @@ -617,8 +611,8 @@ void Setup_alarm() { // Only write to EEPROM if necessary. if (alarmHoursOriginal != alarmHours || alarmMinutesOriginal != alarmMinutes) { Serial.println("Saving the alarm time to EEPROM."); - EEPROM.write(0, alarmMinutes); - EEPROM.write(1, alarmHours); + EEPROM.write(EEPROM_ALARM_MINUTES, alarmMinutes); + EEPROM.write(EEPROM_ALARM_HOURS, alarmHours); EEPROM.commit(); } } From 257da47b6219e86ddba7cd5c5b5e392576d6869d Mon Sep 17 00:00:00 2001 From: howarthcd Date: Mon, 17 Mar 2025 21:19:37 +0000 Subject: [PATCH 12/17] Update FLUX_CAPACITOR.ino - v12 - Prevent a minutes value of 60 or an hours value of 24 being shown briefly when setting the alarm. - Added command to explicitly stop audio before playing next random file during snooze to prevent odd transition sounds. --- FLUX_CAPACITOR.ino | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index b4cc60a..07ec4e1 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -27,6 +27,8 @@ // - Optimised variable types. // - Allowed hard-coded WiFi credentials. If not specified then the config portal will launch. // - Removed enableAudio variable, it was only used during testing when waiting for audio board to arrive. +// v12 - Prevent a minutes value of 60 or an hours value of 24 being shown briefly when setting the alarm. +// - Added command to explicitly stop audio before playing next random file during snooze to prevent odd transition sounds. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. @@ -550,6 +552,7 @@ void alarm() { if (audioFinished == 1) { audioFinished = 0; Serial.println("Next song"); + myDFPlayer.stop(); myDFPlayer.playMp3Folder(random(1, 9)); //Playing the alarm sound } } @@ -599,13 +602,13 @@ void Setup_alarm() { alarmHours = alarmHours + 1; } + if (alarmMinutes > 59) { alarmMinutes = 0; } + if (alarmHours > 23) { alarmHours = 0; } + red1.showNumberDecEx(alarmHours, 0b01000000, true, 2, 0); red1.showNumberDecEx(alarmMinutes, 0b01000000, true, 2, 2); delay(100); - - if (alarmMinutes > 59) { alarmMinutes = 0; } - if (alarmHours > 23) { alarmHours = 0; } } // Only write to EEPROM if necessary. From ebaa3607faddc8aacc025b1e4432b60fc352daa0 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Mon, 17 Mar 2025 22:16:06 +0000 Subject: [PATCH 13/17] Update FLUX_CAPACITOR.ino - v13 - Ensure all MP3 files are played at least once without repetition in a given alarm activation. - Added additional key combination to manually trigger the alarm: Minute pressed and held, then set/stop button pressed. - Don't trigger the alarm when setting the alarm. - Enhanced alarm cancel behaviour. - Added various Serial.print statements. --- FLUX_CAPACITOR.ino | 75 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index 07ec4e1..288764f 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -29,6 +29,12 @@ // - Removed enableAudio variable, it was only used during testing when waiting for audio board to arrive. // v12 - Prevent a minutes value of 60 or an hours value of 24 being shown briefly when setting the alarm. // - Added command to explicitly stop audio before playing next random file during snooze to prevent odd transition sounds. +// v13 - Ensure all MP3 files are played at least once without repetition in a given alarm activation. +// - Added additional key combination to manually trigger the alarm: +// Minute pressed and held, then set/stop button pressed. +// - Don't trigger the alarm when setting the alarm. +// - Enhanced alarm cancel behaviour. +// - Added various Serial.print statements. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. @@ -123,6 +129,8 @@ bool snoozed = false; byte notification_volume; byte audioFinished = 0; +byte mp3PlayOrder[9]; + void setup() { Serial.begin(9600); // Start Serial for Debugging @@ -251,7 +259,14 @@ void setup() { myDFPlayer.volume(notification_volume); myDFPlayer.EQ(DFPLAYER_EQ_BASS); myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); + + randomSeed(analogRead(0)); // Seed randomness + // Initialize the MP3 play order array with numbers 1-9 + for (int i = 0; i < 9; i++) { + mp3PlayOrder[i] = i + 1; + } + timeClient.begin(); Serial.println("Getting the time from the NTP server."); @@ -353,7 +368,7 @@ void loop() { skipTimerLogic = false; } - if (alarmHours == currentHours && flag_alarm == 0 && alarm_on_off == 1) { + if (alarmHours == currentHours && flag_alarm == 0 && alarm_on_off == 1 && !digitalRead(SET_STOP_BUTTON)) { if (alarmMinutes == currentMinutes) { Serial.println("Triggering the alarm."); flag_alarm = 1; @@ -375,7 +390,8 @@ void loop() { Serial.println("Setting the alarm off."); alarm_on_off = 0; digitalWrite(ALARM_ON_OFF_LED, LOW); - snoozed = 0; + if (snoozed) Serial.println("Cancelling snooze."); + snoozed = false; } else { Serial.println("Setting the alarm on."); alarm_on_off = 1; @@ -435,6 +451,12 @@ void loop() { red1.showNumberDecEx(clockBrightness + 1, 0b00000000, true, 2, 2); delay(750); // Delay to allow display showing the new brightness level to be seen } + + if (digitalRead(SET_STOP_BUTTON)) { + Serial.println("Triggering alarm manually."); + delay(1000); + alarm(); + } } skipTimerLogic = false; } @@ -491,8 +513,11 @@ void checkDSTAndSetOffset() { void alarm() { byte h = 1; + boolean cancelAlarm = false; skipTimerLogic = true; snoozed = false; + byte mp3PlayOrderIndex = 0; + shuffleMP3OrderArray(); Serial.println("Count to 88."); @@ -516,7 +541,10 @@ void alarm() { pixels.setBrightness(20 + h); pixels.show(); - if (digitalRead(SET_STOP_BUTTON)) { u = 89; } + if (digitalRead(SET_STOP_BUTTON)) { + u = 89; + Serial.println("Alarm cancelled."); + cancelAlarm = true;} // Check for snooze. If requested then record the snooze request time (so that we can trigger the alarm // again at the end of the snooze period), and then exit the loop. @@ -535,15 +563,17 @@ void alarm() { } // If we haven't yet snoozed then continue. - if (!snoozed) { - //skipTimerLogic = false; - myDFPlayer.playMp3Folder(random(1, 9)); //Playing the alarm sound + if (!snoozed && !cancelAlarm) { + + Serial.print("Playing first MP3, number: "); + Serial.println(mp3PlayOrder[mp3PlayOrderIndex]); + myDFPlayer.playMp3Folder(mp3PlayOrder[mp3PlayOrderIndex]); //Playing the alarm sound delay(1500); colonVisible = true; updateTimeDisplay(); - while (!digitalRead(SET_STOP_BUTTON) && !snoozed) { + while (!cancelAlarm && !snoozed) { maybeUpdateClock(); //If you're not wake-up at the first song, it plays the next one @@ -551,9 +581,17 @@ void alarm() { printDetail(myDFPlayer.readType(), myDFPlayer.read()); if (audioFinished == 1) { audioFinished = 0; - Serial.println("Next song"); myDFPlayer.stop(); - myDFPlayer.playMp3Folder(random(1, 9)); //Playing the alarm sound + // Increment the play order index to select the next song + mp3PlayOrderIndex +=1; + // If we have exceeded the array bound then reset and reshuffle the array. + if (mp3PlayOrderIndex > 8) { + mp3PlayOrderIndex = 0; + shuffleMP3OrderArray(); + } + Serial.print("Playing next MP3, number: "); + Serial.println(mp3PlayOrder[mp3PlayOrderIndex]); + myDFPlayer.playMp3Folder(mp3PlayOrder[mp3PlayOrderIndex]); //Playing the alarm sound } } @@ -578,6 +616,9 @@ void alarm() { snoozed = true; epochTimeSnoozed = epochTimeCurrent; } + + cancelAlarm = digitalRead(SET_STOP_BUTTON); + if (cancelAlarm) Serial.println("Alarm cancelled."); } } @@ -586,6 +627,12 @@ void alarm() { pixels.setBrightness(0); pixels.show(); + + // Prevent the alarm time from being displayed when the set/stop button is pressed + // to cancel the alarm. The delay just prevents the main loop from being returned-to immediately + // and the set/stop button handler from being entered. + delay(200); + skipTimerLogic = false; } @@ -745,3 +792,13 @@ long getEpochTimeFromNTPServer() { } return epochTime; } + +// Function to shuffle the array using Fisher-Yates algorithm +void shuffleMP3OrderArray() { + for (int i = 8; i > 0; i--) { + int j = random(0, i + 1); + int temp = mp3PlayOrder[i]; + mp3PlayOrder[i] = mp3PlayOrder[j]; + mp3PlayOrder[j] = temp; + } +} From ec7947c1dc53d0eb1acdf87245a3493b27345493 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sat, 22 Mar 2025 15:01:05 +0000 Subject: [PATCH 14/17] Create README.md --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c151fc --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +## Setting the Alarm Time +1. Press and hold **SETUP/STOP**. +2. While holding it, press **HOUR** and/or **MINUTE** to adjust the alarm time. +3. The alarm time will appear on the display. + +## Turning the Alarm On/Off +1. Press and hold **HOUR**, then press **SETUP/STOP** to toggle the alarm on or off. +2. The display will show the alarm status: + - **0** = Alarm is off + - **1** = Alarm is on +3. If the alarm is **on**, the **SETUP/STOP** button will stay lit. If it's **off**, the light will turn off. + +## Using the Alarm & Snooze +- When the alarm goes off, press **SETUP/STOP** to turn it off. +- To snooze the alarm for 5 minutes, press **HOUR** or **MINUTE**. The alarm will keep snoozing indefinitely until stopped. +- To cancel snooze, press **SETUP/STOP** or disable the alarm. + +## Adjusting Volume +1. Press and hold **HOUR**. +2. While holding it, press **MINUTE** to increase the volume (levels 1-15). +3. When it reaches 15, the next press will reset it to 1. + +## Adjusting Display Brightness +1. Press and hold **MINUTE**. +2. While holding it, press **HOUR** to adjust brightness (levels 1-4). +3. When it reaches 4, the next press will reset it to 1. + +## Demo Mode (Manually Trigger the Alarm) +- To test the alarm, press and hold **MINUTE**, then press **SETUP/STOP**. +- The alarm will act as if it triggered normally, and you can cancel or snooze it as usual. From 765bc9044051829146f47b171f8cd38903791da5 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 23 Mar 2025 14:26:26 +0000 Subject: [PATCH 15/17] Update FLUX_CAPACITOR.ino - v14 - Allowed volume increase past MP3 level 15. - Volume now scaled. Only allows setting of 1-10, but these are multiplied by 3 when setting the volume level, 10 * 3 = 30 which is the max. --- FLUX_CAPACITOR.ino | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index 288764f..0ea572e 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -35,6 +35,8 @@ // - Don't trigger the alarm when setting the alarm. // - Enhanced alarm cancel behaviour. // - Added various Serial.print statements. +// v14 - Allowed volume increase past MP3 level 15. +// - Volume now scaled. Only allows setting of 1-10, but these are multiplied by 3 when setting the volume level, 10 * 3 = 30 which is the max. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. @@ -192,9 +194,9 @@ void setup() { EEPROM.commit(); } - if (notification_volume < 1 || notification_volume > 15) { - Serial.println("Out of range value found for notification volume, resetting to level 5 as a precaution."); - notification_volume = 5; + if (notification_volume < 1 || notification_volume > 10) { + Serial.println("Out of range value found for notification volume, resetting to low level as a precaution."); + notification_volume = 2; EEPROM.write(EEPROM_NOTIFICATION_VOLUME, notification_volume); EEPROM.commit(); } @@ -415,7 +417,7 @@ void loop() { if (digitalRead(MINUTE_BUTTON)) { notification_volume = notification_volume + 1; - if (notification_volume > 15) { + if (notification_volume > 10) { notification_volume = 1; } @@ -423,7 +425,7 @@ void loop() { EEPROM.write(EEPROM_NOTIFICATION_VOLUME, notification_volume); EEPROM.commit(); - myDFPlayer.volume(notification_volume); + myDFPlayer.volume(notification_volume * 3); red1.showNumberDecEx(00, 0b00000000, true, 2, 0); red1.showNumberDecEx(notification_volume, 0b00000000, true, 2, 2); myDFPlayer.playMp3Folder(14); From 4c6ea8e57ebe03ac503d1b72151136b6ea5e5a0f Mon Sep 17 00:00:00 2001 From: howarthcd Date: Sun, 23 Mar 2025 14:27:52 +0000 Subject: [PATCH 16/17] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c151fc..b854bc4 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ ## Adjusting Volume 1. Press and hold **HOUR**. -2. While holding it, press **MINUTE** to increase the volume (levels 1-15). -3. When it reaches 15, the next press will reset it to 1. +2. While holding it, press **MINUTE** to increase the volume (levels 1-10). +3. When it reaches 10, the next press will reset it to 1. ## Adjusting Display Brightness 1. Press and hold **MINUTE**. From fe15c7988d4c2544edcded1397004dc4c456fc26 Mon Sep 17 00:00:00 2001 From: howarthcd Date: Tue, 1 Apr 2025 21:56:33 +0100 Subject: [PATCH 17/17] Update FLUX_CAPACITOR.ino - v15 - untested Ported latest time keeping code from BTTF logo & clock unit. --- FLUX_CAPACITOR.ino | 276 ++++++++++++++++++++++----------------------- 1 file changed, 135 insertions(+), 141 deletions(-) diff --git a/FLUX_CAPACITOR.ino b/FLUX_CAPACITOR.ino index 0ea572e..061cc70 100644 --- a/FLUX_CAPACITOR.ino +++ b/FLUX_CAPACITOR.ino @@ -37,15 +37,15 @@ // - Added various Serial.print statements. // v14 - Allowed volume increase past MP3 level 15. // - Volume now scaled. Only allows setting of 1-10, but these are multiplied by 3 when setting the volume level, 10 * 3 = 30 which is the max. +// v15 - Ported latest time keeping code from BTTF logo & clock unit. // // TODO: Make use of EEPROM to snooze status in case of accidental restart. // TODO: Modify to only update the time from the NTP server if in the middle of the minute so as not to avoid the time jumping backwards. +// TODO: Port timekeeping code from BTTF clock project. #include // https://github.com/tzapu/WiFiManager -#include // https://github.com/arduino-libraries/NTPClient #include // https://github.com/avishorp/TM1637 #include // https://github.com/Zhentao-Lin/ESP32_WS2812_Lib -#include // https://playground.arduino.cc/Code/Time/ #include // https://github.com/DFRobot/DFRobotDFPlayerMini #include @@ -58,7 +58,6 @@ #define LEDS_COUNT 24 #define EEPROM_SIZE 5 #define CHANNEL 0 -#define UTC_OFFSET 1 #define ALARM_ON_OFF_LED 26 #define EEPROM_ALARM_MINUTES 0 #define EEPROM_ALARM_HOURS 1 @@ -67,23 +66,24 @@ #define EEPROM_CLOCK_BRIGHTNESS 4 //========================USEFUL VARIABLES============================= -int snoozeLengthMinutes = 5; // Snooze time in minutes -int utcOffsetInSeconds = 0; // Non-DST Offset in seconds -byte dateDisplayFormat = 2; // Date display format, 1 = MMDD, 2 = DDMM -bool clock24h = true; // If false then 12H, if true then 24H. -const char* ssid = ""; // YOUR SSID HERE - leave empty to use WiFi management portal -const char* password = ""; // WIFI PASSWORD HERE +const char* ntpServer = "pool.ntp.org"; +String timezone = "GMT0BST,M3.5.0/1,M10.5.0"; // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv +int refreshTimeFromNTPIntervalSeconds = 1800; // The minimum time between time syncs with the NTP server. +int snoozeLengthMinutes = 5; // Snooze time in minutes +const char* ssid = ""; // YOUR SSID HERE - leave empty to use WiFi management portal +const char* password = ""; // WIFI PASSWORD HERE //===================================================================== -byte refreshTimeFromNTPIntervalMinutes = 1; // The minimum time inbetween time syncs from the NTP server. +bool verboseDebug = false; byte clockBrightness; +struct tm timeinfo; -byte currentMinutes = 0, currentHours = 0, currentMonth = 0, currentDay = 0, previousMinutes = 0; +byte currentSeconds = 0, currentMinutes = 0, currentHours = 0, previousMinutes = 0; int currentYear = 0; -long epochTimeCurrent = 0; -long epochTimeSnoozed = 0; -long epochTimeNTP = 0; -long epochTimeLocalLastRefreshFromNTP = 0; //Tracks the local calculated time that the last NTP refresh was attempted. +unsigned long epochTimeSnoozed = 0; +unsigned long epochTimeIncremented = 0; +unsigned long epochTime = 0; +unsigned long epochTimeLocalLastRefreshFromNTP = 0; //Tracks the local calculated time that the last NTP refresh was attempted. unsigned long lastColonToggleTime = 0; bool colonVisible = true; // Start with the colon visible const unsigned long COLON_FLASH_INTERVAL = 1000; // Interval for flashing (1000ms) @@ -93,7 +93,6 @@ ESP32_WS2812 pixels = ESP32_WS2812(LEDS_COUNT, LEDS_PIN, CHANNEL, TYPE_GRB); TM1637Display red1(DISPLAY_CLK, DISPLAY_DIO); WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds* UTC_OFFSET); int timerCount = 5; // Set the timerCount artificially high so that the first display update happens immediately. bool skipTimerLogic = false; @@ -109,8 +108,7 @@ void IRAM_ATTR onTimer() { // other time-sensitive action. if (currentYear > 0 && !skipTimerLogic) updateTimeDisplay(); - // Increment the time by 1s. - epochTimeCurrent += 1; + epochTimeIncremented += 1; // Keep track of how many times we have got here. timerCount += 1; @@ -138,13 +136,13 @@ void setup() { Serial.begin(9600); // Start Serial for Debugging Serial.println("Initialising."); + // Pin initialization pinMode(SET_STOP_BUTTON, INPUT); pinMode(HOUR_BUTTON, INPUT); pinMode(MINUTE_BUTTON, INPUT); pinMode(ALARM_ON_OFF_LED, OUTPUT); pinMode(LEDS_PIN, OUTPUT); - // Pin initialization pixels.begin(); pixels.setBrightness(0); pixels.show(); @@ -201,7 +199,7 @@ void setup() { EEPROM.commit(); } - if (clockBrightness < 0 || clockBrightness > 3) { + if (clockBrightness > 3) { Serial.println("Out of range value found for clock brightness, resetting to level 2 as a precaution."); clockBrightness = 2; EEPROM.write(EEPROM_CLOCK_BRIGHTNESS, clockBrightness); @@ -235,12 +233,28 @@ void setup() { } while (WiFi.status() != WL_CONNECTED) { - delay(500); + delay(100); Serial.print("."); } - + Serial.println(""); Serial.println("Successfully connected to WiFi."); + configTime(0, 0, ntpServer); + + epochTime = getTime(); + + if (epochTime == 0) { + Serial.println("Could not retrieve time from NTP server, restarting..."); + ESP.restart(); // Reset and try again + } else { + Serial.print("Epoch time from NTP server: "); + Serial.println(epochTime); + setTimezone(timezone); + } + + epochTimeLocalLastRefreshFromNTP = epochTime; + epochTimeIncremented = epochTime; + // Initialise the audio player. FPSerial.begin(9600, SERIAL_8N1, RXD2, TXD2); Serial.println(); @@ -261,27 +275,13 @@ void setup() { myDFPlayer.volume(notification_volume); myDFPlayer.EQ(DFPLAYER_EQ_BASS); myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); - - randomSeed(analogRead(0)); // Seed randomness + + randomSeed(analogRead(0)); // Seed randomness // Initialize the MP3 play order array with numbers 1-9 for (int i = 0; i < 9; i++) { mp3PlayOrder[i] = i + 1; } - - timeClient.begin(); - - Serial.println("Getting the time from the NTP server."); - epochTimeNTP = getEpochTimeFromNTPServer(); - if (epochTimeNTP == 0) { - Serial.println("Could not retrieve time from NTP server, restarting..."); - ESP.restart(); // Reset and try again - } else { - Serial.print("Epoch time from NTP server: "); - Serial.println(epochTimeNTP); - } - epochTimeLocalLastRefreshFromNTP = epochTimeNTP; - epochTimeCurrent = epochTimeNTP; // Define a timer. The timer will be used to toggle the time // colon on/off every 1s. @@ -332,26 +332,47 @@ void loop() { audioFinished = 0; pixels.setBrightness(0); - // Maybe update the time from the NTP server. - // TODO: Check seconds so as not to set the time backwards if the local timekeeper has advanced too much. - if (epochTimeCurrent - epochTimeLocalLastRefreshFromNTP > refreshTimeFromNTPIntervalMinutes * 60) { - Serial.println("Getting the time from the NTP server."); - epochTimeNTP = 0; - epochTimeNTP = getEpochTimeFromNTPServer(); - if (epochTimeNTP > 0) { - epochTimeCurrent = epochTimeNTP; + if (!snoozed) { + if (currentSeconds >= 30 && epochTimeIncremented - epochTimeLocalLastRefreshFromNTP >= refreshTimeFromNTPIntervalSeconds) { + Serial.println("Time to resync with NTP server."); + epochTimeLocalLastRefreshFromNTP = epochTimeIncremented; + reconnectWiFi(); } - if (epochTimeNTP == 0) { - Serial.println("Could not retrieve time from NTP server, try again next time."); - } else { - Serial.print("Epoch time from NTP server: "); - Serial.println(epochTimeNTP); + if (WiFi.status() == WL_CONNECTED) { + Serial.println("Getting local time from NTP server."); + } else if (verboseDebug) { + Serial.println("Getting local time from onboard RTC."); + } + + epochTime = getTime(); + + if (WiFi.status() == WL_CONNECTED) { + Serial.println("Disconnecting from WiFi."); + WiFi.disconnect(); + delay(1000); + } + + if (verboseDebug) Serial.print("Local time from onboard RTC is: "); + if (verboseDebug) Serial.println(epochTime); + + if (epochTime == 0) { + Serial.println("Could not get local time from onboard RTC, setting onboard RTC from incremented epoch time."); + setTime(epochTimeIncremented); + Serial.println("Getting local time from onboard RTC after setting from incremented epoch time."); + epochTime = getTime(); + Serial.print("Local time from onboard RTC after setting from incremented epoch time is: "); + Serial.println(epochTime); } - // Store the current time so that we don't get stuck in a loop - // if the NTP server was unavailable. - epochTimeLocalLastRefreshFromNTP = epochTimeCurrent; + if (epochTime == 0) { + Serial.println("Could not retrieve local time, reconnecting to WiFi to sync from NTP server"); + reconnectWiFi(); + } else { + if (verboseDebug) Serial.print("Epoch time from onboard RTC: "); + if (verboseDebug) Serial.println(epochTime); + epochTimeIncremented = epochTime; + } } // Check to see if the time displays need to be updated. @@ -464,29 +485,50 @@ void loop() { } // Check whether we are snoozed and the snooze duration has been passed - if (snoozed && epochTimeCurrent - epochTimeSnoozed >= snoozeLengthMinutes * 60 && alarm_on_off == 1) { + if (snoozed && epochTimeIncremented - epochTimeSnoozed >= snoozeLengthMinutes * 60 && alarm_on_off == 1) { Serial.println("Snooze duration expired, triggering alarm."); alarm(); Serial.println("Exit alarm"); } } +void reconnectWiFi() { + int wifiConnectLoopCounter = 0; + + if (WiFi.status() == WL_CONNECTED) { WiFi.disconnect(); } + Serial.print("Reconnecting to WiFi."); + WiFi.reconnect(); + while (WiFi.status() != WL_CONNECTED && wifiConnectLoopCounter <= 50) { + delay(100); + Serial.print("."); + wifiConnectLoopCounter += 1; + } + + if (WiFi.status() == WL_CONNECTED) { + Serial.println(""); + Serial.println("Successfully reconnected to WiFi."); + } else { + Serial.println(""); + Serial.println("Timeout when connecting to WiFi."); + WiFi.disconnect(); + } +} + void maybeUpdateClock() { if (timerCount > 0 && colonVisible && !digitalRead(MINUTE_BUTTON) && !digitalRead(HOUR_BUTTON) && !digitalRead(SET_STOP_BUTTON)) { //Serial.println("Entered time display update..."); previousMinutes = currentMinutes; - setTime(epochTimeCurrent); - currentYear = year(); - currentMonth = month(); - currentDay = day(); - currentHours = hour(); - currentMinutes = minute(); + epochTime = getTime(); + currentYear = timeinfo.tm_year + 1900; + currentHours = timeinfo.tm_hour; + currentMinutes = timeinfo.tm_min; + currentSeconds = timeinfo.tm_sec; + if (currentYear >= 2025 && previousMinutes != currentMinutes) { Serial.println("Updating time display."); updateTimeDisplay(); red1.setBrightness(clockBrightness); - checkDSTAndSetOffset(); timerCount = 0; } } @@ -503,16 +545,6 @@ void updateTimeDisplay() { } } -void checkDSTAndSetOffset() { - if (isDST(currentMonth, currentDay, currentYear)) { - adjustTime(utcOffsetInSeconds + (UTC_OFFSET * 3600)); // Apply DST - Serial.println("DST active, UTC+1"); - } else { - adjustTime(utcOffsetInSeconds); // Standard time - Serial.println("DST inactive, UTC"); - } -} - void alarm() { byte h = 1; boolean cancelAlarm = false; @@ -546,14 +578,15 @@ void alarm() { if (digitalRead(SET_STOP_BUTTON)) { u = 89; Serial.println("Alarm cancelled."); - cancelAlarm = true;} + cancelAlarm = true; + } // Check for snooze. If requested then record the snooze request time (so that we can trigger the alarm // again at the end of the snooze period), and then exit the loop. if (digitalRead(MINUTE_BUTTON) || digitalRead(HOUR_BUTTON)) { Serial.println("Snoozing."); snoozed = true; - epochTimeSnoozed = epochTimeCurrent; + epochTimeSnoozed = epochTimeIncremented; break; } } @@ -585,7 +618,7 @@ void alarm() { audioFinished = 0; myDFPlayer.stop(); // Increment the play order index to select the next song - mp3PlayOrderIndex +=1; + mp3PlayOrderIndex += 1; // If we have exceeded the array bound then reset and reshuffle the array. if (mp3PlayOrderIndex > 8) { mp3PlayOrderIndex = 0; @@ -616,7 +649,7 @@ void alarm() { { Serial.println("Snoozing."); snoozed = true; - epochTimeSnoozed = epochTimeCurrent; + epochTimeSnoozed = epochTimeIncremented; } cancelAlarm = digitalRead(SET_STOP_BUTTON); @@ -629,7 +662,7 @@ void alarm() { pixels.setBrightness(0); pixels.show(); - + // Prevent the alarm time from being displayed when the set/stop button is pressed // to cancel the alarm. The delay just prevents the main loop from being returned-to immediately // and the set/stop button handler from being entered. @@ -731,70 +764,6 @@ void printDetail(uint8_t type, int value) { } } -bool isDST(int month, int day, int year) { - int lastSundayInMarch = getLastSundayOfMonth(3, year); - int lastSundayInOctober = getLastSundayOfMonth(10, year); - int dayOfYear = getDayOfYear(month, day, year); - return (dayOfYear >= lastSundayInMarch && dayOfYear < lastSundayInOctober); -} - -int getDayOfYear(int month, int day, int year) { - int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - if (isLeapYear(year)) { - daysInMonth[1] = 29; // Leap year adjustment - } - - int dayOfYear = 0; - for (int i = 0; i < month - 1; i++) { - dayOfYear += daysInMonth[i]; - } - return dayOfYear + day; -} - -bool isLeapYear(int year) { - return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); -} - -int getLastSundayOfMonth(int month, int year) { - // Array with number of days per month - int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - if (isLeapYear(year)) { - daysInMonth[1] = 29; // February has 29 days in a leap year - } - int lastDay = daysInMonth[month - 1]; // Last day of the month - - // Time structure to hold information about the last day of the month - tm timeinfo; - timeinfo.tm_year = year - 1900; // Year since 1900 - timeinfo.tm_mon = month - 1; // Month (0-11) - timeinfo.tm_mday = lastDay; - timeinfo.tm_hour = 12; // Set to noon to avoid DST issues - timeinfo.tm_min = 0; - timeinfo.tm_sec = 0; - mktime(&timeinfo); // Normalize tm struct - - // Get the weekday (0 = Sunday, 1 = Monday, ..., 6 = Saturday) - int weekday = timeinfo.tm_wday; - - // Calculate the last Sunday of the month - int lastSunday = lastDay - weekday; - return getDayOfYear(month, lastSunday, year); // Convert to day of the year -} - -long getEpochTimeFromNTPServer() { - long epochTime = 0; - // Get the current time, retry if unsuccessful - for (int i = 0; i <= 20; i++) { - timeClient.update(); - epochTime = timeClient.getEpochTime(); - if (epochTime > 0) { - break; - } - delay(50); - } - return epochTime; -} - // Function to shuffle the array using Fisher-Yates algorithm void shuffleMP3OrderArray() { for (int i = 8; i > 0; i--) { @@ -804,3 +773,28 @@ void shuffleMP3OrderArray() { mp3PlayOrder[j] = temp; } } + +// Function that gets current epoch time +unsigned long getTime() { + time_t now; + if (!getLocalTime(&timeinfo)) { + Serial.println("Failed to obtain time"); + return (0); + } + time(&now); + return now; +} + +void setTime(int epoch) { + struct timeval now = { .tv_sec = epoch }; + + Serial.print("Setting onboard RTC to: "); + Serial.println(epoch); + settimeofday(&now, NULL); +} + +void setTimezone(String timezone) { + Serial.printf("Setting Timezone to %s\n", timezone.c_str()); + setenv("TZ", timezone.c_str(), 1); // Now adjust the TZ. Clock settings are adjusted to show the new local time. + tzset(); +}