MQTT spam with arduino button

Hello,

My goal is to get a working home automatisation system with pyshical push buttons that will control outputs, but at the same time have Openhab control these outputs as well by means of MQTT.

So far I managed to get the communication bit working where I publish messages with Mosquitto on my Raspberry pi.
I can also control the outputs using my smartphone, but the problem comes when I try to move on to the next step in my project: Publishing messages to Mosquitto whenever a state change happens with my physical push button.

The pushbutton works properly using edge detection (see code below), but an unexpected thing happens when I change the command from pinoutput to publishing on Mosquitto: A massive spam.

I guess this is to be expected since otherwise an output like a LED wouldn’t stay turned on, but I fear that if I keep it like this and have up to 30 pushbuttons do the same thing, my Raspberry Pi won’t be handle it since it’s supposed to run 24/7.

How can I make it so that instead of it spamming, it will instead publish the message once upon state change?

If you have any other suggestions on how I can solve this then please go on ahead.

Also, looking forward I see another problem coming up: If the output is high (light on) and I change the output to low on my smartphone, how will the arduino sketch change the state low as well? But first things first :wink:

Code:

#include <SPI.h>
#include <PubSubClient.h>
#include <Ethernet.h>


#define MQTT_SERVER_NAME "192.168.1.122"
#define MQTT_SERVER_PORT 1883
#define MQTT_CLIENTID   "test"
#define MQTT_USER_NAME  "openhab"
#define MQTT_USER_PASS  NULL
#define MQTT_DOMAIN     "openhab"

#define MQTT_TOPIC_LIGHT  MQTT_DOMAIN"/"MQTT_CLIENTID"/light"
#define MQTT_TOPIC_CTRL   MQTT_DOMAIN"/"MQTT_CLIENTID"/control"


// MAC Address of Arduino Ethernet Sheild (on sticker on shield)
byte MAC_ADDRESS[] = {  0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 };
byte ip[] = {192,168,1,105}; 
PubSubClient client;
EthernetClient eclient;

// Pin 9 is the LED output pin
int PIN_LED = 9;

// defines and variable for sensor/control mode
#define MODE_OFF    0  // not sensing light, LED off
#define MODE_ON     1  // not sensing light, LED on
int senseMode;

// Variables will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button



int PIN_INPUT_2 = 2;         // the number of the input pin
int PIN_OUTPUT_7 = 7;       // the number of the output pin

unsigned long time;
unsigned long runNumber = 0;

char message_buff[100];

void setup()
{
  // initialize the digital pin as an output.
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_OUTPUT_7, OUTPUT);
  pinMode(PIN_INPUT_2, INPUT);


  // init serial link for debugging
  Serial.begin(9600);
Ethernet.begin(MAC_ADDRESS, ip);
  if (Ethernet.begin(MAC_ADDRESS) == 0)
  {
    Serial.println("Failed to configure Ethernet using DHCP");
    return;
  }
  // print your local IP address:
  Serial.print("Local IP=");
  Serial.println(Ethernet.localIP());

  client = PubSubClient(MQTT_SERVER_NAME, MQTT_SERVER_PORT, callback, eclient);
}

