Build a Smart Cooker Whistle Counter Using ESP32

Published  September 16, 2026   0
 Cooker Whistle Counter using ESP32

Almost every kitchen has a pressure cooker, but we've all had trouble handling them. It’s too easy to turn on the stove, get distracted by work, TV, or housework, and forget to turn it off. If we miss a whistle count, food gets burned, utensils get spoiled, and there are chances of accidents in the kitchen. But then again, being in the kitchen and manually counting the whistles is a waste of time.    
    So to solve this problem, in this tutorial we are building a smart cooker whistle counter using ESP32. This device will listen for spikes in sound to automatically capture cooker whistles while we do something else. It connects to CircuitDigest Cloud, where we can configure our target whistle count through a custom dashboard, start or stop listening and trigger an instant notification on WhatsApp the moment the preset number of whistles countdown is completed. You can also check out similar ESP32 Projects and IoT projects done previously here at Circuit Digest.

How Does this Smart Cooker Whistle Monitoring Work?

The MAX4466 electret microphone module is the primary acoustic sensor that detects the sound waves from the pressure cooker and converts them to a varying analog voltage signal. The built-in op-amp conditions and amplifies these signals. The signals are biased around a reference point of 1.65V. The conditioned analog signal is input to an ADC pin on the ESP32.
The ESP32 reads incoming sound through its internal 12-bit Analog-to-Digital Converter (ADC), which converts the analog audio voltage into digital values ranging from 0 to 4095. Setting the ADC attenuation to ADC_11db allows the input pin to safely measure full voltage swings up to 3.3V without clipping.

To measure ambient noise accurately, the ESP32 listens to the microphone in 50-millisecond windows (SAMPLE_WINDOW). During each sample window, it tracks the highest and lowest voltage points to determine the peak-to-peak voltage wave using the formula below:
ΔV = Vmax - Vmin 
    Finally, the code converts this peak-to-peak voltage into a readable sound level in decibels (dB) using the formula below:
dB = 41.52 • log₁₀(ΔV) + 64.02

Calibration of MAX4466 Microphone Amplifier:

Calibrating the MAX4466 Microphone Amplifier is important because it will show a higher or lower value if not calibrated properly. To calibrate it, upload the code and adjust the physical trimmer potentiometer to set the ambient noise to 30 dB. Here is another Arduino Whistle Detector Switch using Sound Sensor project where the AC lamp is switched based on a high-frequency whistle.

Components Required

Below is the list of components required to build this project

S.NoComponentsSpecificationQuantity
1MicrocontrollerESP32 Dev Kit     1
  2Microphone AmplifierMAX4466    1

Circuit Diagram

    Below is the wiring diagram of the smart cooker whistle alert

Circuit Diagram of Cooker Whistle Counter using ESP32

Below Table is the wiring of the microphone module with the ESP32

Microphone Module PinESP32
OUTGPIO 34
GNDGND
VCC3.3V


Step-by-Step CircuitDigest Cloud Setup Guide

    A step-by-step guide for this smart cooker whistle counter is listed below.
Step 1⇒ Sign Up / Login
    Sign up or login into your account CircuitDigest Cloud by giving valid email and password.

Step 2⇒Adding New Device
    After logging in, click Dashboard at the top, click the device option on the left side of the website, click Add New Device and enter what you want for your project. For us, we have given it the Cooker Whistle Monitoring and Alerting System. You can give the name you want to display for this device and click the Add Device button in blue.

Step 3⇒ Adding New Variables
    After creating a new device, click Variables on the left side of the website. Now select the device you created from the drop-down option. Click the Add Variable option. You will see a pop-up in the name section. First, we will create a variable for the number of whistles. Give the name you want to display for the variable. We have named it NO OF WHISTLE. Select the key you want for this variable. We have selected analog-input-1, set the direction to bidirectional, and pressed Create Variable. The variable will be created. Do the same for 3 more variables.

Step 4⇒Variable List
    These 4 variable names allow us to choose the key we want: analog-input-1 for the NO OF WHISTLE, analog-input-2 for the SET, analog-input-3 for the WHISTLE COUNT, analog-input-4  for the LIVE DB.

