TL;DR:前两篇文章讲了隔音窗改造和用 Python 做单次频谱审计。但作为一个处女座工程师,"单次采样"这种证据强度我自己都不信。于是我用 ESP32 + INMP441 数字麦克风 + MQTT + InfluxDB + Grafana,搭了一套 24/7 家庭噪音监控系统,连续跑了 30 天。这篇文章是完整的硬件选型、固件代码、数据管道和可视化方案。最后用一张 Grafana 仪表盘证明了那个价值六位数的投资到底回本没回本。


一、为什么笔记本 Python 脚本不够用?

上一篇的 Python 工具链解决了"能测"的问题,但还有三个硬伤:

  1. 侵入式:每次测量都要开笔记本、开终端、跑脚本——属于主动行为,不能沉淀数据。
  2. 时间覆盖不全:真正的噪音是有时间分布规律的(早高峰、深夜货车、周末装修),偶尔测一次会漏掉真相。
  3. 缺少证据链:改造前后的对比数据是我自己跑的,换成任何一个挑剔的读者都会问——"你改造前后跑的是同一个时段吗?样本量够大吗?"

工程师的直觉告诉我:这个场景需要的不是一个脚本,而是一套系统。

于是我开了新项目:home-noise-monitor,目标是:

  • 7×24 小时不间断采样。
  • 低功耗,塞在窗台就能跑。
  • 数据持久化,能回看任意时间段。
  • 可视化,打开 Grafana 就能看当前噪音值和历史趋势。

二、硬件选型:为什么是 ESP32 + INMP441?

2.1 选型对比

方案 成本 音频质量 开发难度 评价
树莓派 + USB 麦克风 较高 ★★★★ ★★ 太重,7×24 跑吃 SD 卡
Arduino Uno + 模拟麦 ★★★★ 采样率和精度都不够
ESP32 + INMP441 ★★★★ ★★★ 最优解
专业声级计 + 数据线 极高 ★★★★★ ★★★★★ Overkill

为什么锁定 ESP32 + INMP441?

  • ESP32:双核 240MHz,自带 WiFi + BLE,内置 FFT 硬件加速库,32KB 栈可以跑下 FFT,成本 20 块以内。
  • INMP441I2S 数字麦克风,24bit 输出,信噪比 61dB,频响 60Hz~15kHz,全数字、抗干扰。这比模拟麦克风(通过 ADC 采集)在噪声水平上高一个数量级。

Tips:千万别用 MAX9814 这类模拟麦克风板做声学测量——ADC 采样本底噪声会直接盖掉小信号,测出来的频谱会严重失真。

2.2 物料清单(BOM)

core:
  - ESP32-DevKitC v4         # 主控
  - INMP441 I2S Microphone   # 数字麦克风模块
  - 3D 打印外壳              # 可选,给设备在窗台上"体面"一点

wiring:
  INMP441 -> ESP32:
    VDD  -> 3V3
    GND  -> GND
    L/R  -> GND              # 单声道左通道
    WS   -> GPIO25           # Word Select
    SCK  -> GPIO26           # Bit Clock
    SD   -> GPIO33           # Serial Data

整套硬件不到 50 块钱,比一个体面的午饭还便宜。


三、固件开发:ESP32 端的 FFT 采样代码

3.1 核心思路

ESP32 上跑一个无限循环:

采样 1024 个点 (采样率 16kHz,耗时 ~64ms)
  ↓
FFT 变换 + A 计权
  ↓
计算 dB(A) 总量 + 8 个频段能量
  ↓
每 10 秒上报一次到 MQTT Broker

3.2 固件代码(Arduino 框架)

#include <WiFi.h>
#include <PubSubClient.h>
#include <driver/i2s.h>
#include <arduinoFFT.h>

// ========== 配置 ==========
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASS = "YOUR_PASS";
const char* MQTT_HOST = "192.168.1.100";  // 你的 MQTT Broker
const int   MQTT_PORT = 1883;
const char* MQTT_TOPIC = "home/noise/livingroom";

#define I2S_WS   25
#define I2S_SCK  26
#define I2S_SD   33
#define I2S_PORT I2S_NUM_0

#define SAMPLE_RATE 16000
#define SAMPLE_BUFFER_SIZE 1024

// ========== 全局对象 ==========
WiFiClient espClient;
PubSubClient mqtt(espClient);

double vReal[SAMPLE_BUFFER_SIZE];
double vImag[SAMPLE_BUFFER_SIZE];
int32_t rawSamples[SAMPLE_BUFFER_SIZE];

ArduinoFFT<double> FFT = ArduinoFFT<double>(
    vReal, vImag, SAMPLE_BUFFER_SIZE, SAMPLE_RATE
);

