Added Websocket Support Added Universal Message Handling for both MQTT and WS Added Timekeeper Class, that handles Physical Clock and Scheduling Added Bell Assignment Settings, Note to Bell mapping
214 lines
6.3 KiB
C++
214 lines
6.3 KiB
C++
#pragma once
|
|
|
|
extern std::vector<uint16_t> melody_steps;
|
|
void sendToApp(String jsonMessage);
|
|
|
|
void PublishMqtt(const char * data);
|
|
bool addMelody(JsonVariant doc);
|
|
|
|
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
|
|
std::string uid = "x"; // The UID of the melody, from Firestore
|
|
std::string url = "-"; // The URL of the binary, from Firestore
|
|
uint16_t noteAssignments[16] = {0}; // The list of Note Assignments for the melody
|
|
uint16_t speed = 500; // Time to wait per beat. (In Miliseconds)
|
|
uint32_t segment_duration = 15000; // Duration of the playback per segment (per "loop")
|
|
uint32_t pause_duration = 0; // Duration of the pauses between segments (loops)
|
|
uint32_t total_duration = 0; // The Total Run Duration of the program. Including all loops
|
|
uint64_t segmentCmpltTime = 0; // The time of completion of the last Segment
|
|
uint64_t segmentStartTime = 0; // The time-point the current segment started Playing
|
|
uint64_t startTime = 0; // The time-point the Melody started Playing
|
|
uint64_t pauseTime = 0; // The time-point the melody paused
|
|
bool continuous_loop = false; // Indicates if the Loop should Run 1-Take or Continuous with Intervals
|
|
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
|
|
bool hardStop = false; // Flags a hardstop, immediately.
|
|
|
|
|
|
void play() {
|
|
isPlaying = true;
|
|
hardStop = false;
|
|
startTime = segmentStartTime = millis();
|
|
LOG_DEBUG("Plbck: PLAY");
|
|
}
|
|
|
|
void forceStop() {
|
|
hardStop = true;
|
|
isPlaying = false;
|
|
LOG_DEBUG("Plbck: FORCE STOP");
|
|
}
|
|
|
|
void stop() {
|
|
hardStop = false;
|
|
isPlaying = false;
|
|
LOG_DEBUG("Plbck: STOP");
|
|
|
|
StaticJsonDocument<128> doc;
|
|
doc["status"] = "NOTIFY";
|
|
doc["type"] = "playback";
|
|
doc["payload"] = "stop";
|
|
|
|
String output;
|
|
serializeJson(doc, output);
|
|
sendToApp(output);
|
|
|
|
}
|
|
|
|
void pause() {
|
|
isPaused = true;
|
|
LOG_DEBUG("Plbck: PAUSE");
|
|
}
|
|
|
|
void unpause() {
|
|
isPaused = false;
|
|
segmentStartTime = millis();
|
|
LOG_DEBUG("Plbck: RESUME");
|
|
}
|
|
|
|
// Handles Incoming Commands to PLAY or STOP
|
|
void command(JsonVariant data, AsyncWebSocketClient *client = nullptr){
|
|
|
|
setMelodyAttributes(data);
|
|
loadMelodyInRAM(melody_steps);
|
|
|
|
String action = data["action"];
|
|
LOG_DEBUG("Incoming Command: %s", action);
|
|
|
|
// Play or Stop Logic
|
|
if (action == "play") {
|
|
play();
|
|
} else if (action == "stop") {
|
|
forceStop();
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// Prepare JSON response
|
|
StaticJsonDocument<128> response;
|
|
response["status"] = "OK";
|
|
response["type"] = action; // "play" or "stop"
|
|
response["payload"] = nullptr; // Use null in JSON
|
|
|
|
// Serialize JSON to char buffer
|
|
char jsonOut[256];
|
|
serializeJson(response, jsonOut);
|
|
|
|
// Send via MQTT
|
|
PublishMqtt(jsonOut);
|
|
|
|
// Optionally send via WebSocket
|
|
if (client) {
|
|
client->text(jsonOut);
|
|
}
|
|
|
|
}
|
|
|
|
// Sets incoming Attributes for the Melody, into the class' variables.
|
|
void setMelodyAttributes(JsonVariant doc){
|
|
|
|
if (doc.containsKey("name")) {
|
|
name = doc["name"].as<const char*>();
|
|
}
|
|
if (doc.containsKey("uid")) {
|
|
uid = doc["uid"].as<const char*>();
|
|
}
|
|
if (doc.containsKey("url")) {
|
|
url = doc["url"].as<const char*>();
|
|
}
|
|
if (doc.containsKey("speed")) {
|
|
speed = doc["speed"].as<uint16_t>();
|
|
}
|
|
if (doc.containsKey("note_assignments")) {
|
|
JsonArray noteArray = doc["note_assignments"];
|
|
size_t arraySize = min(noteArray.size(), (size_t)16);
|
|
for (size_t i = 0; i < arraySize; i++) {
|
|
noteAssignments[i] = noteArray[i];
|
|
}
|
|
}
|
|
if (doc.containsKey("segment_duration")) {
|
|
segment_duration = doc["segment_duration"].as<uint32_t>();
|
|
}
|
|
if (doc.containsKey("pause_duration")) {
|
|
pause_duration = doc["pause_duration"].as<uint32_t>();
|
|
}
|
|
if (doc.containsKey("total_duration")) {
|
|
total_duration = doc["total_duration"].as<uint32_t>();
|
|
}
|
|
if (doc.containsKey("continuous_loop")) {
|
|
continuous_loop = doc["continuous_loop"].as<bool>();
|
|
}
|
|
|
|
if (continuous_loop && total_duration == 0){
|
|
infinite_play = true;
|
|
}
|
|
|
|
if (!continuous_loop) {
|
|
total_duration = segment_duration;
|
|
}
|
|
|
|
// Print Just for Debugging Purposes
|
|
LOG_DEBUG("Set Melody Vars / Name: %s, UID: %s",
|
|
name.c_str(),
|
|
uid.c_str()
|
|
);
|
|
// Print Just for Debugging Purposes
|
|
LOG_DEBUG("URL: %s",
|
|
url.c_str()
|
|
);
|
|
// Print Just for Debugging Purposes
|
|
LOG_DEBUG("Speed: %d, Per Segment Duration: %lu, Pause Duration: %lu, Total Duration: %d, Continuous: %s, Infinite: %s",
|
|
speed,
|
|
segment_duration,
|
|
pause_duration,
|
|
total_duration,
|
|
continuous_loop ? "true" : "false",
|
|
infinite_play ? "true" : "false"
|
|
);
|
|
|
|
}
|
|
|
|
|
|
// Loads the Selected melody from a .bin file on SD into RAM
|
|
void loadMelodyInRAM(std::vector<uint16_t> &melody_steps) {
|
|
String filePath = "/melodies/" + String(uid.c_str());
|
|
|
|
File bin_file = SD.open(filePath.c_str(), FILE_READ);
|
|
if (!bin_file) {
|
|
LOG_ERROR("Failed to open file: %s", filePath.c_str());
|
|
LOG_ERROR("Check Servers for the File...");
|
|
StaticJsonDocument<128> doc;
|
|
doc["download_url"] = url;
|
|
doc["melodys_uid"] = uid;
|
|
if (!addMelody(doc)){
|
|
LOG_ERROR("Failed to Download File. Check Internet Connection");
|
|
return;
|
|
} else {
|
|
bin_file = SD.open(filePath.c_str(), FILE_READ);
|
|
}
|
|
}
|
|
|
|
size_t fileSize = bin_file.size();
|
|
if (fileSize % 2 != 0) {
|
|
LOG_ERROR("Invalid file size: %u (not a multiple of 2)", fileSize);
|
|
bin_file.close();
|
|
return;
|
|
}
|
|
|
|
melody_steps.resize(fileSize / 2);
|
|
|
|
for (size_t i = 0; i < melody_steps.size(); i++) {
|
|
uint8_t high = bin_file.read();
|
|
uint8_t low = bin_file.read();
|
|
melody_steps[i] = (high << 8) | low;
|
|
}
|
|
|
|
LOG_INFO("Melody Load Successful");
|
|
|
|
bin_file.close();
|
|
}
|
|
|
|
};
|