15 of June 2024
This template is for the Gravity Analog Sound Meter released by DFRobot. The code has some boilerplate functionality where the template is able to connect to a designated Wifi network, as well as listen to over the wire OTP updates, whilst continuously polling the Gravity Analog Sound Meter and publishing the measured value to an MQTT bus.
At the time of writing, there is no runtime functionality and the template should just be modified in order to set the Wifi and/or OTP credentials after which it can be wired up and left to run on its own.
The theory behind the template revolves around the linear design of the Analog Sound Meter in that the output voltage is calibrated out of production to correspond to some amount of decibels. As per the specifications, the noise level that the Analog Sound Meter is capable of registering ranges from to with those two values corresponding to , respectively the maximum voltage that the analog pin is capable of reading (for most micro-arduinos, such as the WeMoS, that would be ). Given the linear progression, the only thing left to do is just measure the voltage of the analog pin connected to the meter's signal pin and then linearly map the voltage range into the decibels range .
Here is the code responsible for measuring noise levels using DFRobot's Gravity Analog Sound Level Meter 1.0 within the template provided in the code section:
// https://grimore.org/fuss/mathematics/algebra#linearly_map_a_value_in_a_range_into_another_range float mapValueToRange(float value, float xMin, float xMax, float yMin, float yMax) { return yMin + ((yMax - yMin) * (value - xMin)/(xMax - xMin)); } // measure noise level (dbA) float ai, vc, noise; ai = analogRead(DECIBELMETER_PIN_ANALOG); vc = mapValueToRange(ai, 0, 4095, 0, MAX_ANALOG_VOLTAGE); noise = vc * 50.0;
where:
DECIBELMETER_PIN_ANALOG
is the analog pin that the Analog Sound Level Meter connects to on the ESP,MAX_ANALOG_VOLTAGE
is the maximum in voltage on the ESP (sometimes ranges from to but can also range from to )
As can be observed, after the value from the analog pin is measured, the value is mapped to the voltage range of the pin. The resulting scaled voltage is then multiplied by , which is a cheap operation compared to mapValueToRange
that is meant to scale the measured voltage to the decibel range, based on the observation that the lowest voltage corresponding to the lowest amount of decibels that the board can measure, when multiplied by yield a nice lowest decibel value of .
Aside the boilerplate Arduino code, the former short snippet is the whole nucleus that is responsible of measuring the noise level that is characteristic to the provided template in the code section.
/*************************************************************************/ /* Copyright (C) 2024 Wizardry and Steamworks - License: GNU GPLv3 */ /*************************************************************************/ // Removing comment for debugging over the first serial port. #define DEBUG 1 // The AP to connect to via Wifi. #define STA_SSID "" // The AP Wifi password. #define STA_PSK "" // The MQTT broker to connect to. #define MQTT_HOST "" // The MQTT broker username. #define MQTT_USERNAME "" // The MQTT broker password. #define MQTT_PASSWORD "" // The MQTT broker port. #define MQTT_PORT 1883 // The default MQTT client ID is "esp-CHIPID" where CHIPID is the ESP8266 // or ESP32 chip identifier. #define MQTT_CLIENT_ID() String("esp-" + String(GET_CHIP_ID(), HEX)) // The authentication password to use for OTA updates. #define OTA_PASSWORD "" // The OTA port on which updates take place. #define OTA_PORT 8266 // The default topic that the sketch subscribes to is "esp/CHIPID" where // CHIPID is the ESP8266 or ESP32 chip identifier. #define MQTT_TOPIC() String("esp/" + String(GET_CHIP_ID(), HEX)) // Platform specific defines. #if defined(ARDUINO_ARCH_ESP8266) #define GET_CHIP_ID() (ESP.getChipId()) #elif defined(ARDUINO_ARCH_ESP32) #define GET_CHIP_ID() ((uint16_t)(ESP.getEfuseMac() >> 32)) #endif // Miscellaneous defines. //#define CHIP_ID_HEX (String(GET_CHIP_ID()).c_str()) #define HOSTNAME() String("esp-" + String(GET_CHIP_ID(), HEX)) // Platform specific libraries. #if defined(ARDUINO_ARCH_ESP8266) #include <ESP8266WiFi.h> #include <ESP8266mDNS.h> #elif defined(ARDUINO_ARCH_ESP32) #include <WiFi.h> #include <ESPmDNS.h> #endif // General libraries. #include <WiFiUdp.h> #include <ArduinoOTA.h> #include <PubSubClient.h> #include <ArduinoJson.h> #if defined(ARDUINO_ARCH_ESP32) #include <FS.h> #include <SPIFFS.h> #endif WiFiClient espClient; PubSubClient mqttClient(espClient); #define DECIBELMETER_PIN_ANALOG 32 #define MAX_ANALOG_VOLTAGE 3.3 // https://grimore.org/fuss/mathematics/algebra#linearly_map_a_value_in_a_range_into_another_range float mapValueToRange(float value, float xMin, float xMax, float yMin, float yMax) { return yMin + ( ( yMax - yMin ) * ( value - xMin ) / ( xMax - xMin ) ); } void mqttCallback(char *topic, byte *payload, unsigned int length) { String msgTopic = String(topic); // payload is not null terminated and casting will not work char msgPayload[length + 1]; snprintf(msgPayload, length + 1, "%s", payload); #ifdef DEBUG Serial.println("Message received on topic: " + String(topic) + " with payload: " + String(msgPayload)); #endif // Parse the payload sent to the MQTT topic as a JSON document. StaticJsonDocument<256> doc; #ifdef DEBUG Serial.println("Deserializing message...."); #endif DeserializationError error = deserializeJson(doc, msgPayload); if (error) { #ifdef DEBUG Serial.println("Failed to parse MQTT payload as JSON: " + String(error.c_str())); #endif return; } } bool mqttConnect() { #ifdef DEBUG Serial.println("Attempting to connect to MQTT broker: " + String(MQTT_HOST)); #endif mqttClient.setServer(MQTT_HOST, MQTT_PORT); StaticJsonDocument<256> msg; if (mqttClient.connect(MQTT_CLIENT_ID().c_str(), MQTT_USERNAME, MQTT_PASSWORD)) { #ifdef DEBUG Serial.println("Established connection with MQTT broker using client ID: " + MQTT_CLIENT_ID()); #endif // Uncomment this in order to receive messages from MQTT. //mqttClient.setCallback(mqttCallback); msg["action"] = "connected"; char output[256]; serializeJson(msg, output, 256); mqttClient.publish(MQTT_TOPIC().c_str(), output); #ifdef DEBUG Serial.println("Attempting to subscribe to MQTT topic: " + MQTT_TOPIC()); #endif if (!mqttClient.subscribe(MQTT_TOPIC().c_str())) { #ifdef DEBUG Serial.println("Failed to subscribe to MQTT topic: " + MQTT_TOPIC()); #endif return false; } #ifdef DEBUG Serial.println("Subscribed to MQTT topic: " + MQTT_TOPIC()); #endif msg.clear(); msg["action"] = "subscribed"; serializeJson(msg, output, 256); mqttClient.publish(MQTT_TOPIC().c_str(), output); return true; } #ifdef DEBUG Serial.println("Connection to MQTT broker failed with MQTT client state: " + String(mqttClient.state())); #endif return false; } bool satifsyMessagePumps() { // Process OTA loop first since emergency OTA updates might be needed. ArduinoOTA.handle(); // Process MQTT client loop. if (!mqttClient.connected()) { // If the connection to the MQTT broker has failed then sleep before carrying on. if (!mqttConnect()) { return false; } } mqttClient.loop(); return true; } // the setup function runs once when you press reset or power the board void setup() { // initialize the GPIO pins taht will be used pinMode (DECIBELMETER_PIN_ANALOG, INPUT); Serial.begin(19200); #ifdef DEBUG Serial.println("Booted, setting up Wifi in 10s..."); #endif delay(10000); WiFi.mode(WIFI_STA); #if defined(ARDUINO_ARCH_ESP8266) WiFi.hostname(HOSTNAME().c_str()); #elif defined(ARDUINO_ARCH_ESP32) WiFi.setHostname(HOSTNAME().c_str()); #endif WiFi.begin(STA_SSID, STA_PSK); while (WiFi.waitForConnectResult() != WL_CONNECTED) { #ifdef DEBUG Serial.println("Failed to connect to Wifi, rebooting in 5s..."); #endif delay(5000); ESP.restart(); } #ifdef DEBUG Serial.print("Connected to Wifi: "); Serial.println(WiFi.localIP()); Serial.println("Setting up OTA in 10s..."); #endif delay(10000); // Port defaults to 8266 ArduinoOTA.setPort(OTA_PORT); // Hostname defaults to esp-[ChipID] ArduinoOTA.setHostname(HOSTNAME().c_str()); // Set the OTA password ArduinoOTA.setPassword(OTA_PASSWORD); ArduinoOTA.onStart([]() { switch (ArduinoOTA.getCommand()) { case U_FLASH: // Sketch #ifdef DEBUG Serial.println("OTA start updating sketch."); #endif break; #if defined(ARDUINO_ARCH_ESP8266) case U_FS: #elif defined(ARDUINO_ARCH_ESP32) case U_SPIFFS: #endif #ifdef DEBUG Serial.println("OTA start updating filesystem."); #endif SPIFFS.end(); break; default: #ifdef DEBUG Serial.println("Unknown OTA update type."); #endif break; } }); ArduinoOTA.onEnd([]() { #ifdef DEBUG Serial.println("OTA update complete."); #endif SPIFFS.begin(); #if defined(ARDUINO_ARCH_ESP8266) // For what it's worth, check the filesystem on ESP8266. SPIFFS.check(); #endif ESP.restart(); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { #ifdef DEBUG Serial.printf("OTA update progress: %u%%\r", (progress / (total / 100))); #endif }); ArduinoOTA.onError([](ota_error_t error) { #ifdef DEBUG Serial.printf("OTA update error [%u]: ", error); #endif switch (error) { case OTA_AUTH_ERROR: #ifdef DEBUG Serial.println("OTA authentication failed"); #endif break; case OTA_BEGIN_ERROR: #ifdef DEBUG Serial.println("OTA begin failed"); #endif break; case OTA_CONNECT_ERROR: #ifdef DEBUG Serial.println("OTA connect failed"); #endif break; case OTA_RECEIVE_ERROR: #ifdef DEBUG Serial.println("OTA receive failed"); #endif break; case OTA_END_ERROR: #ifdef DEBUG Serial.println("OTA end failed"); #endif break; default: #ifdef DEBUG Serial.println("Unknown OTA failure"); #endif break; } ESP.restart(); }); ArduinoOTA.begin(); // Set up MQTT client. mqttClient.setServer(MQTT_HOST, MQTT_PORT); // Touchdown. #ifdef DEBUG Serial.println("Setup complete."); #endif } // the loop function runs over and over again forever void loop() { // Check the Wifi connection status. int wifiStatus = WiFi.status(); switch (wifiStatus) { case WL_CONNECTED: if (!satifsyMessagePumps()) { delay(1000); break; } delay(1); break; case WL_NO_SHIELD: #ifdef DEBUG Serial.println("No Wifi shield present."); #endif goto DEFAULT_CASE; break; case WL_NO_SSID_AVAIL: #ifdef DEBUG Serial.println("Configured SSID not found."); #endif goto DEFAULT_CASE; break; // Temporary statuses indicating transitional states. case WL_IDLE_STATUS: case WL_SCAN_COMPLETED: delay(1000); break; // Fatal Wifi statuses trigger a delayed ESP restart. case WL_CONNECT_FAILED: case WL_CONNECTION_LOST: case WL_DISCONNECTED: default: #ifdef DEBUG Serial.println("Wifi connection failed with status: " + String(wifiStatus)); #endif DEFAULT_CASE: delay(10000); ESP.restart(); break; } // measure noise level (dBA) float ai, vc, noise; ai = analogRead(DECIBELMETER_PIN_ANALOG);` vc = mapValueToRange(ai, 0, 4095, 0, MAX_ANALOG_VOLTAGE); noise = vc * 50.0; #ifdef DEBUG Serial.println("Noise (dBA): " + String(noise)); #endif StaticJsonDocument<256> msg; msg["noise"] = noise; char output[256]; serializeJson(msg, output, 256); mqttClient.publish(MQTT_TOPIC().c_str(), output); delay(1000); }