knolleary / pubsubclient

A client library for the Arduino Ethernet Shield that provides support for MQTT.
http://pubsubclient.knolleary.net/
MIT License
3.79k stars 1.46k forks source link

Using reference to PubSubClient inside a class #961

Open aahondema opened 1 year ago

aahondema commented 1 year ago

Hi, I am passing the reference of a WiFiClient and a PubSubClient to a class definition because I want to be able to send an MQTT publish message from within the class. I don't need callbacks. This statement is used to initialize the object:

MLTCsignal1.init(hostName, name, signalNr, signalPin1, signalPin2, espClient, MQTT);

the method inside the class has this format:

void init(char hostName, char name, int signalNr, byte redpin, byte greenpin, WiFiClient& espclient, PubSubClient& MQTT);

As I use a reference, both espClient and MQTT point to the objects in the mail program.

_MQTT is the local PubSubClient definition in the class. MQTT is moved to _MQTT.

Technically, this works. Problem is that I can use MQTT.publish in my class, but only for a few seconds. This is the code for publishing a message inside my class:

_MQTT.loop(); if (!_MQTT.connected()) { _MQTT.setServer(MQTT_SERVER, MQTT_SERVERPORT); _MQTT.connect(_name, MQTT_USERNAME, MQTT_PASSWORD, "mltc/emergency", 0, false, "1"); } _MQTT.publish(Topic, Payload); _MQTT.disconnect();

I need to disconnect because more objects of the same type use MQTT. If I don't connect, only the first created object is able to publish. Once the connection is created it works for about 10 seconds, then the connection fails (-4) and the publish stops. After the main program has re-established the connection it again works for a few seconds. But it is not stable at all. Any ideas on this?

Adrian Hondema, The Netherlands.

aahondema commented 1 year ago

I created a simplified version of my sketch, hoping it will help you understand better what I am trying to do. I am using a NodeMCU minicomputer which has two LED signals connected. Each signal has a red and a green LED. The signal object handles all the logic: switching the LED on and of. When the state of a LED changes, a MQTT message is published from within the class. This works well for about 10 to 15 seconds, then the MQTT connection fails, leading to a new initialization of MQTT after which it works again for the same amount of time. It is obvious that I would prefer a more stable solution....

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

/*
   Signal Class definition
*/
#include "Signal.h"

#define wifiStatusLEDPin D0
#define signal1Pin1 D1
#define signal1Pin2 D2
#define signal2Pin1 D5
#define signal2Pin2 D6

#define WIFI_SSID       "SSID"
#define WIFI_PASSWORD   "PASSWORD"

#define SKETCH_ID       "MLTC"
#define MQTT_SERVER     "192.168.68.104"
#define MQTT_SERVERPORT 1883
#define MQTT_USERNAME   ""
#define MQTT_PASSWORD   ""
#define MQTT_CLIENTID   "MLTC_signal1"

/*
   Create the MLTC objects
*/
Signal MLTCsignal1;
Signal MLTCsignal2;

/*
   Create an ESP8266 WiFiClient class to connect to the MQTT server.
*/
WiFiEventHandler wifiConnectHandler;
WiFiEventHandler wifiDisconnectHandler;
WiFiClient espClient;

/*
   PubSubClient
*/
PubSubClient MQTT(espClient);

void setup() {
  Serial.begin(9600);
  while (!Serial && millis() < 10000)
    ;

  /*
     Init WiFi
     Register event handlers
  */
  wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
  wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);
  InitWiFi();

  /*
     Init MQTT
  */
  MQTT.setServer(MQTT_SERVER, MQTT_SERVERPORT);
  MQTT.setCallback(callback);

  /*
    Init objects
  */
  initSignal1(MQTT_CLIENTID, "signalA1", 1, signal1Pin1, signal1Pin2);
  initSignal2(MQTT_CLIENTID, "signalA2", 2, signal2Pin1, signal2Pin2);
}

void loop() {
  if (!MQTT.connected()) {
    reconnect();
  }
  MQTT.loop();

  MLTCsignal1.mqttLoop();
  MLTCsignal1.red();
  delay(2000);
  MLTCsignal1.mqttLoop();
  MLTCsignal1.green();
  delay(2000);
  MLTCsignal2.mqttLoop();
  MLTCsignal2.red();
  delay(2000);
  MLTCsignal2.mqttLoop();
  MLTCsignal2.green();
  delay(2000);
}

void initSignal1(char* hostName, char* name, int signalNr, byte signalPin1, byte signalPin2) {
  MLTCsignal1.init(hostName, name, signalNr, signalPin1, signalPin2, espClient, MQTT);
}