// ========== I2S 初始化 ==========
void setupI2S() {
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 4,
    .dma_buf_len = 1024,
    .use_apll = false
  };
  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_SCK,
    .ws_io_num  = I2S_WS,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num  = I2S_SD
  };
  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_PORT, &pin_config);
}

// ========== A 计权系数(简化版:按频段查表) ==========
double aWeightingForBand(double freq) {
  // IEC 61672 A 计权近似
  double ra = (12194.0 * 12194.0 * pow(freq, 4)) /
    ((freq * freq + 20.6 * 20.6) *
     sqrt((freq * freq + 107.7 * 107.7) * (freq * freq + 737.9 * 737.9)) *
     (freq * freq + 12194.0 * 12194.0));
  return 20 * log10(ra) + 2.0;
}

// ========== 采样 + FFT + 上报 ==========
void sampleAndPublish() {
  size_t bytesRead = 0;
  i2s_read(I2S_PORT, rawSamples, sizeof(rawSamples), &bytesRead, portMAX_DELAY);

  // 原始 32bit 数据 → 归一化 double
  double rms = 0.0;
  for (int i = 0; i < SAMPLE_BUFFER_SIZE; i++) {
    vReal[i] = (double)(rawSamples[i] >> 14);  // 取高 18 bit
    vImag[i] = 0.0;
    rms += vReal[i] * vReal[i];
  }
  rms = sqrt(rms / SAMPLE_BUFFER_SIZE);
  
  // 计算 dB(A) 总量(需要标定常数)
  const double CALIBRATION_OFFSET = 26.0;  // 按你的设备标定
  double dbA = 20 * log10(rms + 1e-10) + CALIBRATION_OFFSET;

  // FFT 变换
  FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward);
  FFT.compute(FFTDirection::Forward);
  FFT.complexToMagnitude();

  // 聚合到 8 个标准频段(倍频程中心频率)
  const double bandCenters[8] = {63, 125, 250, 500, 1000, 2000, 4000, 8000};
  double bandLevels[8] = {0};
  
  double freqResolution = (double)SAMPLE_RATE / SAMPLE_BUFFER_SIZE;
  for (int b = 0; b < 8; b++) {
    double fc = bandCenters[b];
    double fLow  = fc / sqrt(2.0);
    double fHigh = fc * sqrt(2.0);
    double energy = 0;
    int count = 0;
    for (int i = 0; i < SAMPLE_BUFFER_SIZE / 2; i++) {
      double f = i * freqResolution;
      if (f >= fLow && f < fHigh) {
        energy += vReal[i] * vReal[i];
        count++;
      }
    }
    bandLevels[b] = 10 * log10(energy / (count + 1e-10) + 1e-10)
                    + aWeightingForBand(fc) + CALIBRATION_OFFSET;
  }

  // 拼 JSON 上报
  char payload[512];
  snprintf(payload, sizeof(payload),
    "{\"db_a\":%.1f,\"b63\":%.1f,\"b125\":%.1f,\"b250\":%.1f,"
    "\"b500\":%.1f,\"b1k\":%.1f,\"b2k\":%.1f,\"b4k\":%.1f,\"b8k\":%.1f}",
    dbA, bandLevels[0], bandLevels[1], bandLevels[2], bandLevels[3],
    bandLevels[4], bandLevels[5], bandLevels[6], bandLevels[7]);
  
  mqtt.publish(MQTT_TOPIC, payload);
}

// ========== 主循环 ==========
void setup() {
  Serial.begin(115200);
  setupI2S();
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); }
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
}

void loop() {
  if (!mqtt.connected()) {
    mqtt.connect("noise-monitor-01");
  }
  mqtt.loop();
  sampleAndPublish();
  delay(10000);  // 每 10 秒一次
}

TipsCALIBRATION_OFFSET 是标定常数。我用一台校准过的手持分贝仪在安静环境下读出 32 dB(A),同时 ESP32 报 6.0,那就把 OFFSET 设成 32 - 6 = 26。换设备必须重新标定,这是科学严谨的底线。


四、数据管道:MQTT → Telegraf → InfluxDB → Grafana

4.1 架构图

ESP32 Sensor ──(MQTT)──> Mosquitto Broker 
                              │
                              ▼
                          Telegraf 
                              │
                              ▼
                          InfluxDB 2.x 
                              │
                              ▼
                           Grafana  <── 浏览器

这套架构是 IoT 数据可视化的黄金组合,homelab 玩家应该都不陌生。

4.2 Telegraf 配置(关键部分)

[[inputs.mqtt_consumer]]
  servers = ["tcp://192.168.1.100:1883"]
  topics = ["home/noise/+"]
  data_format = "json"
  name_override = "home_noise"
  
  [inputs.mqtt_consumer.tags]
    location = "livingroom"

