Files
project-vesper/vesper/bellEngine.hpp
bonamin 101f9e7135 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
2025-09-05 19:27:13 +03:00

166 lines
6.0 KiB
C++

// MELODY PLAYBACK WILL BE HANDLED HERE
#include <vector>
extern Player player;
// Define a structure to track active solenoids
struct ActiveRelay {
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
};
// 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);
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 (;;) {
// Playback until stopped (Completes AT LEAST 1 full loop)
loop_playback(melody_steps);
/*
UBaseType_t highWaterMark = uxTaskGetStackHighWaterMark(NULL);
Serial.print("Stack high water mark: ");
Serial.println(highWaterMark);
*/
}
}
// Task to deactivate relays dynamically after set timers
void relayControlTask(void *param) {
while (true) {
uint64_t now = millis();
// Iterate through active relays and deactivate those whose duration has elapsed
for (auto it = activeRelays.begin(); it != activeRelays.end();) {
if (now - it->activationTime >= it->duration) {
relays.digitalWrite(it->relayIndex, HIGH); // Deactivate the relay
it = activeRelays.erase(it); // Remove from the active list
} else {
++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){
LOG_DEBUG("(BellEngine) Single Loop Starting.");
// iterate through the beats and call the bell mechanism on each beat
for (uint16_t note : melody_steps) {
if (player.hardStop) return;
itsHammerTime(note);
int tempo = player.speed;
vTaskDelay(pdMS_TO_TICKS(tempo));
}
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();
// 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
// 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
}