Skip to content

Instantly share code, notes, and snippets.

@ChronSyn
Last active February 20, 2024 15:40
Show Gist options
  • Save ChronSyn/9128188d6e453f2ee41667fa18a78b84 to your computer and use it in GitHub Desktop.
Save ChronSyn/9128188d6e453f2ee41667fa18a78b84 to your computer and use it in GitHub Desktop.
ESPHome sound monitor with ESP32 and KY-037

ESPHome sound monitor using ESP32 and KY-037

I read a lot of reports online of these sensors being unreliable, or being difficult to tune. The latter part is partly true, but there's a lot of misunderstanding about how to interpret the data. Once you understand how you should interpret it, it becomes much clearer how you should calibrate it and how to configure your ESPHome device to use the data it provides.

My first thought was higher measured voltage = louder noise. After all, every sensor I'd worked with worked in this way, but most of them reported the 'human friendly' value - e.g. temperature, CO2, humidity, etc.

Sound is different. You probably know that sound is made up of waves, and you've probably seen a waveform corresponding to audio.

Louder sounds aren't represented by just a higher peak, but by a higher deviation. For example, your baseline is set at 0. When a sound happens, the waveform starts and you have values that are positive and negative - so shouting might be represented as many points going from -0.5, then to +0.5, then back to -0.5, then to +0.5, etc.

