Newer
Older
smart-home-server / devices / sensor / max4466_mic.h
#pragma once

#include <Arduino.h>

/*
    =========================================================
    MAX4466 Microphone — драйвер для ESP32
    Подключение: аналоговый выход на ADC-пин ESP32

    Что умеет:
      - Замер мгновенного уровня шума (dB SPL, приблизительно)
      - EMA-сглаживание dB
      - Динамика шума: скользящее среднее по минутам +
        линейный тренд (как у LD2420)
      - Пиковый уровень за окно наблюдения
      - JSON состояния

    Калибровка:
      Абсолютные dB SPL зависят от усиления потенциометра
      на модуле MAX4466 и акустики помещения.
      Настройте db_ref_mv и db_ref_pa под ваши условия,
      либо используйте относительные значения dB FS.

    Как работает замер:
      За каждый вызов update() снимается sample_count отсчётов
      с интервалом sample_interval_us.
      Вычисляется RMS амплитуды (после вычитания DC-смещения),
      затем переводится в дБ.
    =========================================================
*/

struct Max4466Config {
    /* ---- Пин и ADC ---- */
    uint8_t  adc_pin          = 34;

    /*
        Разрешение ADC ESP32: 12 бит → max = 4095.
        Напряжение питания 3.3 В → 1 LSB ≈ 0.806 мВ.
    */
    uint16_t adc_max_value    = 4095;
    float    adc_vref_mv      = 3300.0f;   // мВ

    /* ---- Сэмплирование ---- */

    /*
        Количество отсчётов за одно измерение.
        Больше → точнее RMS, но дольше блокируется loop().
        128–256 — хороший баланс для замера шума.
    */
    uint16_t sample_count     = 256;

    /*
        Пауза между отсчётами в микросекундах.
        256 отсчётов × 100 мкс = ~25 мс на одно измерение.
    */
    uint16_t sample_interval_us = 100;

    /* ---- Интервал между измерениями ---- */
    uint32_t read_interval_ms = 100;

    /*
        Через сколько мс без успешного чтения считать данные устаревшими.
    */
    uint32_t stale_after_ms   = 2000;

    /* ---- EMA-сглаживание dB ---- */
    /*
        0.0 → почти без обновления (сильное сглаживание)
        1.0 → без сглаживания
    */
    float    db_ema_alpha     = 0.15f;

    /* ---- Калибровка dB ---- */

    /*
        Опорное напряжение в мВ соответствующее 0 dB SPL (≈ 20 мкПа).
        Подберите экспериментально или рассчитайте по даташиту
        на MAX4466 при заданном усилении.
        По умолчанию: 1 мВ RMS → 0 dB FS (относительные dB).
        Если нужен абсолютный dB SPL — откалибруйте под усиление.
    */
    float    db_ref_mv        = 1.0f;

    /*
        Нижний порог шума (dB): значения ниже считаются тишиной.
        Компенсирует шум АЦП при полной тишине.
    */
    float    db_noise_floor   = 30.0f;

    /*
        Диапазон шкалы noise_level (0..10).
        noise_level = 0 соответствует db_noise_floor,
        noise_level = 10 соответствует db_noise_floor + db_scale_range_db.
        По умолчанию: 30 dB диапазон → каждый балл ≈ 3 dB.
    */
    float    db_scale_range_db = 30.0f;

    /* ---- Динамика (тренд по минутам) ---- */

    /*
        Усреднение по секундам для activity_score (0–10).
        Аналогично activity_avg_window_s у LD2420.
    */
    uint8_t  db_avg_window_s  = 30;

    /*
        Окно тренда в минутах.
        Минимум 2, максимум DB_TREND_MAX.
    */
    uint8_t  db_trend_window_min = 10;

    /* ---- Пиковое значение ---- */

    /*
        Период сброса пикового значения (мс).
        0 = никогда не сбрасывать автоматически.
    */
    uint32_t peak_reset_ms    = 60000;
};


/*
    =========================================================
    Max4466Mic — публичный интерфейс
    =========================================================
*/

class Max4466Mic {
public:
    Max4466Mic() = default;

    /*
        Инициализация: настройка ADC-пина.
        Возвращает true всегда (аналоговый датчик не отвечает
        на запросы — online определяем по наличию данных).
    */
    bool begin(const Max4466Config &config);

    /*
        Периодическое обновление.
        Нужно вызывать часто из loop().
    */
    void update();

    /* ---- Статус ---- */
    bool is_online()       const { return _online; }
    bool has_valid_data()  const { return _has_valid_data; }
    bool is_stale()        const;

    /* ---- Текущие значения ---- */

    /*
        Мгновенный dB (сглаженный EMA).
    */
    float get_db()         const { return _db_filtered; }

    /*
        Сырой dB без EMA-фильтра.
    */
    float get_db_raw()     const { return _db_raw; }

    /*
        Усреднённый dB за db_avg_window_s секунд.
    */
    float get_db_avg()     const { return _db_avg; }

    /*
        Пиковый dB за период peak_reset_ms.
    */
    float get_db_peak()    const { return _db_peak; }

    /*
        RMS-напряжение последнего замера (мВ).
    */
    float get_rms_mv()     const { return _rms_mv; }

    /* ---- Динамика ---- */

    /*
        Тренд уровня шума по минутам.
        Возвращает: "increasing" / "decreasing" / "constant" / "variable"
    */
    const char* get_noise_dynamics() const;

    /*
        Средний уровень шума за минуту в виде шкалы 0..10.
        Вычисляется из db_avg относительно db_noise_floor и
        db_noise_floor + db_scale_range_db.
    */
    uint8_t get_noise_level() const;

    /* ---- JSON ---- */
    /*
        Формат:
        {
          "online":          true,
          "current_noise":   65,       // dB (мгновенный, EMA-сглаженный)
          "noise_level":     4,        // 0..10 (среднее за минуту)
          "noise_level_dbi": 65,       // dB (среднее за минуту)
          "noise_dynamics":  "constant"
        }
    */
    String get_state_json() const;

    /* ---- Ручной сброс пика ---- */
    void reset_peak();

private:
    Max4466Config _config;

    bool     _online         = false;
    bool     _has_valid_data = false;

    uint32_t _last_read_ms        = 0;
    uint32_t _last_success_read_ms = 0;
    uint32_t _last_peak_reset_ms  = 0;

    float    _rms_mv      = 0.0f;
    float    _db_raw      = 0.0f;
    float    _db_filtered = 0.0f;
    float    _db_avg      = 0.0f;
    float    _db_peak     = 0.0f;

    /* ---- Внутренние методы ---- */
    float    _measure_rms_mv();
    float    _rms_to_db(float rms_mv) const;
    void     _update_filtered_db(float db);

    /* ---- Секундное усреднение (как act_avg у LD2420) ---- */
    static constexpr uint8_t DB_AVG_MAX  = 60;
    float    _db_avg_buf[DB_AVG_MAX] = {};
    uint8_t  _db_avg_idx   = 0;
    uint8_t  _db_avg_count = 0;
    uint32_t _db_avg_last_ms = 0;
    void     _tick_db_avg(uint32_t now_ms);

    /* ---- Тренд по минутам (как _tick_trend у LD2420) ---- */
    static constexpr uint8_t DB_TREND_MAX = 10;
    float    _trend_buf[DB_TREND_MAX] = {};
    uint8_t  _trend_idx   = 0;
    uint8_t  _trend_count = 0;
    uint32_t _trend_last_ms = 0;
    int8_t   _trend_slope   = 0;
    float    _trend_std_dev = 0.0f;
    void     _tick_trend(uint32_t now_ms);
    void     _compute_trend();
};