Step 5⇒Adding Widget
    After adding the variable, click the dashboard on the left side of the website and select the device you created from the drop-down. First, we will create a widget for NO OF WHISTLE. Click Add Widget, then click SliderWidget. A menu bar will pop up. Select the device you created from the drop-down, and name it NO OF WHISTLE. Set the variable as analog-input-1and click Add Widget. Do the same for the other 3 widgets, but select the proper variable for the widget placed. Now the dashboard setup is over.

Step 6⇒ WhatsApp API Integration
    Click the Home tab at the top, scroll down, and you will see the WhatsApp notification; click it. There, you will see the link number option; click it, then enter your number. An OTP will be sent to your number; verify it, and then your WhatsApp API setup is complete. You can also check out a similar How to Build an AI-based Air Quality Monitoring using ESP32 project where the air quality is monitored on a TFT and also through the CircuitDigest Cloud dashboard, which was previously done here at Circuit Digest.

Program Workflow

Below is the program workflow of this smart cooker whistle counter

Upon powering on, the ESP32 performs System Initialization and System Setup. It sets up GPIO34 as an analog input, configures the ADC attenuation to ADC_11db, initializes Wi-Fi, and establishes a connection with CircuitDigest Cloud. 
In the main loop, the system performs Handling Inputs by executing two parallel tasks:
1. Microphone Module: The ESP32 continuously samples the MAX4466 microphone in 20 ms windows, calculates peak-to-peak voltage, and converts it into a logarithmic decibel level.
2. Cloud Dashboard Inputs: Through background callbacks (CDcloud.loop()), the ESP32 receives real-time updates from the dashboard slider (analog-input-1) to update targetWhistles and the toggle switch (analog-input-2) to turn active listening ON or OFF (isListening). 
    The system then evaluates the Alert Condition Met? decision block. When listening is active, if a sound spike exceeds WHISTLE_ON_DB (60 dB) for at least MIN_WHISTLE_MS (300 ms) and falls back below WHISTLE_OFF_DB (50 dB), the Process Whistle block increments whistleCount, pushes the updated value to the cloud dashboard (analog-input-3), and triggers an 800 ms debounce cooldown. If the whistle target has not been met, the program enters the Whistle Pending state, looping back to wait for the next event. 
    Once whistleCount equals or exceeds targetWhistles, the system transitions to Whistle Reached and executes an HTTP POST request to dispatch a WhatsApp notification to our phone. Finally, in the Finished State, the ESP32 automatically stops listening (isListening = false) and turns the dashboard switch back to OFF. You can also check out a similar How to Build a Smart ESP32 Noise Pollution Monitoring System with IoT project where the noise pollution is monitored through the CircuitDigest Cloud dashboard, which was previously done here at Circuit Digest.

Code Explanation

    The code is written in the Arduino IDE for a smart cooker whistle counter using an ESP32 microcontroller.

pinMode(AUDIO_PIN, INPUT);
analogSetAttenuation(ADC_11db);
CDcloud.subscribe(KEY_TARGET_SET, onTargetSlider);
CDcloud.subscribe(KEY_LISTEN_SW, onListenSwitch);
if (!CDcloud.begin(WIFI_SSID, WIFI_PASS, DEVICE_ID, CONNECTION_KEY, API_KEY)) { ESP.restart(); }
publishWithRetry(KEY_LISTEN_SW, 0.0f);

 Upon powering this automatic cooker counter, the ESP32 configures GPIO34 as an analog input and sets ADC attenuation to 11dB for sound reading. It subscribes to cloud keys for the target slider (KEY_TARGET_SET) and the switch (KEY_LISTEN_SW). Wi-Fi and CircuitDigest Cloud connections are initialized using CDcloud.begin() with user credentials. 

