melkati / CO2-Gadget

An advanced CO2 Monitor/Meter firmware for ESP32 with Android and iOS App for real time visualization and charting of air data, data logger, a variety of communication options (BLE, WIFI, MQTT, ESP-Now) and many supported sensors.
https://emariete.com/medidor-co2-gadget/
GNU General Public License v3.0
63 stars 13 forks source link

Implement buzzer output for alarms #154

Closed melkati closed 7 months ago

melkati commented 8 months ago

Thanks @Coscolin is willing to contribute with this feature.

As with most new features, the tasks necessary to implement this are (open to change WIP):

melkati commented 8 months ago

@Coscolin

To make the buzzer code non-blocking and eliminate delays, you can utilize the millis() function for timing and avoid using delay(). Below is the modified code:

uint64_t buzzerStartTime = 0;
uint64_t buzzerStopTime = 0;

void buzzerRedRange() {
    Serial.println("[BUZZ] Buzzer RED range");
    tone(BUZZER_PIN, toneBuzzerBeep + co2, durationBuzzerBeep);
    buzzerStopTime = millis() + durationBuzzerBeep * 1.3;
}

void buzzerOrangeRange() {
    Serial.println("[BUZZ] Buzzer ORANGE range");
    tone(BUZZER_PIN, toneBuzzerBeep + co2, durationBuzzerBeep);
    buzzerStopTime = millis() + durationBuzzerBeep * 1.3;
}

void buzzerLoop() {
    if (!activeBuzzer)
        return;

    if (inMenu) {  // Inside Menu stop BEEPING
        noTone(BUZZER_PIN);
        return;
    }

    if ((millis() - lastTimeBuzzerBeep >= timeBetweenBuzzerBeep * 1000) || (lastTimeBuzzerBeep == 0)) {
        lastTimeBuzzerBeep = millis();

        if (co2 > co2RedRange) {
            if (belowRedRange || repeatBuzzer) {
                wakeUpDisplay();
                buzz.once(0, buzzerRedRange);
                belowRedRange = false;
            }
            return;
        } else if (co2 < (co2RedRange - BUZZER_HYSTERESIS)) {
            belowRedRange = true;
            noTone(BUZZER_PIN);  // Stop buzzing when below the range
        }

        if (co2 > co2OrangeRange) {
            if (belowOrangeRange || repeatBuzzer) {
                wakeUpDisplay();
                buzz.once(0, buzzerOrangeRange);
                belowOrangeRange = false;
            }
            return;
        } else if (co2 < (co2OrangeRange - BUZZER_HYSTERESIS)) {
            belowOrangeRange = true;
            noTone(BUZZER_PIN);  // Stop buzzing when below the range
        }
    }

    // Stop buzzing after the specified duration
    if (millis() >= buzzerStopTime) {
        noTone(BUZZER_PIN);
    }
}

In this modified code:

This approach allows the main loop to continue executing while the buzzer functions play without introducing delays.

This is untested code but I think you can get the idea to complete it.

