MAJOR update. More like a Backup before things get Crazy

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
This commit is contained in:
2025-09-05 19:27:13 +03:00
parent c1fa1d5e57
commit 101f9e7135
20 changed files with 10746 additions and 9766 deletions

View File

@@ -1,21 +1,27 @@
// MELODY PLAYBACK WILL BE HANDLED HERE
#include <vector>
extern Player player;
// Define a structure to track active solenoids
struct ActiveRelay {
uint8_t relayIndex; // Index of the relay
uint8_t relayIndex; // Physical relay index (0-15)
uint8_t bellIndex; // Bell index for duration lookup
uint64_t activationTime; // Activation start time
uint16_t duration; // Duration for which it should remain active
};
// Array of durations for each relay (configure remotely)
uint16_t relayDurations[16] = {90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90};
// Duration per BELL (not per relay output)
uint16_t bellDurations[16] = {90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90};
// Level 1: Bell to Physical Output mapping (bell index -> relay index)
// bellOutputs[0] = which relay controls Bell #0, etc.
uint16_t bellOutputs[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // 0-based indexing
// Vector to track active solenoids
std::vector<ActiveRelay> activeRelays;
// Locks for Writing the Counters
portMUX_TYPE mySpinlock = portMUX_INITIALIZER_UNLOCKED;
void loop_playback(std::vector<uint16_t> &melody_steps);
void bellEngine(void *parameter);
@@ -23,6 +29,7 @@ void relayControlTask(void *param);
void itsHammerTime(uint16_t note);
void turnOffRelays(uint64_t now);
// Main Bell Engine. Activates Relays on the exact timing required.
void bellEngine(void *parameter) {
// SETUP TASK
for (;;) {
@@ -36,7 +43,7 @@ void bellEngine(void *parameter) {
}
}
// Task to deactivate relays dynamically
// Task to deactivate relays dynamically after set timers
void relayControlTask(void *param) {
while (true) {
uint64_t now = millis();
@@ -49,14 +56,15 @@ void relayControlTask(void *param) {
++it; // Move to the next relay
}
}
vTaskDelay(pdMS_TO_TICKS(10)); // Check every 10ms
}
}
// Function to wait for tempo, then loop to the next beat.
void loop_playback(std::vector<uint16_t> &melody_steps) {
while(player.isPlaying && !player.isPaused){
while(player.isPlaying && !player.isPaused){
LOG_DEBUG("(BellEngine) Single Loop Starting.");
// iterate through the beats and call the bell mechanism on each beat
for (uint16_t note : melody_steps) {
@@ -66,21 +74,92 @@ void loop_playback(std::vector<uint16_t> &melody_steps) {
vTaskDelay(pdMS_TO_TICKS(tempo));
}
LOG_DEBUG("Single Loop Over.");
//if (!player.isPlaying) break; // Stop playback only after completing the loop
player.segmentCmpltTime = millis();
LOG_DEBUG("(BellEngine) Single Full Loop Complete");
}
}
// Function to activate relays for a specific note
void itsHammerTime(uint16_t note) {
uint64_t now = millis();
for (uint8_t i = 0; i < 16; i++) {
if (note & (1 << i)) { // Check if this relay needs to activate
relays.digitalWrite(i, LOW); // Activate the relay
// First, determine which bells should ring based on the note pattern
for (uint8_t noteIndex = 0; noteIndex < 16; noteIndex++) {
if (note & (1 << noteIndex)) { // This note should be played
// Add to the activeRelays list
activeRelays.push_back({i, now, relayDurations[i]});
// Level 2: Map note to bell using noteAssignments
uint8_t bellIndex = player.noteAssignments[noteIndex];
// Skip if no bell assigned to this note (0 means no assignment)
if (bellIndex == 0) continue;
// Convert to 0-based indexing (noteAssignments uses 1-based)
bellIndex = bellIndex - 1;
// Level 1: Map bell to physical relay output
uint8_t relayIndex = bellOutputs[bellIndex];
// Activate the relay
relays.digitalWrite(relayIndex, LOW);
// Add to the activeRelays list with bell-specific duration
activeRelays.push_back({
relayIndex,
bellIndex,
now,
bellDurations[bellIndex]
});
// Write ring to counter (count per bell, not per relay)
portENTER_CRITICAL(&mySpinlock);
strikeCounters[bellIndex]++; // Count strikes per bell
bellLoad[bellIndex]++; // Load per bell
coolingActive = true;
portEXIT_CRITICAL(&mySpinlock);
}
}
}
// Helper function to update bell-to-output mapping via JSON
void updateBellOutputs(JsonVariant doc) {
for (uint8_t i = 0; i < 16; i++) {
String key = String("b") + (i + 1);
if (doc.containsKey(key)) {
bellOutputs[i] = doc[key].as<uint16_t>()-1; // -1 to convert to 0 based indexing
LOG_DEBUG("Bell %d Output set to Relay #%d", i + 1, bellOutputs[i]+1);
} else {
LOG_DEBUG("Bell %d not found in JSON payload. Keeping previous Output: Relay #%d", i + 1, bellOutputs[i]+1);
}
}
LOG_INFO("Updated Relay Outputs.")
StaticJsonDocument<128> response;
response["status"] = "OK";
response["type"] = "set_relay_outputs";
char jsonOut[128]; // Create Char Buffer
serializeJson(response, jsonOut); // Serialize to Buffer
replyOnWebSocket(activeClient, jsonOut); // Reply on WebSocket
}
// Sets Incoming Relay Durations to RAM and then call funtion to save them to file.
void updateRelayTimings(JsonVariant doc) {
// Iterate through the relays in the JSON payload
for (uint8_t i = 0; i < 16; i++) {
String key = String("b") + (i + 1); // Generate "b1", "b2", ...
if (doc.containsKey(key)) {
bellDurations[i] = doc[key].as<uint16_t>();
LOG_DEBUG("Relay %d duration set to %d ms", i + 1, bellDurations[i]);
} else {
LOG_DEBUG("Relay %d not found in JSON payload. Keeping previous duration: %d ms", i + 1, bellDurations[i]);
}
}
saveRelayTimings();
LOG_INFO("Updated Relay Timings.")
StaticJsonDocument<128> response;
response["status"] = "OK";
response["type"] = "set_relay_timings";
char jsonOut[128]; // Create Char Buffer
serializeJson(response, jsonOut); // Serialize to Buffer
replyOnWebSocket(activeClient, jsonOut); // Reply on WebSocket
}