void loop()
{
  if (!client.connected())
  {
    Serial.println("Reconnect to mqtt");

    // clientID, username, MD5 encoded password
    if ( client.connect(MQTT_CLIENTID, MQTT_USER_NAME, MQTT_USER_PASS) ) {
      Serial.println("   Connect client ["MQTT_CLIENTID"] OK");
      if ( client.publish(MQTT_TOPIC_LIGHT, "I'm alive "__TIME__)) {
        Serial.println("   Publishing alive message to ["MQTT_TOPIC_LIGHT"] OK");
        if ( client.subscribe(MQTT_TOPIC_CTRL) ) {
          Serial.println("   Subscribe to ["MQTT_TOPIC_CTRL"] OK");
        } else {
          Serial.println("   subscribe failed");
        }
      } else {
        Serial.println("   publish failed");
      }
    } else {
      Serial.println("   connect failed");
    }
  }









 // read the pushbutton input pin:
  buttonState = digitalRead(PIN_INPUT_2);

  // compare the buttonState to its previous state
  if (buttonState != lastButtonState) {
    // if the state has changed, increment the counter
    if (buttonState == HIGH) {
      // if the current state is HIGH then the button
      // went from off to on:
      buttonPushCounter++;
      Serial.println("on");
      Serial.print("number of button pushes:  ");
      Serial.println(buttonPushCounter);
    } else {
      // if the current state is LOW then the button
      // went from on to off:
      Serial.println("off");
    }
    // Delay a little bit to avoid bouncing
    delay(200);
  }
  // save the current state as the last state,
  //for next time through the loop
  lastButtonState = buttonState;


  // turns on the LED every four button pushes by
  // checking the modulo of the button push counter.
  // the modulo function gives you the remainder of
  // the division of two numbers:
  if (buttonPushCounter % 2 == 0) {
    client.publish(MQTT_TOPIC_LIGHT, "LED_7_ON");
 
  } else {
    client.publish(MQTT_TOPIC_LIGHT, "LED_7_OFF");
  }


  switch (senseMode) {
    case MODE_OFF:
      // light should be off
      digitalWrite(PIN_LED, LOW);
      break;
    case MODE_ON:
      // light should be on
      digitalWrite(PIN_LED, HIGH);
      break;
  }

  // MQTT client loop processing
  client.loop();
}





// handles message arrived on subscribed topic(s)
void callback(char * topic, byte * payload, unsigned int length) {

  int i = 0;

  Serial.println("Message arrived:  topic: " + String(topic));
  Serial.println("Length: " + String(length, DEC));

  // create character buffer with ending null terminator (string)
  for (i = 0; i < length; i++) {
    message_buff[i] = payload[i];
  }
  message_buff[i] = '\0';

  String msgString = String(message_buff);

  Serial.println("Payload: " + msgString);

  if (msgString.equals("OFF")) {
    senseMode = MODE_OFF;
    Serial.println("Switching to OFF mode");
  } else if (msgString.equals("ON")) {
    senseMode = MODE_ON;
    Serial.println("Switching to ON mode");
  } 
}

Just to be clear, you’re defining spam as sending the same info to the same topic multiple times within a second or two?

Yes.

if (buttonPushCounter % 2 == 0) {
    client.publish(MQTT_TOPIC_LIGHT, "LED_7_ON");;
 
  } else {
    client.publish(MQTT_TOPIC_LIGHT, "LED_7_OFF");;
  }

Whenever either one of these 2 states is reached, the Mosquitto terminal is being flooded with it until I push the button, then it’s being flooded with the other state.

You’re telling it to! :smirk:

It’s within the main loop and so it runs that if statement every time but I think that your intended outcome is to have it within the if (buttonState != lastButtonState) statement.

1 Like

Well, now I feel silly :stuck_out_tongue_winking_eye:

This code makes it work

#include <SPI.h>
#include <PubSubClient.h>
#include <Ethernet.h>


#define MQTT_SERVER_NAME "192.168.1.122"
#define MQTT_SERVER_PORT 1883
#define MQTT_CLIENTID   "test"
#define MQTT_USER_NAME  "openhab"
#define MQTT_USER_PASS  NULL
#define MQTT_DOMAIN     "openhab"

#define MQTT_TOPIC_LIGHT  MQTT_DOMAIN"/"MQTT_CLIENTID"/light"
#define MQTT_TOPIC_CTRL   MQTT_DOMAIN"/"MQTT_CLIENTID"/control"


// MAC Address of Arduino Ethernet Sheild (on sticker on shield)
byte MAC_ADDRESS[] = {  0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 };
byte ip[] = {192,168,1,105}; 
PubSubClient client;
EthernetClient eclient;
PubSubClient client2;



// Pin 9 is the LED output pin
int PIN_LED = 9;

// defines and variable for sensor/control mode
#define MODE_OFF    0  // not sensing light, LED off
#define MODE_ON     1  // not sensing light, LED on
int senseMode;

#define MODE_OFF2    0  // not sensing light, LED off
#define MODE_ON2     1  // not sensing light, LED on
int senseMode2;


int state2 = HIGH; // the current state of the output pin
int reading2; // the current reading from the input pin
int previous2 = LOW; // the previous reading from the input pin
// the follow variables are long's because the time, measured in milliseconds
// will quickly become a bigger number than can be stored in an int.
long time2 = 0;                   // the last time the output pin was toggled
long debounce2 = 200;      // the debounce time, increase if the output flickers



int PIN_INPUT_2 = 2;         // the number of the input pin
int PIN_OUTPUT_7 = 7;       // the number of the output pin

unsigned long time;
unsigned long runNumber = 0;

char message_buff[100];

void setup()
{
  // initialize the digital pin as an output.
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_OUTPUT_7, OUTPUT);
  pinMode(PIN_INPUT_2, INPUT);


  // init serial link for debugging
  Serial.begin(9600);
Ethernet.begin(MAC_ADDRESS, ip);
  if (Ethernet.begin(MAC_ADDRESS) == 0)
  {
    Serial.println("Failed to configure Ethernet using DHCP");
    return;
  }
  // print your local IP address:
  Serial.print("Local IP=");
  Serial.println(Ethernet.localIP());

  client = PubSubClient(MQTT_SERVER_NAME, MQTT_SERVER_PORT, callback, eclient);
 
}

void loop()
{
  if (!client.connected())
  {
    Serial.println("Reconnect to mqtt");

    // clientID, username, MD5 encoded password
    if ( client.connect(MQTT_CLIENTID, MQTT_USER_NAME, MQTT_USER_PASS) ) {
      Serial.println("   Connect client ["MQTT_CLIENTID"] OK");
      if ( client.publish(MQTT_TOPIC_LIGHT, "I'm alive "__TIME__)) {
        Serial.println("   Publishing alive message to ["MQTT_TOPIC_LIGHT"] OK");
        if ( client.subscribe(MQTT_TOPIC_CTRL) ) {
          Serial.println("   Subscribe to ["MQTT_TOPIC_CTRL"] OK");
        } else {
          Serial.println("   subscribe failed");
        }
       if ( client.subscribe(MQTT_TOPIC_LIGHT) ) {
          Serial.println("   Subscribe to ["MQTT_TOPIC_LIGHT"] OK");
        } else {
          Serial.println("   subscribe failed");
        }
      } else {
        Serial.println("   publish failed");
      }
    } else {
      Serial.println("   connect failed");
    }
  }









  reading2 = digitalRead(PIN_INPUT_2);

 // if the input just went from LOW and HIGH and we've waited long enough
 // to ignore any noise on the circuit, toggle the output pin and remember
 // the time
 if (reading2 == HIGH && previous2 == LOW && millis() - time2 > debounce2) {
   if (state2 == HIGH){
     state2 = LOW;
        client.publish(MQTT_TOPIC_LIGHT, "OFF");
     Serial.println("Button B is Pressed and message published");}
   else
   {
     state2 = HIGH;
     client.publish(MQTT_TOPIC_LIGHT, "ON");
     Serial.println("Button B is Pressed and message published");
   }
   time2 = millis(); 
 }
// digitalWrite(PIN_OUTPUT_7, state2);
 
 previous2 = reading2;
   



  switch (senseMode) {
    case MODE_OFF:
      // light should be off
      digitalWrite(PIN_LED, LOW);
      break;
    case MODE_ON:
      // light should be on
      digitalWrite(PIN_LED, HIGH);
      break;
  }

 switch (senseMode2) {
    case MODE_OFF2:
      // light should be off
      digitalWrite(PIN_OUTPUT_7, LOW);
      break;
    case MODE_ON2:
      // light should be on
      digitalWrite(PIN_OUTPUT_7, HIGH);
      break;
  }


  // MQTT client loop processing
  client.loop();
}





// handles message arrived on subscribed topic(s)
void callback(char * topic, byte * payload, unsigned int length) {

  int i = 0;

  Serial.println("Message arrived:  topic: " + String(topic));
  Serial.println("Length: " + String(length, DEC));

  // create character buffer with ending null terminator (string)
  for (i = 0; i < length; i++) {
    message_buff[i] = payload[i];
  }
  message_buff[i] = '\0';

  String msgString = String(message_buff);

  Serial.println("Payload: " + msgString);

  if (msgString.equals("OFF")) {
    senseMode = MODE_OFF;
    Serial.println("Switching to OFF mode");
  } else if (msgString.equals("ON")) {
    senseMode = MODE_ON;
    Serial.println("Switching to ON mode");
  } 
  if (msgString.equals("ON")) {
    senseMode2 = MODE_ON2;
    Serial.println("Switching to ON mode");
  } 
   else if (msgString.equals("OFF")) {
    senseMode2 = MODE_OFF2;
    Serial.println("Switching to ON mode");
  } 
}

But now on to my next and final problem before I can call this project of mine a succes!

I want Openhab and the Arduino to communicate with each other properly and by that I mean create a proper feedback loop.

As you all know Openhab has a function to do this using MQTT

{mqtt=">[mosquitto:openhab/test/light:command:OFF:OFF],>[mosquitto:openhab/test/light:command:ON:ON],<[mosquitto:openhab/test/light:state:default]" } 

The first 2 are to turn it on/off and the 3 one is to create a feedback loop to openhab that turns the input in the Openhab UI on or off.
BUT I can only get the feedback loop to work if I use the message “ON” or “OFF” in mosquitto.
The moment I use a message like LED_7_ON instead of just ON, it will turn on/off the output, but the openhab UI will not switch on/off together with it.

The problem I’m facing is that with my current sketch I read the messages from both topic’s of light and control.
So if I listen to “ON” or “OFF” both outputs attached to light and control will turn on/off.

Is there any way to either A) Listen to each topic separately instead of getting them all at once as is the case with my coding.
Or B) Have the feedback loop work without the use of “OFF” or “ON” and instead use something random.

Thanks already for the help so far

EDIT at bottom

I assume this is feeding back into a Switch, that’s cut off in your code box. A Switch item is looking for ON or OFF for feedback. See here

Why do you have your Arduino subscribe to MQTT_TOPIC_LIGHT? The Arduino is the one sending the info! It already knows! An issue you are having is that you publish to that topic and are subscribed to it as well and so it triggers the callback (because you just published to it) and when you get a call back with ON you send an ON. This could also have been a source of the MQTT vomit. Effectively, your state has become a control since you don’t have an ‘if’ for the topic.

if (topic == "your/topic") { //POINT A - this is where you would listen to each topic with two IFs
    //THEN your stuff...
    if (msgString.equals("OFF")) {
.....

Another point is that typically you don’t subscribe each time through the loop. PubSubClient examples use in loop().

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

Then inside of the reconnect function you subscribe to your topics (and send an ‘alive’ announcement if you want). Find the PubSubClient example mqtt_basic.

EDIT!!! OK, I see what you are saying now. I missed the software driven (phone) and physical (Arduino) routes. I think that you are looking for- when you get a callback, you check what topic it’s for.

Use a transform and a REGEX filter. for example

<[mosquitto:openhab/test/light:state:MAP(arduino.map):LED_7_.*]

The above will only match those messages that start with LED_7_ and the MAP transform will look in a arduino.map for an entry that matches the messages and selects its value.

An example arduino.map file might look like:

LED_1_ON=ON
LED_1_OFF=OFF
...
LED_7_ON=ON
LED_7_OFF=OFF

If your messages are not quite so random you could use a REGEX assuming the messages will always contain ON or OFF.

<[mosquitto:openhab/test/light:state:REGEX(LED_7_([ON|OFF])):LED_7_.*]

NOTE: I’m not 100% I got the regex right above.

@rlkoshak Rich, are you suggesting this so that he only has one topic and dynamically sends which LED he wants to send info for? I assumed that he would have a topic for each but this could be a smoother approach.

Just trying to cover all the bases. There are lots of approaches and a quick glance through the posted question made me think that is what he was doing.

I got it to work using the MAP way, thanks a lot!

No matter what I tried, I couldn’t get it to work.

I also managed to get the last problem fixed, which was that when you toggle the switch in openhab, the physical button would need 2 presses to switch states.

Fixed by adding this line of code:

 switch (senseMode2) {
    case MODE_OFF2:
      // light should be off
      digitalWrite(PIN_OUTPUT_7, LOW);
        state2 = LOW;
      break;
    case MODE_ON2:
      // light should be on
      digitalWrite(PIN_OUTPUT_7, HIGH);
       state2 = HIGH;
      break;
  }

I consider this part of my project to be a major succes!
Now I just have to wait for the other parts to arrive in the mail and I will attempt to make the same, but using 3 arduino’s. But that shouldn’t be a problem the way that I designed it.

After that it’s going to be more specific tasts such as temperature and humidity etc., but I don’t consider those vital for now.