Please, test this (as I don't have a buzzer connected) and if it's working fine, we can change the code.

melkati commented 8 months ago

@Coscolin,

I think there is something missing related to the buzzer PIN initialization.

When the buzzer sounds for the first time (just in the first loop) there is an error:

09:46:07.083 > [BUZZ] Buzzer RED range
09:46:07.084 > E (73634) ledc: ledc_get_duty(740): LEDC is not initialized

Later the error disappears:

09:47:01.830 > -->[SENS] CO2: 1748 CO2humi: 58.83 CO2temp: 22.63 H: 0.00 T: 0.00
09:47:07.107 > [BUZZ] Buzzer RED range
09:47:11.871 > -->[SENS] CO2: 1748 CO2humi: 59.09 CO2temp: 22.54 H: 0.00 T: 0.00

This is with the actual code in feature-buzzer branch.

melkati commented 8 months ago

@Coscolin

What buzzer type is it intended to use? I want to include it in the documentation and also for my own use so I can do some testing.

Coscolin commented 8 months ago

I have used a passive buzzer that I reused from an old CO2 meter. I have order in AliExpress this one for complete my others CO2Gadget:

2,02€ | Zumbador pasivo de uso común para Arduino, minizumbadores piezoeléctricos de 10 piezas, CA de 12MM x 8,5 MM, resistencia de 12085 42R, 3V, 5V, 9V, 12V https://a.aliexpress.com/_EIv4KoN

melkati commented 8 months ago

That kind of buzzer can have a power consumption higher than the maximum current an ESP32 can supply on one pin.

I just tested one and at 3.3V it consumes up to 74mA.

Better to use a buzzer module with a driver transistor, as this one:

https://s.click.aliexpress.com/e/_DFCiiLT

Also, this permits the buzzer to be powered by Vin (USB or LiPo power) to power the buzzer without loading the onboard 3.3V LDO.

The ESP32 datasheet is a little cryptic about that but the maximum current on one pin in ESP32 is 40mA and the datasheet states that as more pins are supplying current it can go down to 10mA. CO2 Gadget uses more than one pin with current consumption (LEDs for example) so is better to be safe than sorry.

If anyway you need to use that kind of buzzer, without a driving transistor, at least connect a current limiting resistor in series (probably 220 to 470Ohm, better to test and measure).

Coscolin commented 8 months ago

@melkati you can remove " Remove references to beep on orange" from "Code to drive the buzzer" in task list.

melkati commented 8 months ago

Can we tick task "Include board specific data in platformio.ini" as done or is there anything left to include?

Coscolin commented 8 months ago

The only lines added to platformio.ini are this ones: -DBUZZER_PIN=13 ; ESP32 pin GPIO13 connected to piezo buzzer -DBUZZER_HYSTERESIS=50 ; Hysteresis PPM to avoid BUZZER ON and OFF continuously if repeat is once -DSUPPORT_BUZZER ;

I think it's not necessary to include any more. Even SUPPORT_BUZZER is not used at all.

melkati commented 8 months ago

We must move the buzzer GPIO to another PIN, as GPIO is reserved (preferably) for serial sensors as is this table:

Board model TX RX Notes
ESP32GENERIC 1 3 ESP32 Pio defaults
TTGOT7 / ESP32DEVKIT / D1MINI / NODEFINED 16 17 CanAirIO devices **
TTGO_TDISPLAY 12 13
M5COREINK 14 13
TTGO TQ 18 13
HELTEC 18 17
WEMOSOLED 15 13
ESP32PICOD4 3 1
Coscolin commented 7 months ago

No problem for me. Choose the one you prefer or think is best. I put pin 13 because I saw it was available in both T-Display and S3 and it was not used by any part of the actual code.

melkati commented 7 months ago

No problem for me. Choose the one you prefer or think is best. I put pin 13 because I saw it was available in both T-Display and S3 and it was not used by any part of the actual code.

It's used by CO2 Gadget in TTGO T-Display to connect the serial sensors (as Senseair S8 LP, MH-Z19, etc): https://emariete.com/en/co2-meter-gadget/#Pines_utilizados_por_CO2_Gadget_GPIO

As we have more precompiled versions (the precompiled flavours) we will have to think of a well thought table of used GPIOs. It will be better if we can use the same pins on most of the boards (if possible). It's not an easy task with so many moving gears!

melkati commented 7 months ago

I have tried to add the GPIO used to the menu (for user notice), but I have not succeeded.

#define BUZZER_MENU_TITLE "Buzzer: (GPIO" BUZZER_PIN ")" // TO-DO: Add buzzer pin to menu

MENU(buzzerConfigMenu, BUZZER_MENU_TITLE, doNothing, noEvent, wrapStyle
  ,SUBMENU(timeBetweenBuzzerBeepMenu)
  ,SUBMENU(toneBuzzerBeepMenu)
  ,SUBMENU(durationBuzzerBeepMenu)
  ,EXIT("<Back"));
#endif
melkati commented 7 months ago

I already moved BUZZER_PIN to GPIO 02

I will leave:

_To finish and before publishing into the release, unify it with the led, relays, Neopixels, and other "Outputs" stuff, in order to remove the code that is scattered everywhere, and rename it as CO2_GadgetOutputs.h.

For after the release. I don't want to delay it any longer just for this reason (it would take a short time to do it but it involves a lot of testing afterwards)...

So I think it's ready for release...