float readDBLevel() {
 // Samples MAX4466 over 20ms to measure peak-to-peak amplitude
 float voltage = (peakToPeak * V_REF) / ADC_RESOLUTION;
 float rawDb = (41.52 * log10(voltage)) + 64.02;
 return (rawDb < 30.0 || !lastReadingValid) ? 30.0 : rawDb;
}

    In this loop, the MAX4466 output is sampled across 20ms windows to find signal peak-to-peak amplitude. Peak voltage is calculated as (peakToPeak * V_REF) / ADC_RESOLUTION and mapped to logarithmic decibels. The calculated decibel value is clamped to 30.0 dB to normalise low-level background noise. Every second, the processed live dB level is published to the cloud dashboard (KEY_LIVE_DB) for real-time monitoring.

CDcloud.loop(); // Handlers update targetWhistles and toggle isListening
void onTargetSlider(float v) { targetWhistles = (int)v; }
void onListenSwitch(float v) { isListening = (bool)v; whistleCount = 0; }
if (WiFi.status() != WL_CONNECTED) ESP.restart();

    The background handler CDcloud.loop() processes incoming MQTT/HTTP telemetry packets continuously. Moving the dashboard slider triggers onTargetSlider() to update the active targetWhistles count. Toggling the dashboard switch calls onListenSwitch(), resetting whistleCount to 0 when turned on. If Wi-Fi drops, the system detects connection loss in loop() and calls ESP.restart() to auto-recover.

if (isListening && !inWhistle && db > WHISTLE_ON_DB && now > cooldownUntilMs) {
 inWhistle = true; whistleStartMs = now;
} else if (inWhistle && db < WHISTLE_OFF_DB) {
 inWhistle = false;
 if ((now - whistleStartMs) >= MIN_WHISTLE_MS) {
   whistleCount++;
   cooldownUntilMs = now + WHISTLE_COOLDOWN_MS; // WHISTLE_COOLDOWN_MS = 10000UL
   CDcloud.publish(KEY_LIVE_COUNT, (float)whistleCount);
 }
}

    When isListening is active, a signal rising above WHISTLE_ON_DB marks the potential start of a whistle event. If the signal stays above for MIN_WHISTLE_MS (300ms) and drops below WHISTLE_OFF_DB, a valid whistle is logged. whistleCount increments, updates KEY_LIVE_COUNT on the cloud, and sets cooldownUntilMs = now + 10000UL. This 10-second lock prevents long-duration whistle echoes from triggering double counts.

if (whistleCount >= targetWhistles) {
 sendWhatsAppAlert(whistleCount); // HTTP POST to www.circuitdigest.cloud:443
 isListening = false;
 CDcloud.publish(KEY_LISTEN_SW, 0.0f); // Sync switch back to OFF
}

    When whistleCount >= targetWhistles, the system sends a JSON payload containing the final count and sends an HTTP POST request to WhatsApp. After sending the alert, isListening is set to false to prevent duplicate messaging on extra noise spikes.

Output

The automatic cooker whistle counter was tested using a pressure cooker in a kitchen. Users can set their desired target count (1–20 whistles) on the CircuitDigest Cloud dashboard slider and switch it on. The system continuously measures ambient sound levels, counts each whistle, and updates the live whistle count on the dashboard. Once the specified whistle limit is reached, it automatically triggers an instant WhatsApp alert to the phone and stops listening.

 Real-Time Demo: Automatic Cooker Whistle Counter

Troubleshooting

1. How to calibrate the MAX4466 module?
Fix: Rotate the trim potentiometer on the bottom side of the module counterclockwise to increase signal gain and clockwise to decrease signal gain.
2. What to do when the Wi-Fi is not connected to the ESP32?
Fix: Check whether the proper Wi-Fi credentials are entered in the code.
3. What to do if, even after adjusting the potentiometer in the MAX4466 module, the dB value of the quiet room is always around 80 dB?
Fix: Replace the MAX4466 module with a new one and check using the same procedure.
4. What to do when the whistle generated can’t be sensed by the MAX4466 module?
Fix: In the code, reduce the WHISTLE_ON_DB and WHISTLE_OFF_DB to make the MAX4466 module sense the generated whistle 

GitHub Repository

 Cooker Whistle Counter GitHub RepoCooker Whistle Counter Downloadable Zip File

Complete Project Code

/*
 Cooker Whistle Counter with WhatsApp Alert - ESP32 + MAX4466
 CircuitDigest Cloud (no display, headless)
 Dashboard:
   analog-input-1 (Slider, 0-20, direction: output->device)  -> Set target whistle count
   analog-input-2 (Toggle/Switch, direction: output->device) -> Start/Stop listening (turn ON to arm, turns itself OFF once target whistle count is reached and alert is sent)
   analog-input-3 (Gauge/Number, direction: input->dashboard, optional) -> Live whistle count while listening
   analog-input-4 (Gauge/Number, direction: input->dashboard) -> Live dB level, updated continuously (~1x/sec)
 Detection logic:
   A whistle is a sharp dB spike that stays loud for a short burst then dies away. We track an envelope dB level, and count one whistle every time the level rises above WHISTLE_ON_DB, stays above it for at least MIN_WHISTLE_MS, then falls back below WHISTLE_OFF_DB. A cooldown after each detected whistle prevents one long whistle from being counted twice.
 On every power-up/reset: whistle count, target, listening state, and the live dB/count dashboard widgets are all forced back to zero/off, so the dashboard never shows a stale value left over from before the reset.
 Wiring (Generic ESP32 Dev Kit / DevKitC):
   MAX4466 VCC -> 3.3V
   MAX4466 GND -> GND
   MAX4466 OUT -> GPIO34   (ADC1_CH6, input-only, safe from Serial/USB/WiFi)
 NOTE: GPIO1 (used in some XIAO ESP32-S3 reference sketches as "A0") is actually the UART TX0 pin on a generic ESP32 DevKitC — it's reserved for USB-serial communication. Reading the mic there conflicts with Serial and causes garbage output / boot issues. GPIO34 avoids that entirely, and also stays reliable with WiFi active (ADC2 pins can get flaky when WiFi is on; GPIO34 is ADC1).
*/
#include <CircuitDigestCloud.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
// ── Fill in your credentials ────────────────────────────────────────────
#define WIFI_SSID      "YOUR_WIFI_SSID"
#define WIFI_PASS      "YOUR_WIFI_PASSWORD"
#define DEVICE_ID      "YOUR_DEVICE_ID"
#define CONNECTION_KEY "YOUR_CONNECTION_KEY"
#define API_KEY        "YOUR_API_KEY"
#define PHONE_NUMBER   "YOUR_PHONE_NUMBER"
#define KEY_TARGET_SET   "analog-input-1"   // slider: desired whistle count
#define KEY_LISTEN_SW    "analog-input-2"   // switch: start/stop listening
#define KEY_LIVE_COUNT   "analog-input-3"   // live whistle count readback
#define KEY_LIVE_DB      "analog-input-4"   // live dB level readback
const char* host = "www.circuitdigest.cloud";
// ──────────────────────────────────────────────────────────────────────
CircuitDigestCloud CDcloud;
// ---------- Mic Config ----------
#define AUDIO_PIN 34             // GPIO34 (ADC1_CH6) — generic ESP32 Dev Kit / DevKitC
#define SAMPLE_WINDOW 20         // ms per envelope sample (fast, for bursts)
#define ADC_RESOLUTION 4095.0
#define V_REF 3.3
// ---------- Whistle Detection ----------
#define WHISTLE_ON_DB       60.0    // dB level that counts as "whistle sound"
#define WHISTLE_OFF_DB      50.0    // must drop below this to end the whistle
#define MIN_WHISTLE_MS      100UL   // must stay loud at least this long
#define WHISTLE_COOLDOWN_MS 10000UL // ignore new whistles for this long after one ends
// ---------- State ----------
bool     isListening      = false;
int      targetWhistles   = 0;      // starts at 0 until dashboard slider sets it
int      whistleCount     = 0;
bool     inWhistle        = false;  // currently inside a loud burst
unsigned long whistleStartMs = 0;
unsigned long cooldownUntilMs = 0;
#define CLOUD_PUBLISH_MS 1000UL     // how often live dB is pushed to the dashboard
unsigned long lastCloudPublish = 0;
// Raw diagnostics from the last readDBLevel() call — printed in loop() so
// you can see WHY dB is stuck instead of just the final number.
unsigned int lastSignalMax = 0;
unsigned int lastSignalMin = 0;
unsigned int lastPeakToPeak = 0;
// Raw (unclamped) dB from the last readDBLevel() call — lets you see real
// gradations below the 30.0 floor for calibration. The clamped/floored
// value is still what's used for whistle detection and cloud publishing.
float lastRawDB = 30.0;
bool  lastReadingValid = true;
// A genuinely disconnected/floating mic tends to read a FLAT, unchanging
// value (near-zero peakToPeak) persistently over many samples in a row —
// unlike a loud transient (snap/whistle), which has large peakToPeak and
// only lasts briefly. We only flag a fault after several consecutive
// flatlined samples, so a real loud sound is never mistaken for a fault.
#define FLATLINE_P2P_MAX     3      // peakToPeak this low counts as "flat"
#define FLATLINE_FAULT_COUNT 15     // consecutive flat samples (~300ms) before flagging fault
unsigned int flatlineStreak = 0;
// ── Read instantaneous envelope dB over SAMPLE_WINDOW ───────────────────
float readDBLevel() {
 unsigned long startMillis = millis();
 unsigned int signalMax = 0;
 unsigned int signalMin = 4095;
 while (millis() - startMillis < SAMPLE_WINDOW) {
   unsigned int sample = analogRead(AUDIO_PIN);
   if (sample < 4095) {
     if (sample > signalMax) signalMax = sample;
     if (sample < signalMin) signalMin = sample;
   }
 }
 unsigned int peakToPeak = signalMax - signalMin;
 lastSignalMax = signalMax;
 lastSignalMin = signalMin;
 lastPeakToPeak = peakToPeak;
 // Fault detection: only a SUSTAINED flatline counts as disconnected.
 // A loud transient (snap/whistle) has large peakToPeak and resets the
 // streak immediately, so it's never mistaken for a fault.
 if (peakToPeak <= FLATLINE_P2P_MAX) {
   if (flatlineStreak < 65000) flatlineStreak++;
 } else {
   flatlineStreak = 0;
 }
 lastReadingValid = (flatlineStreak < FLATLINE_FAULT_COUNT);
 float voltage = (peakToPeak * V_REF) / ADC_RESOLUTION;
 if (voltage < 0.01) voltage = 0.01;
 float rawDb = (41.52 * log10(voltage)) + 64.02;
 lastRawDB = rawDb;
 float db = rawDb;
 if (db < 30.0) db = 30.0;
 // Invalid (persistently flatlined/disconnected) readings never count as
 // loud — report them as silence rather than letting a stuck pin trigger
 // a whistle. A real loud sound always has nonzero peakToPeak, so it is
 // never affected by this.
 if (!lastReadingValid) db = 30.0;
 return db;
}
// ── WhatsApp alert ───────────────────────────────────────────────────────
void sendWhatsAppAlert(int completedWhistles) {
 WiFiClientSecure client;
 client.setInsecure();
 client.setTimeout(5000);
 Serial.println("Attempting WhatsApp alert connection...");
 if (!client.connect(host, 443)) {
   Serial.println("WhatsApp API Connection FAILED - could not reach host");
   return;
 }
 Serial.println("Connected to CircuitDigest Cloud host. Sending request...");
 // NOTE: "threshold_violation_alert" is reused here since it's a generic
 // template with device/parameter/measured_value/limit/location fields.
 // If your CircuitDigest Cloud account has a dedicated whistle-alert
 // template, swap template_id below to that instead.
 String payload =
   "{\"phone_number\":\"" + String(PHONE_NUMBER) + "\","
   "\"template_id\":\"threshold_violation_alert\","
   "\"variables\":{"
   "\"device_name\":\"ESP32 Cooker Whistle Counter\","
   "\"parameter\":\"Whistle Count\","
   "\"measured_value\":\"" + String(completedWhistles) + " whistles\","
   "\"limit\":\"" + String(completedWhistles) + " whistles\","
   "\"location\":\"Kitchen\"}}";
 client.println("POST /api/v1/whatsapp/send HTTP/1.1");
 client.println("Host: www.circuitdigest.cloud");
 client.println("X-API-Key: " + String(API_KEY));
 client.println("Content-Type: application/json");
 client.println("Connection: close");
 client.print("Content-Length: ");
 client.println(payload.length());
 client.println();
 client.print(payload);
 unsigned long responseStart = millis();
 while (client.connected() && !client.available()) {
   if (millis() - responseStart > 5000) {
     Serial.println("WhatsApp API response TIMEOUT - no reply from server");
     client.stop();
     return;
   }
   delay(10);
 }
 Serial.println("---- Server Response ----");
 String responseBody = "";
 while (client.connected() || client.available()) {
   if (client.available()) {
     String line = client.readStringUntil('\n');
     Serial.println(line);
     responseBody += line;
   }
 }
 Serial.println("--------------------------");
 if (responseBody.indexOf("200") > 0 || responseBody.indexOf("201") > 0) {
   Serial.println("WhatsApp Alert appears to have SENT successfully.");
 } else {
   Serial.println("WhatsApp Alert may have FAILED - check response above for error details.");
 }
 client.stop();
}
// Publishes a value, checking the actual success/failure return instead of
// firing-and-forgetting. Retries a few times with the loop pumped in
// between, since a publish can fail if the cloud session isn't fully
// settled yet (e.g. right after boot).
void publishWithRetry(const char* key, float value) {
 for (int attempt = 1; attempt <= 5; attempt++) {
   bool ok = CDcloud.publish(key, value);
   Serial.print("Publish ");
   Serial.print(key);
   Serial.print(" = ");
   Serial.print(value);
   Serial.print(" (attempt ");
   Serial.print(attempt);
   Serial.print("): ");
   Serial.println(ok ? "OK" : "FAILED");
   if (ok) return;
   CDcloud.loop();
   delay(300);
 }
 Serial.print("Giving up publishing ");
 Serial.println(key);
}
// ── Cloud → device callbacks ─────────────────────────────────────────────
void onTargetSlider(float v) {
 targetWhistles = (int)v;
 if (targetWhistles < 1) targetWhistles = 1;
 Serial.print("Target whistle count set to: ");
 Serial.println(targetWhistles);
}
void onListenSwitch(float v) {
 bool turningOn = (bool)v;
 if (turningOn && !isListening) {
   // Arm fresh: reset count, clear any in-progress whistle state
   isListening = true;
   whistleCount = 0;
   inWhistle = false;
   cooldownUntilMs = 0;
   CDcloud.publish(KEY_LIVE_COUNT, 0.0f);
   Serial.println("Listening STARTED - whistle count reset to 0");
 } else if (!turningOn && isListening) {
   isListening = false;
   Serial.println("Listening STOPPED by user");
 }
}
void setup() {
 Serial.begin(115200);
 delay(1000);
 pinMode(AUDIO_PIN, INPUT);
 analogSetAttenuation(ADC_11db);
 // ---- Force a clean slate on every power-up/reset ----
 isListening    = false;
 whistleCount   = 0;
 targetWhistles = 0;
 inWhistle      = false;
 cooldownUntilMs = 0;
 CDcloud.subscribe(KEY_TARGET_SET, onTargetSlider);
 CDcloud.subscribe(KEY_LISTEN_SW, onListenSwitch);
 Serial.println("Connecting to Wi-Fi & CircuitDigest Cloud...");
 if (!CDcloud.begin(WIFI_SSID, WIFI_PASS, DEVICE_ID, CONNECTION_KEY, API_KEY)) {
   Serial.println("CDcloud begin() failed — check credentials. Rebooting...");
   delay(2000);
   ESP.restart();
 } else {
   Serial.println("CDcloud initialized successfully.");
 }
 // CDcloud.begin() succeeding only means WiFi + the initial handshake
 // worked — the cloud session isn't necessarily ready to accept publishes
 // the instant it returns. Pump the loop for a bit so it fully settles
 // before we push the reset values, or they can silently get dropped.
 for (int i = 0; i < 15; i++) {
   CDcloud.loop();
   delay(200);
 }
 // Push the reset state to the dashboard so it never shows a stale value
 // from before this boot (switch OFF, count/db back to zero). Each
 // publish is retried a few times since a single attempt can still land
 // before the connection is fully ready.
 publishWithRetry(KEY_LISTEN_SW, 0.0f);
 publishWithRetry(KEY_LIVE_COUNT, 0.0f);
 publishWithRetry(KEY_LIVE_DB, 0.0f);
 Serial.println("System Ready. Set target on analog-input-1, arm with analog-input-2.");
}
void loop() {
 CDcloud.loop();
 if (WiFi.status() != WL_CONNECTED) {
   Serial.println("Wi-Fi Disconnected! Restarting system...");
   delay(2000);
   ESP.restart();
 }
 // Always sample the mic (even when not armed) so you can watch live dB
 // in Serial Monitor and tune WHISTLE_ON_DB / WHISTLE_OFF_DB before relying
 // on the automatic detection.
 float db = readDBLevel();
 static unsigned long lastDbPrint = 0;
 unsigned long nowDb = millis();
 if (nowDb - lastDbPrint >= 200) {
   lastDbPrint = nowDb;
   Serial.print("Live dB: ");
   Serial.print(db, 1);
   Serial.print(" (raw: ");
   Serial.print(lastRawDB, 1);
   Serial.print(")  | ADC min: ");
   Serial.print(lastSignalMin);
   Serial.print(" max: ");
   Serial.print(lastSignalMax);
   Serial.print(" p2p: ");
   Serial.print(lastPeakToPeak);
   Serial.print(" | mic: ");
   Serial.println(lastReadingValid ? "OK" : "DISCONNECTED?");
 }
 if (isListening) {
   unsigned long now = millis();
   if (!inWhistle) {
     // Waiting for a whistle to start (respect cooldown after last one)
     if (db > WHISTLE_ON_DB && now > cooldownUntilMs) {
       inWhistle = true;
       whistleStartMs = now;
     }
   } else {
     // Currently inside a whistle burst — has it ended?
     if (db < WHISTLE_OFF_DB) {
       unsigned long whistleDuration = now - whistleStartMs;
       inWhistle = false;
       if (whistleDuration >= MIN_WHISTLE_MS) {
         whistleCount++;
         cooldownUntilMs = now + WHISTLE_COOLDOWN_MS;
         Serial.print("Whistle detected! Count: ");
         Serial.print(whistleCount);
         Serial.print(" / ");
         Serial.println(targetWhistles);
         CDcloud.publish(KEY_LIVE_COUNT, (float)whistleCount);
         if (whistleCount >= targetWhistles) {
           Serial.println("Target whistle count reached! Sending WhatsApp alert...");
           sendWhatsAppAlert(whistleCount);
           // Auto-disarm so it doesn't keep counting/alerting
           isListening = false;
           CDcloud.publish(KEY_LISTEN_SW, 0.0f); // sync dashboard switch back to OFF
         }
       }
       // else: too short, was just noise — ignored, no count
     }
   }
 }
 // ---- Publish live dB to the dashboard every second ----
 unsigned long nowPub = millis();
 if (nowPub - lastCloudPublish >= CLOUD_PUBLISH_MS) {
   lastCloudPublish = nowPub;
   CDcloud.publish(KEY_LIVE_DB, db);
 }
 // ---- Periodic serial debug ----
 static unsigned long lastDebug = 0;
 unsigned long now2 = millis();
 if (now2 - lastDebug >= 500) {
   lastDebug = now2;
   Serial.print("Listening: ");
   Serial.print(isListening ? "YES" : "NO");
   Serial.print(" | Count: ");
   Serial.print(whistleCount);
   Serial.print(" / ");
   Serial.println(targetWhistles);
 }
}
Have any question related to this Article?

Add New Comment

Login to Comment Sign in with Google Log in with Facebook Sign in with GitHub