The KY-037 works in this way too. When the noise is 'ambient' (in this context, that just means you're remaining quiet to measure the 'quiet' voltage), the voltage fluctuates only slighty. When you make noise, the voltage fluctuates wildly above and below the 'ambient' values. More noise = more fluctuation.

With this knowledge at hand, I was able to put together the appropriate lambda which compares current measured voltage against the previous measured voltage and determine the deviation (aka the delta).

Using abs() on this value ensured that the negative values become positive - e.g. -0.27 becomes 0.27. By sampling this frequently, and then using a moving window average, I was able to calculate the average 'noise level' in the environment the sensor is in.

To calibrate this, I used a sound meter app on my phone. I don't need it to be ultra-precise, I just needed it to be good enough. I sat silently, measuring the delta over a time and seeing what the sound meter app said. That corresponded to my 'quiet' measurement.

Then, I loaded up a 100hz test tone video on youtube, and used my headphones and the app to measure a constant noise level. This would be my 'noisy room' condition. Then, I used the exact same video and headphones with the KY-037 setup and measured the delta.

From this, I was able to adjust the potentiometer on the KY-037 and re-measure. Eventually, I managed to get the values and tuning dialled in to satisfactory levels and ultimately add environmental sound quality monitoring to my other measurements.

The files

Note: You should adjust the values in the calibrate_linear according to your own specific conditions and calibrated measurements

esphome-esp32-ky037.yml

  • This file (esphome-esp32-ky037.yml) is useful for short-term updates of values. It smoothes the output values to avoid heavy fluctuations, but is only useful for looking at short-term trends (e.g. < 30 minutes).

esphome-esp32-ky037-long-duration.yml

  • This file (esphome-esp32-ky037-long-duration.yml) is useful for long-term updates. It averages the input values using a 5-minute window, and sends 1 value every 15 seconds. The output values (after passing through various functions) are also smoothed over a 5-minute window, and 1 value is sent every 15 seconds. This version would be most useful for a home-assistant dashboard.
esphome:
name: esphome-web-sound-monitor
friendly_name: Sound Monitor
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "a-long-encryption-key-should-be-placed-here"
ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Esphome-Web-sound-monitor"
password: "your-esphome-fallback-wifi-password"
captive_portal:
sensor:
# This is the source sensor for all the below
# A frequent update interval allows us to sample a lot of times per second while ignoring many minor variations in the measurements
- platform: adc
pin: 35
name: ky037_analog_raw
id: ky037_analog_raw
accuracy_decimals: 2
update_interval: 100ms
unit_of_measurement: v
attenuation: auto
internal: True
filters:
- sliding_window_moving_average:
send_every: 150
send_first_at: 150
window_size: 3000
- platform: copy
source_id: ky037_analog_raw
name: "ky037_lambda"
id: "ky037_lambda"
internal: True
unit_of_measurement: dB
accuracy_decimals: 2
filters:
# We use a lambda which calculates the deviation between previous and current value
# Then it uses abs() to ensure it's positive (e.g. -0.27 becomes 0.27)
- lambda: |-
static float last_value = 0;
float change = 0;
change = x - last_value;
last_value = x;
return abs(change);
# Calibrates the values from values to suitable linear range
- platform: copy
source_id: ky037_lambda
name: "ky037_calibrate_linear"
id: "ky037_calibrate_linear"
internal: True
unit_of_measurement: dB
accuracy_decimals: 2
filters:
- calibrate_linear:
# Use the values measured from `ky037_analog long abs deviation` as your `calibrate_linear` filter values
# First value = low value = quiet condition
# Last value = high value = calibrated against a loud source (e.g. 100hz test tone and headphones measured using a phone app)
# You can add more values if needed
- 0.000001 -> 22.0
- 0.000150 -> 30.0
- 0.000350 -> 45.0
- 0.000550 -> 55.0
- 0.000750 -> 65.0
# Clamps values to beyween 15 and 80
- platform: copy
source_id: ky037_calibrate_linear
name: "ky037_clamp"
id: "ky037_clamp"
internal: True
unit_of_measurement: dB
accuracy_decimals: 2
filters:
- clamp:
# We clamp the results to 15dB low and 80dB high
# Feel free to adjust these as you deem fit
max_value: 80
min_value: 15
# This is the output which should be used as your measured DB value
- platform: copy
source_id: ky037_clamp
name: "ky037_analog"
id: "ky037_analog"
unit_of_measurement: dB
accuracy_decimals: 2
on_value:
then:
- logger.log:
format: "ky037_analog - Measured %.1f"
args: ['id(ky037_analog).state']
level: INFO
filters:
- sliding_window_moving_average:
# This will average values over 5 minutes, and send a reading every 15 second
send_every: 1
send_first_at: 1
window_size: 20
esphome:
name: esphome-web-sound-monitor
friendly_name: Sound Monitor
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "a-long-encryption-key-should-be-placed-here"
ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Esphome-Web-sound-monitor"
password: "your-esphome-fallback-wifi-password"
captive_portal:
sensor:
# This is the source sensor for all the below
# A frequent update interval allows us to sample a lot of times per second while ignoring many minor variations in the measurements
- platform: adc
pin: 35
name: ky037_analog_raw
id: ky037_analog_raw
accuracy_decimals: 2
update_interval: 100ms
unit_of_measurement: v
attenuation: auto
internal: True
# This is the short-time deviation
# It uses 5-second moving window and sends a result every 1 second
- platform: copy
source_id: ky037_analog_raw
name: "ky037_analog abs deviation"
unit_of_measurement: dB
accuracy_decimals: 6
internal: True
filters:
- lambda: |-
static float last_value = 0;
float change = 0;
change = x - last_value;
last_value = x;
return abs(change);
- sliding_window_moving_average:
send_every: 10
send_first_at: 10
window_size: 50
# Use this sensor to view the long-time deviation
# It takes the average over 30 seconds and sends the results every 10 seconds
# Very useful for calibrating as less susceptible to minor natural deviations
- platform: copy
source_id: ky037_analog_raw
name: "ky037_analog long abs deviation"
unit_of_measurement: dB
accuracy_decimals: 6
filters:
- lambda: |-
static float last_value = 0;
float change = 0;
change = x - last_value;
last_value = x;
return abs(change);
- sliding_window_moving_average:
send_every: 100
send_first_at: 100
window_size: 300
# We'll use the same lambda but apply linear calibration and clamp filters
- platform: copy
source_id: ky037_analog_raw
name: "ky037_analog_deviation"
id: "ky037_analog_deviation"
internal: True
unit_of_measurement: dB
accuracy_decimals: 2
filters:
# We use a lambda which calculates the deviation between previous and current value
# Then it uses abs() to ensure it's positive (e.g. -0.27 becomes 0.27)
- lambda: |-
static float last_value = 0;
float change = 0;
change = x - last_value;
last_value = x;
return abs(change);
- calibrate_linear:
# Use the values measured from `ky037_analog long abs deviation` as your `calibrate_linear` filter values
# First value = low value = quiet condition
# Last value = high value = calibrated against a loud source (e.g. 100hz test tone and headphones measured using a phone app)
# You can add more values if needed
- 0.004940 -> 31
- 0.007600 -> 75
- sliding_window_moving_average:
# This will average values over 30 seconds, and send a reading every 5 seconds
send_every: 50
send_first_at: 50
window_size: 300
- clamp:
# We clamp the results to 15dB low and 80dB high
# Feel free to adjust these as you deem fit
max_value: 80
min_value: 15
# This allows us to average based on the above lambda, and potentially avoid the super-high first reading
# It should also smooth out significant deviations further
# This is the output which should be used as your measured DB value
- platform: copy
source_id: ky037_analog_deviation
name: "ky037_analog"
id: "ky037_analog"
unit_of_measurement: dB
accuracy_decimals: 2
filters:
- sliding_window_moving_average:
# This will average values over 2 minutes, and send a reading every 30 seconds
send_every: 6
send_first_at: 6
window_size: 24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment