Weird topic in MQTT explorer - am I spamming broker?

I was torn between posting over on the Arduino forum but decided I would start here, I am having issues with a new project but it's a general question really.

If you see the below screenshot, there's a strange topic which is corrupted. As I have the client.publish in my void loop I think this may be the cause, correct?

My project is to capture a regular loud sound event (barking) and send this to node red, so the MAX4466 sensor is always sampling every 50ms.

I'm sure with more coding knowledge this can be allowed for in the code, but I am stuck at present. So I have used a switch and trigger node to filter the stream of results.

  • Firstly I was wondering if I am effectively "spamming" the MQTT server which is causing the circled problem?

  • Secondly, any general tips would be appreciated in processing the detected sound (I know there will be false alarms).

Full code:

/****************************************
  Based on:
  Example Sound Level Sketch for the Adafruit Microphone Amplifier
  https://learn.adafruit.com/adafruit-microphone-amplifier-breakout/measuring-sound-levels

  877dev
  Hardware: Wemos D1 mini and MAX4466 microphone amplifier
  Required behaviour:
  When no sound, publish "QUIET" to MQTT and turn OFF built in LED
  When sound occurs, publish "SOUND" to MQTT and turn ON built in LED
  //
****************************************/

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
//
#define wifi_ssid ""
#define wifi_password ""
//
#define mqtt_server "192.168.xx.xx"
#define mqtt_user ""
#define mqtt_password ""
//
#define sound_level_topic "garden/sound/level"
//
WiFiClient espClient;
PubSubClient client(espClient);
//
char *client_id = "NAME_HERE"; // MQTT Client ID


//MAX4466 configuration
const int sampleWindow = 50; // Sample window width in mS (50 mS = 20Hz)
unsigned int sample;
//
const float vMax = 3.2;  // 3.3V is the maximum



void setup()
{


  Serial.begin(9600);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  //
  while (!Serial) {} // Wait

  pinMode(LED_BUILTIN, OUTPUT);  // setup builtin LED for indication of high sound detected
}


void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(wifi_ssid);

  WiFi.begin(wifi_ssid, wifi_password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}


void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    // If you do not want to use a username and password, change next line to
    // if (client.connect("ESP8266Client")) {
    if (client.connect(client_id, mqtt_user, mqtt_password)) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}



void loop()
{

  //Check MQTT connected
  if (!client.connected())
  {
    reconnect();
  }
  client.loop();


  unsigned long startMillis = millis(); // Start of sample window
  unsigned int peakToPeak = 0;   // peak-to-peak level

  unsigned int signalMax = 0;
  unsigned int signalMin = 1024;

  // collect data for 50 mS
  while (millis() - startMillis < sampleWindow)
  {
    sample = analogRead(0);
    if (sample < 1024)  // toss out spurious readings
    {
      if (sample > signalMax)
      {
        signalMax = sample;  // save just the max levels
      }
      else if (sample < signalMin)
      {
        signalMin = sample;  // save just the min levels
      }
    }
  }
  peakToPeak = signalMax - signalMin;  // max - min = peak-peak amplitude
  double volts = (peakToPeak * 3.3) / 1024;  // convert to volts

  Serial.println(volts);

  if (volts > vMax) {
    // turn the LED on (HIGH is the voltage level)
    digitalWrite(LED_BUILTIN, LOW);  // Turns LED ON
    Serial.println("Sound detected");
    client.publish(sound_level_topic, String("SOUND").c_str(), false);
  } else {
    // turn the LED off by making the voltage LOW
    digitalWrite(LED_BUILTIN, HIGH); // Turns LED OFF
    client.publish(sound_level_topic, String("QUIET").c_str(), false);
  }
}


Very likely, you are sending an mqtt message every 50ms.

If bark detected = send mqtt (QOS 0 without retain), if not, don't send anything. Would be a better solution I would think ?

1 Like

I also don't know that you really need that last level of topic unless you are going to put something else in there too.

Personally, I wouldn't want to put a loop inside a loop, better to call a function from the main loop that tests and resets a ms variable, only trigger the processing if the elapsed time is > a threshold.

Now you can test for sound on every loop and once it is high enough, start recording your ms timer variable. On each loop where the sound is still high, continue to update the timer variable until you exceed the threshold. Then send the msg and reset the timer. If the sound level goes low, reset.

Also personally, I would want sound levels to be triggered over a longer period but of course that means that you need to allow for small periods of quiet that you would expect from a barking dog while it takes a breath. A 50ms sound could be triggered by all sorts of things. A repeated intermittent sound over, lets say, a minute, would be more likely to be a barking dog.

1 Like

I think yes you are correct it’s every 50ms. I could use millis to call a function I guess.

With your solution how would the topic get set the string back to “QUIET”. Does QoS 0 without retain get thrown away immediately?

Thank you, yes you’re correct about the topics but I like it to be organised for some reason.

Millis timer is also a great idea I will implement.

I need to think through the logic of what I need, I think overall I will have it detect every noise over a certain value, then I will have a gate node to allow/block the flow depending on whether it’s a barking episode or another noise.

Not perfect but detecting the barks seems quite difficult from my searches.

It will not be "replayed", any subscriber (node-red in this case) will get a notification when it's detected.

1 Like

Thanks, I set it to QoS 0 not retain and am experimenting with the results.

I'm noticing an issue in serial monitor which is causing unresponsiveness:
Attempting MQTT connection...failed, rc=-2 try again in 5 seconds

I think it's due to the number of analogRead's happening in void loop, but I need a lot to get the sample rate.

Looking into it...

Thanks @bakman2 and @TotallyInformation :+1:
Making changes as suggested by yourselves and it's working as expected so far.

I got rid of the "QUIET" message altogether and added a 100ms delay in reading the MAX4466 sound sensor, it's still responsive enough.

Now to test it outside! :laughing:

I am attaching the code in case anyone has a similar situation in the future:

/****************************************
  Based on:
  Example Sound Level Sketch for the Adafruit Microphone Amplifier
  https://learn.adafruit.com/adafruit-microphone-amplifier-breakout/measuring-sound-levels

  877dev
  Hardware: Wemos D1 mini and MAX4466 microphone amplifier
  Required behaviour:
  When no sound, publish "QUIET" to MQTT and turn OFF built in LED
  When sound occurs, publish "SOUND" to MQTT and turn ON built in LED
  //
  This is to capture a dog barking noise
  //
****************************************/

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
//
#define wifi_ssid ""
#define wifi_password ""
//
#define mqtt_server "192.168.xx.xx"
#define mqtt_user ""
#define mqtt_password ""
//
#define sound_level_topic "garden/sound/level"
//
WiFiClient espClient;
PubSubClient client(espClient);
//
char *client_id = "NAME_HERE"; // MQTT Client ID


//MAX4466 configuration
const int sampleWindow = 50; // Sample window width in mS (50 mS = 20Hz)
unsigned int sample;
//
const float vMax = 1.5;  // 3.3V is the maximum

unsigned long checkSoundTime;  // for calling of checkSound function time gap period
int checkSoundInterval = 100;


void setup()
{


  Serial.begin(9600);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  //
  while (!Serial) {} // Wait

  pinMode(LED_BUILTIN, OUTPUT);  // setup builtin LED for indication of high sound detected
}


void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(wifi_ssid);

  WiFi.begin(wifi_ssid, wifi_password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}


void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    // If you do not want to use a username and password, change next line to
    // if (client.connect("ESP8266Client")) {
    if (client.connect(client_id, mqtt_user, mqtt_password)) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}



void loop() {

  if (!client.connected())
  {
    reconnect();
  }
  client.loop();

  //check if interval has elapsed since sound was checked
  if (millis() > checkSoundTime + checkSoundInterval )
  {
    checkSound();
  }

}





void checkSound()
{

  checkSoundTime = millis(); //reset readTime to current millis, ensures this function is called every x seconds - see void loop

  unsigned long startMillis = millis(); // Start of sample window
  unsigned int peakToPeak = 0;   // peak-to-peak level

  unsigned int signalMax = 0;
  unsigned int signalMin = 1024;

  // collect data for 50 mS
  while (millis() - startMillis < sampleWindow)
  {
    sample = analogRead(0);
    if (sample < 1024)  // toss out spurious readings
    {
      if (sample > signalMax)
      {
        signalMax = sample;  // save just the max levels
      }
      else if (sample < signalMin)
      {
        signalMin = sample;  // save just the min levels
      }
    }
  }
  peakToPeak = signalMax - signalMin;  // max - min = peak-peak amplitude
  double volts = (peakToPeak * 3.3) / 1024;  // convert to volts

  Serial.println(volts);

  if (volts > vMax) {
    // turn the LED on (HIGH is the voltage level)
    digitalWrite(LED_BUILTIN, LOW);  // Turns LED ON
    Serial.println("Sound detected");
    client.publish(sound_level_topic, String("SOUND").c_str(), false);
  } else {
    // turn the LED off by making the voltage LOW
    digitalWrite(LED_BUILTIN, HIGH); // Turns LED OFF
  }
}

Just something else which may help.

Why every 50ms?

I know the sound of a bark is quite short, but..... 50ms?

Maybe try 500ms.

That will also reduce the load of the arduino.

Also I think it would be more flooding the broker rather than spamming it.
But that is just a term.

1 Like

Hi, I’m actually reading every 100ms, it’s the sample window which is 50ms long. So essentially 50ms sampling and 50ms resting. I may be wrong and am happy to be corrected.

Source:

Yes flooding is a much better description :+1: