#include "my_buzzer.h" #include #include #include #include #include #include "JsonConstrain.h" #include "global.h" const char* DEFAULT_MELODY = "Ack:d=16,o=5,b=200:c,e,g"; // serial debugging enabled //#define ANY_RTTTL_INFO static const char* tag = "buzzer"; BUZZ_TUNE buzzTune[TUNE_MAX_COUNT]; int8_t buzzPin; int8_t buzzerChannel = -1; // Store the LEDC channel used by the buzzer // File-scope state for tune management (minimal overhead) static volatile int prev_tune = -1; static volatile bool buzzer_busy = false; // Optimized tone functions - minimal overhead, no logging void buzzerTone(uint8_t pin, unsigned int frequency, unsigned long duration) { if (buzzerChannel >= 0 && frequency > 0) { ledcWriteTone(buzzerChannel, frequency); } } void buzzerNoTone(uint8_t pin) { if (buzzerChannel >= 0) { ledcWrite(buzzerChannel, 0); } } void Init_Buzzer(int8_t pin, const char* configFile, int8_t channel) { buzzPin = pin; if(buzzPin >= 0){ pinMode(buzzPin, OUTPUT); // If channel is not provided, find an unused one if (channel < 0) { buzzerChannel = findUnusedLedcChannel(); if (buzzerChannel < 0) { ESP_LOGE(tag, "No available LEDC channel for buzzer"); return; } } else { // Use the provided channel and mark it as used extern bool markLedcChannelUsed(int ch); // Function from global.cpp if (markLedcChannelUsed(channel)) { buzzerChannel = channel; } else { ESP_LOGE(tag, "Requested channel %d is already in use, finding alternative", channel); buzzerChannel = findUnusedLedcChannel(); if (buzzerChannel < 0) { ESP_LOGE(tag, "No available LEDC channel for buzzer"); return; } } } // Set up the channel for the buzzer with proper audio frequency range ledcSetup(buzzerChannel, 2000, 10); // 2000 Hz base, 10-bit resolution for better frequency range ledcAttachPin(buzzPin, buzzerChannel); // Test the channel is working ESP_LOGI(tag, "Testing buzzer channel %d...", buzzerChannel); ledcWriteTone(buzzerChannel, 1000); // Test tone delay(100); ledcWrite(buzzerChannel, 0); // Stop test tone // Set custom tone functions for anyrtttl anyrtttl::setToneFunction(buzzerTone); anyrtttl::setNoToneFunction(buzzerNoTone); ESP_LOGI(tag, "Buzzer hardware initialized on pin %d using LEDC channel %d", buzzPin, buzzerChannel); } Buzzer_Load_Tunes(configFile); // Load Tunes ESP_LOGI(tag, "Buzzer initialized on pin %d, channel %d", buzzPin, buzzerChannel); } int8_t Buzzer_Get_Channel() { return buzzerChannel; } void Buzzer_Play_Tune(TUNE_TYPE tune, bool async, bool hasPriority) { // Fast path checks - minimal overhead if (buzzPin < 0 || buzzerChannel < 0) { ESP_LOGW(tag, "Buzzer not initialized - pin:%d, channel:%d", buzzPin, buzzerChannel); return; } if (tune < 0 || tune >= TUNE_MAX_COUNT) { ESP_LOGW(tag, "Invalid tune index: %d (max: %d)", (int)tune, TUNE_MAX_COUNT); return; } // Direct reference to avoid String copying const String& melody = buzzTune[tune].melody; if (melody.isEmpty()) { ESP_LOGW(tag, "Empty melody for tune %d", (int)tune); return; } ESP_LOGI(tag, "Playing tune %d: %s (async=%d, priority=%d)", (int)tune, melody.c_str(), async, hasPriority); // Simple atomic check for thread safety without mutex overhead if (buzzer_busy && !hasPriority) { ESP_LOGD(tag, "Buzzer busy, skipping tune %d", (int)tune); return; } // Async mode: minimal state management if (async) { bool playing = anyrtttl::nonblocking::isPlaying(); if (hasPriority && playing) { ESP_LOGD(tag, "Stopping current tune for priority tune %d", (int)tune); anyrtttl::nonblocking::stop(); playing = false; } if (!playing || prev_tune != tune) { ESP_LOGI(tag, "Starting async tune %d", (int)tune); anyrtttl::nonblocking::begin(buzzPin, melody.c_str()); prev_tune = tune; } anyrtttl::nonblocking::play(); return; } // Blocking mode: minimal cycles with yield for multitasking ESP_LOGI(tag, "Playing blocking tune %d, cycles=%d", (int)tune, buzzTune[tune].cycles); buzzer_busy = true; const int cycles = buzzTune[tune].cycles; const int pause_ms = buzzTune[tune].pause; for (int c = 0; c < cycles; ++c) { anyrtttl::blocking::play(buzzPin, melody.c_str()); if (pause_ms > 0 && c + 1 < cycles) { delay(pause_ms); } yield(); // Allow other tasks to run } prev_tune = tune; buzzer_busy = false; ESP_LOGI(tag, "Finished playing tune %d", (int)tune); } // Optimized beep function - minimal overhead void Buzzer_Beep(int mSecs, int freq) { if (buzzPin < 0 || buzzerChannel < 0) return; ledcWriteTone(buzzerChannel, freq); delay(mSecs); ledcWrite(buzzerChannel, 0); } // Test function to verify buzzer functionality void Buzzer_Test() { if (buzzPin < 0 || buzzerChannel < 0) { ESP_LOGE(tag, "Cannot test buzzer - not initialized"); return; } ESP_LOGI(tag, "Testing buzzer..."); // Test direct LEDC control ESP_LOGI(tag, "Test 1: Direct LEDC tones"); for (int freq = 500; freq <= 2000; freq += 500) { ESP_LOGI(tag, "Playing %d Hz", freq); ledcWriteTone(buzzerChannel, freq); delay(200); ledcWrite(buzzerChannel, 0); delay(100); } // Test custom tone functions ESP_LOGI(tag, "Test 2: Custom tone functions"); buzzerTone(buzzPin, 1000, 500); delay(500); buzzerNoTone(buzzPin); // Test anyrtttl with a simple melody ESP_LOGI(tag, "Test 3: anyrtttl blocking play"); anyrtttl::blocking::play(buzzPin, DEFAULT_MELODY); ESP_LOGI(tag, "Buzzer test complete"); } // Optimized tune loading - minimal memory allocation void Buzzer_Load_Tunes(const char* tunesPath){ ESP_LOGI(tag, "Loading tunes from: %s", tunesPath); File file = LittleFS.open(tunesPath); if (!file) { ESP_LOGW(tag, "Could not open %s, using default tune", tunesPath); // Set default tune only at index 0 buzzTune[0].cycles = 1; buzzTune[0].pause = 0; buzzTune[0].melody = DEFAULT_MELODY; ESP_LOGI(tag, "Loaded default tune at index 0: %s", DEFAULT_MELODY); return; } // Use smaller JSON document for memory efficiency JsonDocument doc; DeserializationError error = deserializeJson(doc, file); file.close(); if(error){ ESP_LOGE(tag, "JSON parse error: %s", error.c_str()); // Set default tune on error buzzTune[0].cycles = 1; buzzTune[0].pause = 0; buzzTune[0].melody = DEFAULT_MELODY; ESP_LOGI(tag, "Loaded default tune due to JSON error: %s", DEFAULT_MELODY); return; } JsonArray tuneJsonArray = doc["tunes"]; if(!tuneJsonArray.isNull()){ int tuneIndex = 0; for(JsonObject obj : tuneJsonArray){ if(tuneIndex >= TUNE_MAX_COUNT) break; buzzTune[tuneIndex].cycles = jsonConstrain(tag, obj, "cycles", 1, 100, 1); buzzTune[tuneIndex].pause = jsonConstrain(tag, obj, "pause", 0, 100, 0); buzzTune[tuneIndex].melody = jsonConstrainString(tag, obj, "tune", DEFAULT_MELODY); ESP_LOGI(tag, "Loaded tune %d: cycles=%d, pause=%d, melody=%.40s...", tuneIndex, buzzTune[tuneIndex].cycles, buzzTune[tuneIndex].pause, buzzTune[tuneIndex].melody.c_str()); tuneIndex++; } ESP_LOGI(tag, "Successfully loaded %d tunes", tuneIndex); } else { ESP_LOGW(tag, "No 'tunes' array found in JSON"); // Set default tune if no tunes array found buzzTune[0].cycles = 1; buzzTune[0].pause = 0; buzzTune[0].melody = DEFAULT_MELODY; ESP_LOGI(tag, "Loaded default tune: %s", DEFAULT_MELODY); } }