#pragma once extern std::vector melody_steps; void PublishMqtt(const char * data); class Player { public: uint16_t id; // The (internal) ID of the selected melody. Not specificly used anywhere atm. Might be used later. std::string name = "melody1"; // Name of the Melody saved. Will be used to read the file: /name.bin uint16_t speed = 500; // Time to wait per beat. (In Miliseconds) uint32_t duration = 15000; // Total Duration that program will run (In Miliseconds) uint32_t loop_duration = 0; // Duration of the playback per segment uint32_t interval = 0; // Indicates the Duration of the Interval between finished segments, IF "inf" is true bool infinite_play = false; // Infinite Loop Indicator (If True the melody will loop forever or until stoped, with pauses of "interval" in between loops) bool isPlaying = false;; // Indicates if the Melody is actually Playing right now. bool isPaused = false; // If playing, indicates if the Melody is Paused uint64_t startTime = 0; // The time-point the Melody started Playing uint64_t loopStartTime = 0; // The time-point the current segment started Playing bool hardStop = false; // Flags a hardstop, immediately. uint64_t pauseTime = 0; // The time-point the melody paused void play() { isPlaying = true; hardStop = false; startTime = loopStartTime = millis(); Serial.println("Plbck: PLAY"); } void forceStop() { hardStop = true; isPlaying = false; Serial.println("Plbck: FORCE STOP"); } void stop() { hardStop = false; isPlaying = false; Serial.println("Plbck: STOP"); } void pause() { isPaused = true; Serial.println("Plbck: PAUSE"); } void unpause() { isPaused = false; loopStartTime = millis(); Serial.println("Plbck: RESUME"); } // Handles Incoming Commands to PLAY or STOP void command(char * command){ Serial.print("INCOMING COMMAND: "); Serial.println(command); if (command[0] == '1') { play(); PublishMqtt("OK - PLAY"); } else if (command[0] == '0') { forceStop(); PublishMqtt("OK - STOP"); } } // Sets incoming Attributes for the Melody, into the class' variables. void setMelodyAttributes(JsonDocument doc){ if (doc.containsKey("name")) { name = doc["name"].as(); } if (doc.containsKey("id")) { id = doc["id"].as(); } if (doc.containsKey("duration")) { duration = doc["duration"].as(); } if (doc.containsKey("infinite")) { infinite_play = doc["infinite"].as(); } if (doc.containsKey("interval")) { interval = doc["interval"].as(); } if (doc.containsKey("speed")) { speed = doc["speed"].as(); } if (doc.containsKey("loop_dur")) { loop_duration = doc["loop_dur"].as(); } // Print Just for Debugging Purposes Serial.printf("Name: %s, ID: %d, Total Duration: %lu, Loop Duration: %lu, Interval: %d, Speed: %d, Inf: %s\n", name.c_str(), id, duration, loop_duration, interval, speed, infinite_play ? "true" : "false" ); } // Loads the Selected melody from a .bin file, into RAM void loadMelodyInRAM(std::vector &melody_steps) { std::string filePath = "/" + name + ".bin"; Serial.println("New Melody Selected !!!"); Serial.println("Reading data from file..."); File bin_file = SPIFFS.open(filePath.c_str(), "r"); if (!bin_file) { Serial.println("Failed to Open File"); return; } size_t fileSize = bin_file.size(); size_t steps = fileSize / 2; melody_steps.resize(steps); Serial.print("Opened File ! Size: "); Serial.print(fileSize); Serial.print(" Steps: "); Serial.println(steps); for (size_t i=0; i dailySchedule; TimeNow now; // update to current time void updateTime(){ now.hour = getHour(); now.minute = getMinute(); now.second = getSecond(); } // Read the current Month's JSON file and add the daily entries to the dailySchedule void loadDailySchedule() { std::string filePath = "/" + getMonth() + ".json"; File file = SPIFFS.open(filePath.c_str(), "r"); if (!file) { Serial.printf("Failed to open file: %s\n", filePath.c_str()); return; } Serial.println("Opened daily schedule file"); StaticJsonDocument<8192> doc; // Adjust size based on expected JSON complexity DeserializationError error = deserializeJson(doc, file); file.close(); if (error) { Serial.printf("Failed to parse JSON: %s\n", error.c_str()); return; } Serial.println("- - - SERIALIZE JSON - - -"); serializeJsonPretty(doc, Serial); Serial.println("- - - - END JSON - - - -"); // Extract entries for the current day std::string currentDay = getDay(); dailySchedule.clear(); // Clear previous day's schedule Serial.printf("Current Day: %s\n", currentDay.c_str()); if (doc.containsKey(currentDay.c_str())) { Serial.println("doc contains key!"); // Check if the current day contains a single object or an array JsonVariant dayVariant = doc[currentDay.c_str()]; if (dayVariant.is()) { // Handle case where the day contains an array of entries JsonArray dayEntries = dayVariant.as(); for (JsonObject entry : dayEntries) { ScheduleEntry schedule; schedule.mel = entry["name"].as(); schedule.hour = entry["hour"]; schedule.minute = entry["minute"]; schedule.speed = entry["speed"]; schedule.duration = entry["duration"]; dailySchedule.push_back(schedule); Serial.printf("Added Entry - Melody: %s, Hour: %d, Minute: %d, Speed: %d, Duration: %d\n", schedule.mel.c_str(), schedule.hour, schedule.minute, schedule.speed, schedule.duration); } } else if (dayVariant.is()) { // Handle case where the day contains a single object JsonObject entry = dayVariant.as(); ScheduleEntry schedule; schedule.mel = entry["name"].as(); schedule.hour = entry["hour"]; schedule.minute = entry["minute"]; schedule.speed = entry["speed"]; schedule.duration = entry["duration"]; dailySchedule.push_back(schedule); Serial.printf("Added Single Entry - Melody: %s, Hour: %d, Minute: %d, Speed: %d, Duration: %d\n", schedule.mel.c_str(), schedule.hour, schedule.minute, schedule.speed, schedule.duration); } else { Serial.println("Invalid data format for the current day."); } } else { Serial.println("No schedule found for today."); } } // Calls "loadDailySchedule" and returns true ONCE per new day (00:00) bool newDayCheck() { // Static variable to store whether we've already detected the new day static bool alreadyTriggered = false; // Check if it's midnight if (now.hour == 0 && now.minute == 0) { if (!alreadyTriggered) { // First time at midnight, trigger the event alreadyTriggered = true; Serial.println("New day detected, returning true."); loadDailySchedule(); return true; } else { // It's still midnight, but we've already triggered Serial.println("Already triggered for today, returning false."); return false; } } else { // Reset the trigger after midnight has passed alreadyTriggered = false; return false; } } public: // get date and time data from the RTC module uint16_t getYear() { return rtc.now().year(); } std::string getMonth() { return monthName(rtc.now().month()); } std::string getDay() { return std::to_string(rtc.now().day()); } uint8_t getHour() { return rtc.now().hour(); } uint8_t getMinute() { return rtc.now().minute(); } uint8_t getSecond() { return rtc.now().second(); } // turn months from decimal to char* std::string monthName(uint8_t monthNumber) { const char* monthNames[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; return monthNumber >= 1 && monthNumber <= 12 ? monthNames[monthNumber - 1] : "unknown"; } // Loads and updates the daily schedule void refreshDailySchedule() { loadDailySchedule(); } // Returns the daily schedule in "ScheduleEntry" format const std::vector& getDailySchedule() const { return dailySchedule; } // Prints the time, NOW. void printTimeNow(){ Serial.printf("Current Time: %s %s, %u - %02u:%02u:%02u\n", getMonth().c_str(), // Month as a string getDay().c_str(), // Day as a string getYear(), // Year as an integer getHour(), // Hour (24-hour format) getMinute(), // Minute getSecond()); // Second } void tick(){ updateTime(); newDayCheck(); } void checkAndRunSchedule(Player & player) { for (auto it = dailySchedule.begin(); it != dailySchedule.end(); ) { Serial.println("Running daily schedule check"); if (now.hour == it->hour && now.minute == it->minute) { Serial.printf("Entry Exists, returning True!"); StaticJsonDocument<200> jsonDoc; jsonDoc["name"] = it->mel.c_str(); jsonDoc["speed"] = it->speed; jsonDoc["duration"] = it->duration; jsonDoc["loop_dur"] = 0; jsonDoc["internal"] = 0; Serial.printf("name: %s speed: %d duration: %d",it->mel.c_str(), it->speed, it->duration); if (!player.isPlaying){ player.setMelodyAttributes(jsonDoc); player.loadMelodyInRAM(melody_steps); player.play(); it = dailySchedule.erase(it); } } else { Serial.printf("No Entry, returning False!"); ++it; } } } };