boothifier/temporary/my_buzzer.cpp
2025-09-07 23:38:56 -07:00

249 lines
7.6 KiB
C++

#include "my_buzzer.h"
#include <FS.h>
#include <LittleFS.h>
#include <anyrtttl.h>
#include <binrtttl.h>
#include <pitches.h>
#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<int>(tag, obj, "cycles", 1, 100, 1);
buzzTune[tuneIndex].pause = jsonConstrain<int>(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);
}
}