void initSignal2(char* hostName, char* name, int signalNr, byte signalPin1, byte signalPin2) {
  MLTCsignal2.init(hostName, name, signalNr, signalPin1, signalPin2, espClient, MQTT);
}

void InitWiFi()
{
  digitalWrite(wifiStatusLEDPin, HIGH);
  Serial.println("WiFi initialization...");

  WiFi.hostname(MQTT_CLIENTID);

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

void onWifiConnect(const WiFiEventStationModeGotIP& event) {
  Serial.println("Connected to WiFi successfully.");

  digitalWrite(wifiStatusLEDPin, LOW);

  Serial.println("WiFi initialization completed");
}

void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) {
  Serial.println("Disconnected from WiFi, trying to connect...");

  digitalWrite(wifiStatusLEDPin, HIGH);

  WiFi.disconnect();
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

void callback(char* topic, byte* payload, unsigned int length)
{
  /*
      Select only the characters according to the length of the payload
  */
  payload[length] = '\0';

  Serial.print("MQTT [");
  Serial.print(topic);
  Serial.print("]: ");
  Serial.println((char *)payload);

  /*
    Signal 1
  */
  if (String(topic) == String(MLTCsignal1.getMQTTSetStateTopic()))
  {
    switch (atoi((char *)payload))
    {
      case 1:
        {
          MLTCsignal1.green();
          break;
        }
      case 0:
        {
          MLTCsignal1.red();
          break;
        }
    }
    MQTT.publish(MLTCsignal1.getMQTTStateTopic(), MLTCsignal1.getState() ? "1" : "0");
  }

  /*
    Signal 2
  */
  if (String(topic) == String(MLTCsignal2.getMQTTSetStateTopic()))
  {
    switch (atoi((char *)payload))
    {
      case 1:
        {
          MLTCsignal2.green();
          break;
        }
      case 0:
        {
          MLTCsignal2.red();
          break;
        }
    }
    MQTT.publish(MLTCsignal2.getMQTTStateTopic(), MLTCsignal2.getState() ? "1" : "0");
  }
}

void reconnect()
{
  if (WiFi.status() != WL_CONNECTED)
  {
    InitWiFi();
  }

  while (!MQTT.connected())
  {
    Serial.println("MQTT initialization...");

    if (MQTT.connect(MQTT_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD, "mltc/emergency", 0, false, "1"))
    {
      Serial.println("MQTT initialization completed!");
      Serial.print("MQTT ClientID: ");
      Serial.println(MQTT_CLIENTID);

      MQTT.subscribe("mltc/emergency");
      MQTT.subscribe("mltc/signalA1/setstate");
      MQTT.subscribe("mltc/signalA2/setstate");
    }
    else
    {
      Serial.print("failed, rc=");
      Serial.print(String(MQTT.state()));
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

My class (.h) definition is as follows:

#ifndef MLTC_SIGNAL_h
#define MLTC_SIGNAL_h

#include <Arduino.h>
#include <ESP8266WiFi.h> 
#include <PubSubClient.h>

class Signal {

  private:

    char* _hostName;
    char* _name;
    int _number = -1;
    byte _redpin;
    byte _greenpin;
    int _state;
    int _previousState = -1;

    WiFiClient _espClient;
    PubSubClient _MQTT;

    /*
      MQTT Topics
    */
    char _pub_State[100] = "";
    char _sub_SetState[100] = "";

  public:
    Signal();

    void init(char* hostName, char* name, int signalNr, byte redpin, byte greenpin, WiFiClient& espclient, PubSubClient& MQTT);
    void red();
    void green();
    void mqttLoop();
    void mqttPublish(char* Topic, char* payload);

    void setState(int state);

    char* getName();
    int getNumber();
    int getState();
    char* getMQTTStateTopic();
    char* getMQTTSetStateTopic();
};
#endif

The .cpp file is as follows:

#include "Signal.h"
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

#define SKETCH_ID       "MLTC"
#define MQTT_SERVER     "192.168.68.104"
#define MQTT_SERVERPORT 1883
#define MQTT_USERNAME   ""
#define MQTT_PASSWORD   ""
#define MQTT_CLIENTID   "MLTC_signal1"

Signal::Signal() {}

void Signal::init(char* hostName, char* name, int number, byte redpin, byte greenpin, WiFiClient& espClient, PubSubClient& MQTT)
{
  _hostName = hostName;
  _name = name;
  _number = number;
  _redpin = redpin;
  _greenpin = greenpin;

  pinMode(_redpin, OUTPUT);
  pinMode(_greenpin, OUTPUT);

  _espClient = espClient;
  _MQTT = MQTT;

  /*
     MQTT Topics
  */
  sprintf(_pub_State, "mltc/%s/state", _name);
  sprintf(_sub_SetState, "mltc/%s/setstate", _name);

  /*
     Set signal to default position
  */
  red();
}

void Signal::red() {
  setState(0);
}

void Signal::green() {
  setState(1);
}

/*
   Setters
*/
void Signal::setState(int state) {
  _state = state;
  if (state != _previousState)
  {
    _previousState = state;

    switch (_state) {
      case 0:
        digitalWrite(_redpin, HIGH);
        digitalWrite(_greenpin, LOW);
        mqttPublish(_pub_State, "0");
        break;
      case 1:
        digitalWrite(_redpin, LOW);
        digitalWrite(_greenpin, HIGH);
        mqttPublish(_pub_State, "1");
        break;
    }

    Serial.print(_name);
    Serial.print(" set to ");
    Serial.println(_state ? "1 (Green)" : "0 (Red)");
  }
}

void Signal::mqttLoop()
{
  if (!_MQTT.connected())
  {
    _MQTT.setServer(MQTT_SERVER, MQTT_SERVERPORT);
    _MQTT.connect(_name, MQTT_USERNAME, MQTT_PASSWORD, "mltc/emergency", 0, false, "1");
  }
  _MQTT.loop();  
}

void Signal::mqttPublish(char* Topic, char* Payload)
{
  _MQTT.publish(Topic, Payload);
  // _MQTT.disconnect();

  Serial.print("_MQTT.publish: ");
  Serial.print(Topic);
  Serial.print(", ");
  Serial.println(Payload);
  Serial.print("MQTT state: ");
  Serial.println(_MQTT.state());
}

char* Signal::getName() {
  return _name;
}

int Signal::getNumber() {
  return _number;
}

int Signal::getState() {
  return _state;
}

char* Signal::getMQTTStateTopic() {
  return _pub_State;
}

char* Signal::getMQTTSetStateTopic() {
  return _sub_SetState;
}

Serial Monitor output:

  09:25:56.451 -> _MQTT.publish: mltc/signalA1/state, 0
  09:25:56.499 -> MQTT state: 0
  09:25:56.499 -> signalA1 set to 0 (Red)
  09:25:58.453 -> _MQTT.publish: mltc/signalA1/state, 1
  09:25:58.499 -> MQTT state: 0
  09:25:58.499 -> signalA1 set to 1 (Green)
  09:26:00.459 -> _MQTT.publish: mltc/signalA2/state, 0
  09:26:00.506 -> MQTT state: 0
  09:26:00.506 -> signalA2 set to 0 (Red)
  09:26:02.450 -> _MQTT.publish: mltc/signalA2/state, 1
  09:26:02.498 -> MQTT state: 0
  09:26:02.498 -> signalA2 set to 1 (Green)
  09:26:04.500 -> _MQTT.publish: mltc/signalA1/state, 0
  09:26:04.547 -> MQTT state: 0
  09:26:04.547 -> signalA1 set to 0 (Red)
  09:26:06.511 -> _MQTT.publish: mltc/signalA1/state, 1
  09:26:06.559 -> MQTT state: 0
  09:26:06.559 -> signalA1 set to 1 (Green)
  09:26:08.492 -> _MQTT.publish: mltc/signalA2/state, 0
  09:26:08.534 -> MQTT state: 0
  09:26:08.534 -> signalA2 set to 0 (Red)
  09:26:10.481 -> _MQTT.publish: mltc/signalA2/state, 1
  09:26:10.532 -> MQTT state: 0
  09:26:10.532 -> signalA2 set to 1 (Green)
  09:26:12.485 -> MQTT initialization...                    <- FAILURE AND RECONNECT
  09:26:27.500 -> failed, rc=-4 try again in 5 seconds
  09:26:32.488 -> MQTT initialization...
  09:26:32.535 -> MQTT initialization completed!
  09:26:32.571 -> MQTT ClientID: MLTC_signal1
  09:26:32.618 -> _MQTT.publish: mltc/signalA1/state, 0
  09:26:32.656 -> MQTT state: 0                             <- AND THEN IT WORKS FOR ANOTHER +/- 10 SECONDS 
  09:26:32.656 -> signalA1 set to 0 (Red)
  09:26:34.556 -> _MQTT.publish: mltc/signalA1/state, 1
  09:26:34.602 -> MQTT state: 0
  09:26:34.602 -> signalA1 set to 1 (Green)
  09:26:36.557 -> _MQTT.publish: mltc/signalA2/state, 0
  09:26:36.610 -> MQTT state: 0