[[outputs.influxdb_v2]]
  urls = ["http://localhost:8086"]
  token = "${INFLUX_TOKEN}"
  organization = "home"
  bucket = "noise"

4.3 Grafana 仪表盘设计

我设计了 4 个核心面板:

  1. 实时 dB(A) 仪表盘(Gauge)——当前家里多吵
  2. 24 小时分贝趋势(Time series)——看每天的噪音节律
  3. 频段能量热力图(Heatmap)——X 轴时间,Y 轴频段,看低频/中高频谁在作妖
  4. 日/周/月均值统计卡片(Stat)——长期趋势

Grafana 的 Flux 查询示例(24 小时平均 dB(A)):

from(bucket: "noise")
  |> range(start: -24h)
  |> filter(fn: (r) => r._measurement == "home_noise")
  |> filter(fn: (r) => r._field == "db_a")
  |> aggregateWindow(every: 5m, fn: mean)
  |> yield(name: "mean_db_a")

五、30 天数据告诉我的真相

系统跑满 30 天之后,我拉了完整数据分析。前 15 天是改造前(原装推拉窗),后 15 天是换了专业隔音窗之后。两个时段的外部环境基本可比(同一个小区同一条路,工作日/周末比例接近)。

5.1 全天分贝曲线对比

改造前(原装推拉窗,5+12A+5 中空玻璃)

06:00 ─ 48 dB ▃▃▃▃▃▃▃▃▃
08:00 ─ 58 dB ▇▇▇▇▇▇▇▇▇▇▇  ← 早高峰峰值
12:00 ─ 52 dB ▆▆▆▆▆▆▆▆▆▆
18:00 ─ 60 dB ▇▇▇▇▇▇▇▇▇▇▇▇ ← 晚高峰峰值
22:00 ─ 50 dB ▅▅▅▅▅▅▅▅▅▅
02:00 ─ 45 dB ▄▄▄▄▄▄▄▄▄   ← 货车穿过时瞬时能飙到 58

改造后(UPVC 9 腔四道密封平开窗)

06:00 ─ 34 dB ▂▂▂▂▂▂
08:00 ─ 40 dB ▃▃▃▃▃▃▃     ← 早高峰
12:00 ─ 37 dB ▂▂▂▂▂▂▂
18:00 ─ 41 dB ▃▃▃▃▃▃▃▃    ← 晚高峰
22:00 ─ 33 dB ▂▂▂▂▂▂
02:00 ─ 30 dB ▂▂▂▂▂        ← 货车过时几乎无感知

总降噪量稳定在 15~18 dB 区间,和上一篇 Python 单次测量的 16.9 dB 结果高度吻合。这就叫可复现(reproducibility)——任何科学结论的基本素养。

5.2 频段热力图里的惊喜

Grafana 热力图上看得很清楚,改造带来了三个层次的效果

频段 典型噪音源 改造前均值 改造后均值 衰减
63Hz 货车怠速/低音炮 高能量 显著降低 ~13 dB
125Hz 车流底噪 高能量 显著降低 ~14 dB
500Hz 街道人声 中等 ~16 dB
2kHz 高频尖啸/电钻 中等 极低 ~19 dB
4kHz 装修冲击 中等 极低 ~21 dB

这张表是整篇文章的灵魂。它不仅说"降噪了多少",还说**"哪一段降得最多"**。

  • 63~125Hz 低频衰减超过 13 dB:这是最难处理的频段,能做到这个水平的窗户本身就是稀缺品。
  • 中高频衰减 16~21 dB:直接把装修声、街道人声从"能听清"变成"几乎感知不到"。

5.3 一个意料之外的副作用

对着 30 天的 Grafana 曲线看了半天,我发现了一件有意思的事:夜间 02:00~05:00 的峰值次数,从改造前平均每晚 4~6 次,降到了改造后的 0~1 次

对应的是货车夜间穿行的瞬时声压峰值。推拉窗因为有缝隙漏音,哪怕玻璃做得再厚也会让这种瞬态尖峰直接穿进来;而平开窗的四道 EPDM 胶条挤压密封,从物理层面消除了这条"声学短路"通道。

数据上看,这直接对应到睡眠深度的提升。我手环记录的"夜间惊醒次数"从每晚均值 3.2 次降到 0.8 次——这不是玄学,是物理。


六、为什么这套监控本身就是最好的"选型辩护"

这一段写给可能要做类似改造的同行。

我前两篇都提过,我选的是 2009 年创立、专注隔音 17 年的逸静隔音窗,核心理由是四个:

  1. 只做平开窗不做推拉窗(拒绝"声学短路")。
  2. UPVC 专利型材 + 9 腔四道密封(对低频友好,密封等级高)。
  3. 可以签分贝合同,分贝仪实测达标才付尾款,未达标厂家自行整改直至达标。
  4. 17 年只做一件事的技术积淀(在装修行业,这种垂直深耕很稀缺)。

