Battery-powered ESP32 firmware that measures ambient light, temperature, calibrated sound level (dB) and battery charge, buffers readings to flash, and publishes them as JSON over MQTT.
Context — Proyecto Sueña (Red de Salud UC CHRISTUS). This firmware powers the sensor nodes of Proyecto Sueña, an initiative to monitor and improve the rest and sleep environment of hospitalized patients. Each node continuously measures the noise, light and temperature conditions at the bedside so that disruptive levels can be detected and acted upon — factors directly linked to patient sleep quality and recovery.
Proyecto Sueña is the firmware for a small, self-contained IoT sensor node built around an ESP32. Each node samples its environment every couple of seconds, computes a true A/C-weighted sound pressure level in real time using an assembly-optimised IIR filter, stores the readings as JSON lines in on-board flash (SPIFFS), and periodically wakes Wi-Fi to push the buffered batch to an MQTT broker. Between publishes the radio is switched off to save power, making the design suitable for battery operation.
The node also subscribes to an NTP-over-MQTT topic so the broker can push wall-clock time into the ESP32's internal RTC, allowing every reading to carry a real epoch timestamp.
- Light level — lux measurement via a BH1750 ambient-light sensor (I²C), with a software calibration factor.
- Temperature — read from a Bosch BME280 sensor (I²C, address
0x76). - Sound level (dB) — continuous Leq sound-pressure measurement from an INMP441 I²S MEMS microphone, with:
- per-microphone equalisation IIR filter (flattens the INMP441 response),
- selectable A-weighting / C-weighting frequency weighting,
- a hand-written ESP32 assembly SOS-IIR kernel for low-overhead, real-time filtering on a dedicated FreeRTOS task,
- 48 kHz sampling with overload / noise-floor clamping.
- Battery gauge — battery percentage derived from an ADC pin through a voltage divider, mapped to 0–100 %.
- Timestamping — RTC kept in sync via an NTP time string received over MQTT.
- Store-and-forward — readings are appended to a SPIFFS file as JSON and flushed to MQTT in batches.
- Power management — Wi-Fi is enabled only around a publish and CPU runs at 80 MHz.
- Self-healing — watchdog-style restarts on Wi-Fi connect failures or an obviously invalid timestamp.
| Function | Part / signal | ESP32 pin | Bus / notes |
|---|---|---|---|
| Microphone (sound) | INMP441 I²S MEMS | WS=19, SCK=18, SD=23 |
I²S peripheral I2S_NUM_0, 48 kHz, 24-bit, left channel |
| Light sensor | BH1750 | SDA/SCL (I²C) |
Default I²C address |
| Temp / humidity | Bosch BME280 | SDA/SCL (I²C) |
I²C address 0x76 (only temperature is read) |
| Battery sense | Voltage divider | GPIO36 (ADC1_CH0) |
analogRead mapped 2827–4350 → 0–100 % |
Pins are defined in
readNoise.h(I²S) andreadSensors.h(battery). I²C uses the ESP32 defaultWirepins (SDA=21,SCL=22). Adjust the#defines to match your board.
flowchart LR
subgraph Sensors
MIC[INMP441 I2S mic]
LUX[BH1750 lux]
BME[BME280 temp]
BAT[Battery ADC]
end
MIC -->|48 kHz I2S| DSP[SOS-IIR filter task<br/>EQ + A/C weighting]
DSP -->|Leq dB| AGG
LUX --> AGG[Reading aggregator]
BME --> AGG
BAT --> AGG
AGG -->|JSON line| FS[(SPIFFS buffer)]
FS -->|batch on timer| PUB[Publisher]
PUB -->|enable Wi-Fi| WIFI[Wi-Fi STA]
WIFI --> BROKER{{MQTT broker}}
BROKER -->|ntp/time| RTC[ESP32 RTC]
RTC --> AGG
| File | Responsibility |
|---|---|
V1.1.6.ino |
Main sketch: setup, timers, sensor read loop, JSON payload assembly and publish orchestration. |
wireless.h |
Wi-Fi station bring-up/tear-down and broker constants (credentials pulled from secrets.h). |
MQTTHandler.h |
PubSubClient setup, connect/reconnect, ntp/time subscription and the RTC time callback. |
readSensors.h |
BH1750 lux, BME280 temperature and battery-percentage reads. |
readNoise.h |
I²S microphone init, FreeRTOS reader task and Leq dB calculation. |
sos_IRR_Filter.h |
ESP32 assembly Second-Order-Sections IIR filter engine (© Ivan Kostoski, GPLv3). |
storageHandler.h |
SPIFFS helpers: init, start/append/clear and read-back of the JSON buffer file. |
neoTimer.h |
Lightweight non-blocking millis() timer class used to schedule sampling and publishing. |
SoftwareStack.h |
Small string helper utilities (split / String→char*). |
This is an ESP32 project and depends on ESP32-only APIs (I²S driver, SPIFFS, FreeRTOS, inline Xtensa assembly), so it must be built for an ESP32 target.
cp secrets.h.example secrets.h
# edit secrets.h with your Wi-Fi SSID/password and MQTT broker details- Install the ESP32 board package (Espressif) via the Boards Manager.
- Select your ESP32 board and enable a SPIFFS-capable partition scheme.
- Install these libraries from the Library Manager:
PubSubClient(Nick O'Leary)ESP32Time(fbiego)BH1750(Christopher Laws)Adafruit BME280+Adafruit Unified Sensor
- Open
V1.1.6.inoand upload.
A minimal platformio.ini:
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
knolleary/PubSubClient
fbiego/ESP32Time
claws/BH1750
adafruit/Adafruit BME280 Library
adafruit/Adafruit Unified SensorThe SOS-IIR engine, Wi-Fi, I²S and SPIFFS are part of the Arduino-ESP32 core and need no extra dependency.
| Direction | Topic | Payload |
|---|---|---|
| Publish | Sensores/7 |
JSON object(s) — one per reading, batched (see below). |
| Subscribe | ntp/time |
Date-time string YYYY-MM-DD HH:MM:SS used to set the RTC. |
- Broker: host/port from
secrets.h, TCP1883. - Client ID:
ESP32_clientID. - Publish topic is
MQTT_PUB_IDinV1.1.6.ino(defaultSensores/7, matchingidSensor = "7").
Each reading is a compact JSON object:
{ "id": "7", "ts": "1716902400", "l": "120", "t": "23.40", "n": "47.2", "b": "86" }| Key | Meaning | Unit |
|---|---|---|
id |
Sensor / node ID | — |
ts |
Epoch timestamp | seconds |
l |
Light level | lux |
t |
Temperature | °C |
n |
Sound pressure level (Leq) | dB |
b |
Battery | % |
| Setting | Where |
|---|---|
| Wi-Fi SSID / password | secrets.h (WIFI_SSID, WIFI_PASSWORD) |
| MQTT broker / user / password | secrets.h (MQTT_SERVER, MQTT_USER, MQTT_PASS) |
| Node ID & publish topic | idSensor, MQTT_PUB_ID in V1.1.6.ino |
| Sample interval | interval in V1.1.6.ino |
| Publish interval | eventTime_2_mqttPub in V1.1.6.ino |
| Frequency weighting (A/C) | WEIGHTING in readNoise.h |
| Microphone calibration | MIC_* defines in readNoise.h |
| Lux calibration factor | bh1750Factor in readSensors.h |
| Battery ADC mapping | readBatt() in readSensors.h |
| I²S / I²C pins | readNoise.h / Wire.begin() |
Current firmware: V1.1.6 (sketch V1.1.6.ino; FIRMWARE_VERSION = "1.1"). See the header comment block in the sketch for the change history of earlier revisions.
The real-time sound-level pipeline (the assembly SOS-IIR filter in sos_IRR_Filter.h, the INMP441 equaliser and the A/C-weighting coefficients) is based on Ivan Kostoski's ESP32 sound-level-meter work and is distributed under the GNU GPL v3 — see the header in sos_IRR_Filter.h.
The original code in this repository is released under the MIT License — see LICENSE.
Note that sos_IRR_Filter.h is third-party code under GPLv3; if you redistribute the combined firmware, honour that license for that file.