Fixed OTA problems, Clock Alerts and MQTT Logs. V151
This commit is contained in:
@@ -1249,6 +1249,8 @@ void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const Messa
|
|||||||
contents["checksum"].as<String>() : "";
|
contents["checksum"].as<String>() : "";
|
||||||
size_t fileSize = contents.containsKey("file_size") ?
|
size_t fileSize = contents.containsKey("file_size") ?
|
||||||
contents["file_size"].as<size_t>() : 0;
|
contents["file_size"].as<size_t>() : 0;
|
||||||
|
uint16_t version = contents.containsKey("version") ?
|
||||||
|
contents["version"].as<uint16_t>() : 0;
|
||||||
|
|
||||||
// Check if player is active
|
// Check if player is active
|
||||||
if (_player && _player->isCurrentlyPlaying()) {
|
if (_player && _player->isCurrentlyPlaying()) {
|
||||||
@@ -1257,10 +1259,11 @@ void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const Messa
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("Custom update: URL=%s, Checksum=%s, Size=%u",
|
LOG_INFO("Custom update: URL=%s, Checksum=%s, Size=%u, Version=%u",
|
||||||
firmwareUrl.c_str(),
|
firmwareUrl.c_str(),
|
||||||
checksum.isEmpty() ? "none" : checksum.c_str(),
|
checksum.isEmpty() ? "none" : checksum.c_str(),
|
||||||
fileSize);
|
fileSize,
|
||||||
|
version);
|
||||||
|
|
||||||
sendSuccessResponse("custom_update",
|
sendSuccessResponse("custom_update",
|
||||||
"Starting custom OTA update. Device may reboot.", context);
|
"Starting custom OTA update. Device may reboot.", context);
|
||||||
@@ -1269,7 +1272,7 @@ void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const Messa
|
|||||||
delay(1000);
|
delay(1000);
|
||||||
|
|
||||||
// Perform the custom update
|
// Perform the custom update
|
||||||
bool result = _otaManager.performCustomUpdate(firmwareUrl, checksum, fileSize);
|
bool result = _otaManager.performCustomUpdate(firmwareUrl, checksum, fileSize, version);
|
||||||
|
|
||||||
// Note: If update succeeds, device will reboot and this won't be reached
|
// Note: If update succeeds, device will reboot and this won't be reached
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
|||||||
@@ -1146,10 +1146,17 @@ String ConfigManager::getAllSettingsAsJson() const {
|
|||||||
time["daylightOffsetSec"] = timeConfig.daylightOffsetSec;
|
time["daylightOffsetSec"] = timeConfig.daylightOffsetSec;
|
||||||
|
|
||||||
// Bell durations (relay timings)
|
// Bell durations (relay timings)
|
||||||
JsonObject bells = doc.createNestedObject("bells");
|
JsonObject bellDurations = doc.createNestedObject("bellDurations");
|
||||||
for (uint8_t i = 0; i < 16; i++) {
|
for (uint8_t i = 0; i < 16; i++) {
|
||||||
String key = String("b") + (i + 1);
|
String key = String("b") + (i + 1);
|
||||||
bells[key] = bellConfig.durations[i];
|
bellDurations[key] = bellConfig.durations[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bell outputs (physical output mapping)
|
||||||
|
JsonObject bellOutputs = doc.createNestedObject("bellOutputs");
|
||||||
|
for (uint8_t i = 0; i < 16; i++) {
|
||||||
|
String key = String("b") + (i + 1);
|
||||||
|
bellOutputs[key] = bellConfig.outputs[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clock configuration
|
// Clock configuration
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ bool Logging::isLevelEnabled(LogLevel level) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Logging::error(const char* format, ...) {
|
void Logging::error(const char* format, ...) {
|
||||||
if (!isLevelEnabled(ERROR)) return;
|
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
log(ERROR, "🔴 EROR", format, args);
|
log(ERROR, "🔴 EROR", format, args);
|
||||||
@@ -44,8 +42,6 @@ void Logging::error(const char* format, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Logging::warning(const char* format, ...) {
|
void Logging::warning(const char* format, ...) {
|
||||||
if (!isLevelEnabled(WARNING)) return;
|
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
log(WARNING, "🟡 WARN", format, args);
|
log(WARNING, "🟡 WARN", format, args);
|
||||||
@@ -53,8 +49,6 @@ void Logging::warning(const char* format, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Logging::info(const char* format, ...) {
|
void Logging::info(const char* format, ...) {
|
||||||
if (!isLevelEnabled(INFO)) return;
|
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
log(INFO, "🟢 INFO", format, args);
|
log(INFO, "🟢 INFO", format, args);
|
||||||
@@ -62,8 +56,6 @@ void Logging::info(const char* format, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Logging::debug(const char* format, ...) {
|
void Logging::debug(const char* format, ...) {
|
||||||
if (!isLevelEnabled(DEBUG)) return;
|
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
log(DEBUG, "🐞 DEBG", format, args);
|
log(DEBUG, "🐞 DEBG", format, args);
|
||||||
@@ -71,8 +63,6 @@ void Logging::debug(const char* format, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Logging::verbose(const char* format, ...) {
|
void Logging::verbose(const char* format, ...) {
|
||||||
if (!isLevelEnabled(VERBOSE)) return;
|
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
log(VERBOSE, "🧾 VERB", format, args);
|
log(VERBOSE, "🧾 VERB", format, args);
|
||||||
@@ -80,19 +70,33 @@ void Logging::verbose(const char* format, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Logging::log(LogLevel level, const char* levelStr, const char* format, va_list args) {
|
void Logging::log(LogLevel level, const char* levelStr, const char* format, va_list args) {
|
||||||
// Print the formatted message
|
// Check if ANY output needs this log level
|
||||||
|
bool serialEnabled = (currentLevel >= level);
|
||||||
|
bool mqttEnabled = (mqttLogLevel >= level && mqttPublishCallback);
|
||||||
|
// bool sdEnabled = (sdLogLevel >= level && sdLogCallback); // Future: SD logging
|
||||||
|
|
||||||
|
// Early exit if no outputs need this message
|
||||||
|
if (!serialEnabled && !mqttEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the message once (only if at least one output needs it)
|
||||||
char buffer[512];
|
char buffer[512];
|
||||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||||
|
|
||||||
// Serial output
|
// Serial output (independent check)
|
||||||
|
if (serialEnabled) {
|
||||||
Serial.printf("[%s] ", levelStr);
|
Serial.printf("[%s] ", levelStr);
|
||||||
Serial.print(buffer);
|
Serial.print(buffer);
|
||||||
Serial.println();
|
Serial.println();
|
||||||
|
}
|
||||||
|
|
||||||
// MQTT output (if enabled and callback is set)
|
// MQTT output (independent check)
|
||||||
if (mqttLogLevel >= level && mqttPublishCallback) {
|
if (mqttEnabled) {
|
||||||
publishToMqtt(level, levelStr, buffer);
|
publishToMqtt(level, levelStr, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Future: SD logging would go here with its own independent check
|
||||||
}
|
}
|
||||||
|
|
||||||
void Logging::publishToMqtt(LogLevel level, const char* levelStr, const char* message) {
|
void Logging::publishToMqtt(LogLevel level, const char* levelStr, const char* message) {
|
||||||
|
|||||||
@@ -493,9 +493,57 @@ bool OTAManager::downloadDirectToFlash(const String& url, size_t expectedSize) {
|
|||||||
LOG_INFO("OTA: Checksum validation will be performed by ESP32 bootloader");
|
LOG_INFO("OTA: Checksum validation will be performed by ESP32 bootloader");
|
||||||
setStatus(Status::INSTALLING);
|
setStatus(Status::INSTALLING);
|
||||||
|
|
||||||
// Stream directly to flash
|
// Stream directly to flash with periodic watchdog feeding
|
||||||
WiFiClient* stream = http.getStreamPtr();
|
WiFiClient* stream = http.getStreamPtr();
|
||||||
size_t written = Update.writeStream(*stream);
|
uint8_t buffer[4096]; // 4KB buffer for efficient transfer
|
||||||
|
size_t written = 0;
|
||||||
|
size_t lastLoggedPercent = 0;
|
||||||
|
unsigned long lastWatchdogReset = millis();
|
||||||
|
|
||||||
|
while (http.connected() && written < (size_t)contentLength) {
|
||||||
|
size_t available = stream->available();
|
||||||
|
if (available) {
|
||||||
|
size_t toRead = min(available, sizeof(buffer));
|
||||||
|
size_t bytesRead = stream->readBytes(buffer, toRead);
|
||||||
|
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
// Write to flash
|
||||||
|
size_t bytesWritten = Update.write(buffer, bytesRead);
|
||||||
|
|
||||||
|
if (bytesWritten != bytesRead) {
|
||||||
|
LOG_ERROR("OTA: Flash write failed at offset %u (%u/%u bytes written)",
|
||||||
|
written, bytesWritten, bytesRead);
|
||||||
|
http.end();
|
||||||
|
|
||||||
|
// Resume systems
|
||||||
|
if (_timeKeeper) _timeKeeper->resumeClockUpdates();
|
||||||
|
if (_telemetry) _telemetry->resume();
|
||||||
|
|
||||||
|
setStatus(Status::FAILED, ErrorCode::WRITE_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
written += bytesWritten;
|
||||||
|
|
||||||
|
// Log progress every 20%
|
||||||
|
size_t currentPercent = (written * 100) / contentLength;
|
||||||
|
if (currentPercent >= lastLoggedPercent + 20) {
|
||||||
|
LOG_INFO("OTA: Flash write progress: %u%% (%u/%u bytes)",
|
||||||
|
currentPercent, written, contentLength);
|
||||||
|
lastLoggedPercent = currentPercent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed watchdog every 500ms to prevent timeout
|
||||||
|
if (millis() - lastWatchdogReset > 500) {
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
lastWatchdogReset = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small yield to prevent tight loop
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
http.end();
|
http.end();
|
||||||
|
|
||||||
@@ -1038,7 +1086,7 @@ bool OTAManager::performManualUpdate(const String& channel) {
|
|||||||
// CUSTOM FIRMWARE UPDATE
|
// CUSTOM FIRMWARE UPDATE
|
||||||
// ════════════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
bool OTAManager::performCustomUpdate(const String& firmwareUrl, const String& checksum, size_t fileSize) {
|
bool OTAManager::performCustomUpdate(const String& firmwareUrl, const String& checksum, size_t fileSize, uint16_t version) {
|
||||||
if (_status != Status::IDLE) {
|
if (_status != Status::IDLE) {
|
||||||
LOG_WARNING("OTA update already in progress");
|
LOG_WARNING("OTA update already in progress");
|
||||||
return false;
|
return false;
|
||||||
@@ -1059,12 +1107,25 @@ bool OTAManager::performCustomUpdate(const String& firmwareUrl, const String& ch
|
|||||||
LOG_INFO(" Checksum: %s (NOTE: ESP32 will validate after flash)", checksum.c_str());
|
LOG_INFO(" Checksum: %s (NOTE: ESP32 will validate after flash)", checksum.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version > 0) {
|
||||||
|
LOG_INFO(" Target Version: %u", version);
|
||||||
|
}
|
||||||
|
|
||||||
setStatus(Status::DOWNLOADING);
|
setStatus(Status::DOWNLOADING);
|
||||||
|
|
||||||
// Download directly to flash
|
// Download directly to flash
|
||||||
bool result = downloadDirectToFlash(firmwareUrl, fileSize);
|
bool result = downloadDirectToFlash(firmwareUrl, fileSize);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
// Update version in config if provided
|
||||||
|
if (version > 0) {
|
||||||
|
_configManager.setFwVersion(String(version));
|
||||||
|
_configManager.saveDeviceConfig();
|
||||||
|
LOG_INFO("✅ Custom firmware version %u saved to NVS", version);
|
||||||
|
} else {
|
||||||
|
LOG_WARNING("⚠️ No version provided - NVS version unchanged");
|
||||||
|
}
|
||||||
|
|
||||||
LOG_INFO("🚀 Custom firmware installed - device will reboot");
|
LOG_INFO("🚀 Custom firmware installed - device will reboot");
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR("❌ Custom firmware installation failed");
|
LOG_ERROR("❌ Custom firmware installation failed");
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public:
|
|||||||
void checkFirmwareUpdateFromSD(); // Check SD for firmware update
|
void checkFirmwareUpdateFromSD(); // Check SD for firmware update
|
||||||
bool performManualUpdate(); // Manual update triggered by app
|
bool performManualUpdate(); // Manual update triggered by app
|
||||||
bool performManualUpdate(const String& channel); // Manual update from specific channel
|
bool performManualUpdate(const String& channel); // Manual update from specific channel
|
||||||
bool performCustomUpdate(const String& firmwareUrl, const String& checksum = "", size_t fileSize = 0); // Custom firmware update
|
bool performCustomUpdate(const String& firmwareUrl, const String& checksum = "", size_t fileSize = 0, uint16_t version = 0); // Custom firmware update
|
||||||
|
|
||||||
// Hardware identification
|
// Hardware identification
|
||||||
String getHardwareVariant() const;
|
String getHardwareVariant() const;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "../Communication/CommunicationRouter/CommunicationRouter.hpp"
|
#include "../Communication/CommunicationRouter/CommunicationRouter.hpp"
|
||||||
#include "../BellEngine/BellEngine.hpp"
|
#include "../BellEngine/BellEngine.hpp"
|
||||||
#include "../Telemetry/Telemetry.hpp"
|
#include "../Telemetry/Telemetry.hpp"
|
||||||
|
#include "../TimeKeeper/TimeKeeper.hpp" // 🔥 Include for Timekeeper class definition
|
||||||
#include "../BuiltInMelodies/BuiltInMelodies.hpp"
|
#include "../BuiltInMelodies/BuiltInMelodies.hpp"
|
||||||
|
|
||||||
// Note: Removed global melody_steps dependency for cleaner architecture
|
// Note: Removed global melody_steps dependency for cleaner architecture
|
||||||
@@ -31,6 +32,7 @@ Player::Player(CommunicationRouter* comm, FileManager* fm)
|
|||||||
, _fileManager(fm)
|
, _fileManager(fm)
|
||||||
, _bellEngine(nullptr)
|
, _bellEngine(nullptr)
|
||||||
, _telemetry(nullptr)
|
, _telemetry(nullptr)
|
||||||
|
, _timekeeper(nullptr)
|
||||||
, _durationTimerHandle(NULL) {
|
, _durationTimerHandle(NULL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ Player::Player()
|
|||||||
, _fileManager(nullptr)
|
, _fileManager(nullptr)
|
||||||
, _bellEngine(nullptr)
|
, _bellEngine(nullptr)
|
||||||
, _telemetry(nullptr)
|
, _telemetry(nullptr)
|
||||||
|
, _timekeeper(nullptr)
|
||||||
, _durationTimerHandle(NULL) {
|
, _durationTimerHandle(NULL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +110,12 @@ void Player::play() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔥 CRITICAL: Interrupt any active clock alerts - user playback has priority!
|
||||||
|
if (_timekeeper) {
|
||||||
|
_timekeeper->interruptActiveAlert();
|
||||||
|
LOG_DEBUG("Player: Interrupted any active clock alerts");
|
||||||
|
}
|
||||||
|
|
||||||
if (_bellEngine) {
|
if (_bellEngine) {
|
||||||
_bellEngine->setMelodyData(_melodySteps);
|
_bellEngine->setMelodyData(_melodySteps);
|
||||||
_bellEngine->start();
|
_bellEngine->start();
|
||||||
|
|||||||
@@ -133,6 +133,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
void setTelemetry(Telemetry* telemetry) { _telemetry = telemetry; }
|
void setTelemetry(Telemetry* telemetry) { _telemetry = telemetry; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set Timekeeper reference for alert coordination
|
||||||
|
* @param timekeeper Pointer to Timekeeper instance
|
||||||
|
*/
|
||||||
|
void setTimekeeper(class Timekeeper* timekeeper) { _timekeeper = timekeeper; }
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
// MELODY METADATA - Public access for compatibility
|
// MELODY METADATA - Public access for compatibility
|
||||||
// ═══════════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -249,6 +255,7 @@ private:
|
|||||||
FileManager* _fileManager; // 📁 File operations reference
|
FileManager* _fileManager; // 📁 File operations reference
|
||||||
BellEngine* _bellEngine; // 🔥 High-precision timing engine reference
|
BellEngine* _bellEngine; // 🔥 High-precision timing engine reference
|
||||||
Telemetry* _telemetry; // 📄 Telemetry system reference
|
Telemetry* _telemetry; // 📄 Telemetry system reference
|
||||||
|
class Timekeeper* _timekeeper; // ⏰ Timekeeper reference for alert coordination
|
||||||
|
|
||||||
std::vector<uint16_t> _melodySteps; // 🎵 Melody data owned by Player
|
std::vector<uint16_t> _melodySteps; // 🎵 Melody data owned by Player
|
||||||
TimerHandle_t _durationTimerHandle = NULL; // ⏱️ FreeRTOS timer (saves 4KB vs task!)
|
TimerHandle_t _durationTimerHandle = NULL; // ⏱️ FreeRTOS timer (saves 4KB vs task!)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "../OutputManager/OutputManager.hpp"
|
#include "../OutputManager/OutputManager.hpp"
|
||||||
#include "../ConfigManager/ConfigManager.hpp"
|
#include "../ConfigManager/ConfigManager.hpp"
|
||||||
#include "../Networking/Networking.hpp"
|
#include "../Networking/Networking.hpp"
|
||||||
|
#include "../Player/Player.hpp" // 🔥 Include for Player class definition
|
||||||
#include "SD.h"
|
#include "SD.h"
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
@@ -47,6 +48,19 @@ void Timekeeper::setNetworking(Networking* networking) {
|
|||||||
LOG_INFO("Timekeeper connected to Networking");
|
LOG_INFO("Timekeeper connected to Networking");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Timekeeper::setPlayer(Player* player) {
|
||||||
|
_player = player;
|
||||||
|
LOG_INFO("Timekeeper connected to Player for playback coordination");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timekeeper::interruptActiveAlert() {
|
||||||
|
if (alertInProgress.load()) {
|
||||||
|
LOG_INFO("⚡ ALERT INTERRUPTED by user playback - marking as complete");
|
||||||
|
alertInProgress.store(false);
|
||||||
|
// Alert will stop naturally on next check in fireAlertBell loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Timekeeper::setRelayWriteFunction(void (*func)(int, int)) {
|
void Timekeeper::setRelayWriteFunction(void (*func)(int, int)) {
|
||||||
relayWriteFunc = func;
|
relayWriteFunc = func;
|
||||||
LOG_WARNING("Using LEGACY relay function - consider upgrading to OutputManager");
|
LOG_WARNING("Using LEGACY relay function - consider upgrading to OutputManager");
|
||||||
@@ -538,14 +552,34 @@ void Timekeeper::checkClockAlerts() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔥 CRITICAL: Check if Player is busy - if so, SKIP alert completely
|
||||||
|
if (_player && _player->isPlaying) {
|
||||||
|
// Player is active (playing, paused, stopping, etc.) - skip alert entirely
|
||||||
|
// Mark this alert as processed to prevent it from firing when playback ends
|
||||||
|
DateTime now = rtc.now();
|
||||||
|
int currentMinute = now.minute();
|
||||||
|
|
||||||
|
if (currentMinute == 0) {
|
||||||
|
lastHour = now.hour(); // Mark hour as processed
|
||||||
|
} else if (currentMinute == 30) {
|
||||||
|
lastMinute = 30; // Mark half-hour as processed
|
||||||
|
} else if (currentMinute == 15 || currentMinute == 45) {
|
||||||
|
lastMinute = currentMinute; // Mark quarter-hour as processed
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("⏭️ SKIPPING clock alert - Player is busy (playing/paused)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get current time
|
// Get current time
|
||||||
DateTime now = rtc.now();
|
DateTime now = rtc.now();
|
||||||
int currentHour = now.hour();
|
int currentHour = now.hour();
|
||||||
int currentMinute = now.minute();
|
int currentMinute = now.minute();
|
||||||
int currentSecond = now.second();
|
int currentSecond = now.second();
|
||||||
|
|
||||||
// Only trigger alerts on exact seconds (0-2) to avoid multiple triggers
|
// Only trigger alerts in first 30 seconds of the minute
|
||||||
if (currentSecond > 2) {
|
// The lastHour/lastMinute tracking prevents duplicate triggers
|
||||||
|
if (currentSecond > 30) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,7 +659,16 @@ void Timekeeper::fireAlertBell(uint8_t bellNumber, int count) {
|
|||||||
|
|
||||||
const auto& clockConfig = _configManager->getClockConfig();
|
const auto& clockConfig = _configManager->getClockConfig();
|
||||||
|
|
||||||
|
// Mark alert as in progress
|
||||||
|
alertInProgress.store(true);
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
|
// 🔥 Check for interruption by user playback
|
||||||
|
if (!alertInProgress.load()) {
|
||||||
|
LOG_INFO("⚡ Alert interrupted at ring %d/%d - stopping immediately", i + 1, count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get bell duration from bell configuration
|
// Get bell duration from bell configuration
|
||||||
uint16_t bellDuration = _configManager->getBellDuration(bellNumber);
|
uint16_t bellDuration = _configManager->getBellDuration(bellNumber);
|
||||||
|
|
||||||
@@ -640,6 +683,9 @@ void Timekeeper::fireAlertBell(uint8_t bellNumber, int count) {
|
|||||||
vTaskDelay(pdMS_TO_TICKS(clockConfig.alertRingInterval));
|
vTaskDelay(pdMS_TO_TICKS(clockConfig.alertRingInterval));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark alert as complete
|
||||||
|
alertInProgress.store(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Timekeeper::checkBacklightAutomation() {
|
void Timekeeper::checkBacklightAutomation() {
|
||||||
@@ -697,14 +743,22 @@ bool Timekeeper::isInSilencePeriod() {
|
|||||||
|
|
||||||
// Check daytime silence period
|
// Check daytime silence period
|
||||||
if (clockConfig.daytimeSilenceEnabled) {
|
if (clockConfig.daytimeSilenceEnabled) {
|
||||||
if (isTimeInRange(currentTime, clockConfig.daytimeSilenceOnTime, clockConfig.daytimeSilenceOffTime)) {
|
bool inDaytime = isTimeInRange(currentTime, clockConfig.daytimeSilenceOnTime, clockConfig.daytimeSilenceOffTime);
|
||||||
|
LOG_DEBUG("🔇 Daytime silence check: current=%s, range=%s-%s, inRange=%s",
|
||||||
|
currentTime.c_str(), clockConfig.daytimeSilenceOnTime.c_str(),
|
||||||
|
clockConfig.daytimeSilenceOffTime.c_str(), inDaytime ? "YES" : "NO");
|
||||||
|
if (inDaytime) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check nighttime silence period
|
// Check nighttime silence period
|
||||||
if (clockConfig.nighttimeSilenceEnabled) {
|
if (clockConfig.nighttimeSilenceEnabled) {
|
||||||
if (isTimeInRange(currentTime, clockConfig.nighttimeSilenceOnTime, clockConfig.nighttimeSilenceOffTime)) {
|
bool inNighttime = isTimeInRange(currentTime, clockConfig.nighttimeSilenceOnTime, clockConfig.nighttimeSilenceOffTime);
|
||||||
|
LOG_DEBUG("🌙 Nighttime silence check: current=%s, range=%s-%s, inRange=%s",
|
||||||
|
currentTime.c_str(), clockConfig.nighttimeSilenceOnTime.c_str(),
|
||||||
|
clockConfig.nighttimeSilenceOffTime.c_str(), inNighttime ? "YES" : "NO");
|
||||||
|
if (inNighttime) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <RTClib.h>
|
#include <RTClib.h>
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
@@ -61,6 +62,7 @@ private:
|
|||||||
// Alert management - new functionality
|
// Alert management - new functionality
|
||||||
int lastHour = -1; // Track last processed hour to avoid duplicate alerts
|
int lastHour = -1; // Track last processed hour to avoid duplicate alerts
|
||||||
int lastMinute = -1; // Track last processed minute for quarter/half alerts
|
int lastMinute = -1; // Track last processed minute for quarter/half alerts
|
||||||
|
std::atomic<bool> alertInProgress{false}; // Flag to track if alert is currently playing
|
||||||
|
|
||||||
// Backlight management - new functionality
|
// Backlight management - new functionality
|
||||||
bool backlightState = false; // Track current backlight state
|
bool backlightState = false; // Track current backlight state
|
||||||
@@ -69,6 +71,7 @@ private:
|
|||||||
OutputManager* _outputManager = nullptr;
|
OutputManager* _outputManager = nullptr;
|
||||||
ConfigManager* _configManager = nullptr;
|
ConfigManager* _configManager = nullptr;
|
||||||
Networking* _networking = nullptr;
|
Networking* _networking = nullptr;
|
||||||
|
class Player* _player = nullptr; // Reference to Player for playback status checks
|
||||||
|
|
||||||
// Legacy function pointer (DEPRECATED - will be removed)
|
// Legacy function pointer (DEPRECATED - will be removed)
|
||||||
void (*relayWriteFunc)(int relay, int state) = nullptr;
|
void (*relayWriteFunc)(int relay, int state) = nullptr;
|
||||||
@@ -84,12 +87,16 @@ public:
|
|||||||
void setOutputManager(OutputManager* outputManager);
|
void setOutputManager(OutputManager* outputManager);
|
||||||
void setConfigManager(ConfigManager* configManager);
|
void setConfigManager(ConfigManager* configManager);
|
||||||
void setNetworking(Networking* networking);
|
void setNetworking(Networking* networking);
|
||||||
|
void setPlayer(class Player* player); // Set Player reference for playback coordination
|
||||||
|
|
||||||
// Clock Updates Pause Functions
|
// Clock Updates Pause Functions
|
||||||
void pauseClockUpdates() { clockUpdatesPaused = true; }
|
void pauseClockUpdates() { clockUpdatesPaused = true; }
|
||||||
void resumeClockUpdates() { clockUpdatesPaused = false; }
|
void resumeClockUpdates() { clockUpdatesPaused = false; }
|
||||||
bool areClockUpdatesPaused() const { return clockUpdatesPaused; }
|
bool areClockUpdatesPaused() const { return clockUpdatesPaused; }
|
||||||
|
|
||||||
|
// Alert interruption - called by Player when starting playback
|
||||||
|
void interruptActiveAlert();
|
||||||
|
|
||||||
// Legacy interface (DEPRECATED - will be removed)
|
// Legacy interface (DEPRECATED - will be removed)
|
||||||
void setRelayWriteFunction(void (*func)(int, int));
|
void setRelayWriteFunction(void (*func)(int, int));
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
* 👨💻 AUTHOR: BellSystems bonamin
|
* 👨💻 AUTHOR: BellSystems bonamin
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define FW_VERSION "138"
|
#define FW_VERSION "151"
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -77,6 +77,8 @@
|
|||||||
* v1.3 (130) - Added Telemetry Reports to App, Various Playback Fixes
|
* v1.3 (130) - Added Telemetry Reports to App, Various Playback Fixes
|
||||||
* v137 - Made OTA and MQTT delays Async
|
* v137 - Made OTA and MQTT delays Async
|
||||||
* v138 - Removed Ethernet, added default WiFi creds (Mikrotik AP) and fixed various Clock issues
|
* v138 - Removed Ethernet, added default WiFi creds (Mikrotik AP) and fixed various Clock issues
|
||||||
|
* v140 - Changed FW Updates to Direct-to-Flash and added manual update functionality with version check
|
||||||
|
* v151 - Fixed Clock Alerts not running properly
|
||||||
* ═══════════════════════════════════════════════════════════════════════════════
|
* ═══════════════════════════════════════════════════════════════════════════════
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -315,6 +317,7 @@ void setup()
|
|||||||
timekeeper.setOutputManager(&outputManager);
|
timekeeper.setOutputManager(&outputManager);
|
||||||
timekeeper.setConfigManager(&configManager);
|
timekeeper.setConfigManager(&configManager);
|
||||||
timekeeper.setNetworking(&networking);
|
timekeeper.setNetworking(&networking);
|
||||||
|
timekeeper.setPlayer(&player); // 🔥 Connect for playback coordination
|
||||||
// Clock outputs now configured via ConfigManager/Communication commands
|
// Clock outputs now configured via ConfigManager/Communication commands
|
||||||
|
|
||||||
// Register TimeKeeper with health monitor
|
// Register TimeKeeper with health monitor
|
||||||
@@ -356,6 +359,7 @@ void setup()
|
|||||||
player.setDependencies(&communication, &fileManager);
|
player.setDependencies(&communication, &fileManager);
|
||||||
player.setBellEngine(&bellEngine); // Connect the beast!
|
player.setBellEngine(&bellEngine); // Connect the beast!
|
||||||
player.setTelemetry(&telemetry);
|
player.setTelemetry(&telemetry);
|
||||||
|
player.setTimekeeper(&timekeeper); // 🔥 Connect for alert coordination
|
||||||
|
|
||||||
// Register Communication with health monitor
|
// Register Communication with health monitor
|
||||||
healthMonitor.setCommunication(&communication);
|
healthMonitor.setCommunication(&communication);
|
||||||
|
|||||||
Reference in New Issue
Block a user