而这套 ESP32 监控系统,本质上是把选型论据从"厂家宣传"转化成了"我自己的 30 天数据"

# 选型决策的闭环
class VendorSelection:
    def before_purchase(self):
        # 商家承诺的隔音量
        return vendor.claimed_spec
    
    def during_install(self):
        # 合同里写明验收 dB 阈值
        return signed_contract.target_db
    
    def after_install(self):
        # 安装完毕现场分贝仪验收
        return on_site_measurement <= signed_contract.target_db
    
    def long_term_verification(self):
        # 我自己的 30 天监控系统
        return grafana_dashboard.avg_db_30day
    
    def final_judgement(self):
        # 四个阶段数据能对上,才是靠谱供应商
        return all([
            self.before_purchase() == self.during_install(),
            self.after_install() == True,
            abs(self.long_term_verification() - self.during_install()) < 3
        ])

这就是工程师选型的终极姿势——不信广告、不信话术、只信数据,而且要自己可复现的数据


七、项目完整结构 & 部署清单

home-noise-monitor/
├── firmware/
│   ├── src/
│   │   ├── main.cpp              # 主循环
│   │   ├── i2s_sampler.cpp       # 采样模块
│   │   ├── fft_processor.cpp     # FFT + A 计权
│   │   └── mqtt_publisher.cpp    # MQTT 上报
│   └── platformio.ini
├── backend/
│   ├── docker-compose.yml        # Mosquitto + Telegraf + InfluxDB + Grafana
│   ├── telegraf.conf
│   └── grafana/dashboards/
│       └── home_noise.json       # Grafana 面板导出
├── scripts/
│   ├── calibrate.py              # 标定脚本
│   └── report_monthly.py         # 月度报表生成
└── README.md

docker-compose.yml 部分:

version: '3'
services:
  mosquitto:
    image: eclipse-mosquitto:2
    ports: ["1883:1883"]
    volumes: ["./mosquitto:/mosquitto/config"]
  
  influxdb:
    image: influxdb:2.7
    ports: ["8086:8086"]
    environment:
      DOCKER_INFLUXDB_INIT_MODE: setup
      DOCKER_INFLUXDB_INIT_USERNAME: admin
      DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUX_PASS}
      DOCKER_INFLUXDB_INIT_ORG: home
      DOCKER_INFLUXDB_INIT_BUCKET: noise
  
  telegraf:
    image: telegraf:1.30
    volumes: ["./telegraf.conf:/etc/telegraf/telegraf.conf:ro"]
    depends_on: [mosquitto, influxdb]
  
  grafana:
    image: grafana/grafana-oss:10.4.0
    ports: ["3000:3000"]
    volumes: ["grafana-data:/var/lib/grafana"]

volumes:
  grafana-data:

整套系统一台旧 N100 主机就能跑,日常功耗不到 10W。


八、总结:工程师改造家的终局心法

回看这三篇文章的递进逻辑:

阶段 工具 回答的问题
第 1 篇 大脑 + 调研 "为什么要改?改成什么?"
第 2 篇 Python + 笔记本 "单次怎么测?选型有没有效?"
第 3 篇 ESP32 + Grafana "长期有效吗?我能不能证明?"

这个递进其实就是工程师认知世界的标准流程先假设 → 再实验 → 最后长期观测验证

我觉得真正的 "DIY Home Improvement" 不是动手装个 IKEA 柜子,而是:

把家当成一个可观测(Observable)的系统——有指标、有日志、有告警、有 SLO。

噪音是家庭声学环境的一个 SLO 指标。装隔音窗是一次架构升级。30 天的 Grafana 数据,是这次架构升级的可用性报告

至于那扇窗到底值不值,30 天的数据已经替我回答了


附:下一步计划

  • [ ] 加入二氧化碳传感器(SCD40),做"空气+声学"双指标监控。
  • [ ] 多节点部署:卧室、客厅、书房分别一台,看声学空间分布。
  • [ ] 接入 HomeAssistant,噪音超标自动触发白噪音机。
  • [ ] 用 30 天数据训练一个小模型,预测噪音峰值时段(LSTM / Prophet)。

有兴趣一起折腾的评论区见。


免责声明:本文所述监控系统为个人爱好项目,使用的 INMP441 麦克风与校准方法不具备法律效力,数据仅作家庭自用参考。涉及的产品与服务选择均为个人工程决策记录,不构成商业推荐。

关键词:程序员居家办公、家庭噪音监控、ESP32 声学传感器、INMP441 I2S 麦克风、MQTT + InfluxDB + Grafana、FFT 实时频谱分析、A 计权分贝测量、Homelab 改造、深度工作环境搭建、隔音效果量化评估、平开窗与推拉窗的隔音对比、逸静隔音窗评测

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