Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keepalive timeout for default MQTT Broker is 10s, pubsubclient is default set to 15s? #239

Open
NonaSuomy opened this issue Jan 24, 2017 · 19 comments

Comments

@NonaSuomy
Copy link

Is this an issue, should this be changed?

I was having timeout and disconnections on broker when I looked into finding a timeout in code, I discovered the default keepalive timeout for Mosquitto is 10s seconds, then I looked at the MQTT_KEEPALIVE in pubsubclient which is default to 15s seconds, I changed this to 10s and never had a timeout problem on the broker again.

@knolleary
Copy link
Owner

That is an interesting question.

On the other hand we have #223 that says 15seconds is too aggressive and should be lengthened.

¯_(ツ)_/¯

@leopucci-zz
Copy link

The most important is to match server and client.
I think that if stays on 10 like mosquitto will cause problem to less persons just because they will match and will not get timeout.
The gsm guy will need to change keep-alive on the server anyway, he is the exception of the rule.

@knolleary
Copy link
Owner

@leopucci I'm sure @andysc, co-inventor of MQTT will appreciate being referred to as 'the gsm guy'.

😉

@leopucci-zz
Copy link

leopucci-zz commented Jan 25, 2017

Wasn´t personal, sorry. I don´t know who he is. I just meant that the exception could not be the rule and for micro controllers i think that poor network connections will not be the rule, as most of them will be connected to adsl,cable,etc.
And for more specific types of connections, the person will need to tune the server, so it will not be a problem to tune the client too.

@knolleary
Copy link
Owner

No offense will have been taken. I can assure you of that.

The client determines what keepalive is used. Mosquitto defaults are irrelevant to the behaviour of this client - mosquitto must honour what the client says it is going to use.

You cannot make any assumptions about a typical client. I have used this in a wide variety of devices and network conditions where different timeouts have been necessary.

I am not inclined to change the default value. It had been 15 seconds for 7+ years. If anything, we will make it easier to customise and not rely on editing the header file.

@leopucci-zz
Copy link

Strange, because @NonaSuomy opened this because of server <-> client timeouts on 10/15 difference.. I will try here to see if mosquitto 10s conf cause this behaviour

@leopucci-zz
Copy link

Humm @knolleary i think i got it the client sets the keepalive part. There is no conf on mqtt conf on server.. it is defined on handshake.
Does not seem to be an issue, maybe some delays on the @NonaSuomy code was causing the client to time out as it was not able to keep-alive on the right time.

@andysc
Copy link

andysc commented Jan 26, 2017

@knolleary if we could change it in the application code, i.e. over-ride the constant in the MQTT library, that would solve my problem... I can just put the keepalive I want into my app.
Maybe set it as a parameter in the initialisation of the MQTT object... as the gsm guy intended ;)

@NonaSuomy
Copy link
Author

It seems, why 15 seconds works in most cases is because Standard Docs say that the keepalive on the broker will wait for 1.5 times what the client is set to. Basically a buffer zone, so if it waited a few seconds after that because an interrupt or something fired off, ~15s give or take it would drop PubSubClient.

Keepalive in the standard documentation is shown as binary 10s, so even though the test went well, can the person above stress test it if they did not, how long did it take for it to actually drop the connection after 15s, was it 16s or 15sx1.5=22.5s?

If it obeys what the client tells it (seems like what the doc is saying it should) that would mean my device must have got hung up for more than 22.5s.

What happens if there are many clients on the server setting it to 10s because they read that in the docs, does it keep switching on demand for that single ping request session to each keepalive instance device?

< Start Documentation >

The Keep Alive is a time interval measured in seconds. Expressed as a 16-bit word, it is the maximum time interval that is permitted to elapse between the point at which the Client finishes transmitting one Control Packet and the point it starts sending the next. It is the responsibility of the Client to ensure that the interval between Control Packets being sent does not exceed the Keep Alive value. In the absence of sending any other Control Packets, the Client MUST send a PINGREQ Packet [MQTT-3.1.2-23].

The Client can send PINGREQ at any time, irrespective of the Keep Alive value, and use the PINGRESP to determine that the network and the Server are working.

If the Keep Alive value is non-zero and the Server does not receive a Control Packet from the Client within one and a half times the Keep Alive time period, it MUST disconnect the Network Connection to the Client as if the network had failed [MQTT-3.1.2-24].

If a Client does not receive a PINGRESP Packet within a reasonable amount of time after it has sent a PINGREQ, it SHOULD close the Network Connection to the Server.

A Keep Alive value of zero (0) has the effect of turning off the keep alive mechanism. This means that, in this case, the Server is not required to disconnect the Client on the grounds of inactivity.
Note that a Server is permitted to disconnect a Client that it determines to be inactive or non-responsive at any time, regardless of the Keep Alive value provided by that Client.

Example in the Documentation for a Broker

Keep Alive
byte 9 Keep Alive MSB (0)  00000000
byte 10  Keep Alive LSB (10)  00001010

< / End Documentation >

I agree you should have a settable variable in there like for IP/username/password/keepalive as stated above.

The other tedious thing is you cannot set the default keepalive value from the config in the standard based Mosquitto broker, should allow you to set a base minimum in the configuration, but I was reading it's only possible to set the keepalive option before you start the server somehow, maybe this was just talking about the client setting it though.

It does state below that, that the recommended value should be a few mins.

Non normative comment
The actual value of the Keep Alive is application specific; typically this is a few minutes. The maximum value is 18 hours 12 minutes and 15 seconds.

Wonder why they didn't set a few mins in the example above, seems counter productive.

@knolleary
Copy link
Owner

In the MQTT protocol, the client determines the keepalive value used on a connection. The broker has no say in it.

The only keepalive config option on mosquitto is its bridge keepalive - where it is acting as a client connecting to another broker - https://mosquitto.org/man/mosquitto-conf-5.html

@NonaSuomy
Copy link
Author

NonaSuomy commented Jan 26, 2017

I can guarantee it is something I am doing wrong causing the over delay can someone point it out to me so I can refactor.

Arduino Create Share

I2CMasterBroker

/*
 * Code: GDS (I2CNET) Garage Door System I2C Master Module
 * User: NonaSuomy
 * Date: 20161111
 * Upda: 20170126
 * Desc: Retrofit Garage Door Opener with Automation and Sensors.
 *       Network I2C Master Node that communicates with I2C Slaves, which pass 
 *       sensor values to master from garage area, with an MQTT Broker that is
 *       read from the home automation server OpenHAB.
 */
 
// Serial Debug Items
#define DEBUG // Comment this define out to disable serial debug print.
int baud = 9600; // Debug Serial baud rate. 74880,115200;

// I2C Items
#include <Wire.h> // I2C Wire Library.
//#define I2CSDA A4 // I2C Data Line on Arduino Pin A4 or GPIO0.
//#define I2CSCL A5 // I2C Clock Line on Arduino Pin A5 or GPIO2.
#define I2CMADDR001 0x8 // I2C Master1 Address.
//#define I2CMADDR002 0x9 // I2C Master2 Address.
#define I2CSADDR001 0x21 // I2C Slave1 Address.
volatile byte data[16]; // I2C data packet.
// Convert Temp/Humi to bytes. Volatile is required for variables used outside of interupt for I2C.
union float2bytes { volatile float f; volatile char b[sizeof(volatile float)]; };
float2bytes f2bt;
float2bytes f2bh;

// LED Items
int ledPin = 13; // LED pin number.
int ledState = LOW; // ledState used to set the LED

// Encoder Items
long TBEncPos = 0; // Encoder position.
long TBEncLast; // Last encoder position.
long TBEncOpen = 310; // Door fully open.
long TBEncClosed = 0; // Door fully closed.
//long TBEncPercent,TBEncPercLast;

// Ethernet Items
#include <SPI.h>
#include <Ethernet2.h>
#define SS     10U    //D10----- SS
#define RST    11U    //D11----- Reset
byte MAC[]     = {0xDE, 0xED, 0xBE, 0xEF, 0xFE, 0xED }; // MAC address for our module.
byte IP[]      = { 10, 13, 37, 100 };  // Static IP for our module (Not in use, currently set to DHCP MAC address only required).
byte Netmask[] = { 255, 255, 255, 0 }; // Local router netmask.
byte Gateway[] = { 10, 13, 37, 1 };   // Local router gateway.
EthernetClient ethClient;

// MQTT Items
#include <PubSubClient.h> // MQTT library 2.6.0.
const char* mqtt_broker = "10.13.37.42"; // MQTT Broker IP address.
const char* mqtt_client = "I2CMasterNode001"; // MQTT Client Name.
const char* mqtt_user = "USERNAME"; // MQTT Broker user. 
const char* mqtt_pass = "PASSWORD"; // MQTT Broker password.
long mqtt_connect_count = 0;
PubSubClient mqttclient(ethClient);
// Setup MQTT Topics for publishing to.
#define mast_topic "hq/garage/#"
#define rel1_topic "hq/garage/relay1"
#define swi1_topic "hq/garage/switch1"
#define posi_topic "hq/garage/position"
#define avai_topic "hq/garage/available"
#define humi_topic "hq/garage/humidity"
#define temp_topic "hq/garage/temperature"
//#define baro_topic "hq/garage/barometer"

// Track switches open/close status so we don't spam.
int swopenstat, swopenlast;  
int swclosestat, swcloselast; 

// Setup non-blocking delay variables.
long millis_now;
long millis_prev1; // Check close switch status every 5 seconds.
long millis_prev2; // Send MQTT Broke that this module is still alive every 5 mins and check the temperature.
long millis_prev3; // Check Encoder status every 2 seconds.
long millis_prev4; // Blink LED on Master and Slave 

// Setup Arduino Items
void setup() {
  #ifdef DEBUG
  Serial.begin(baud);     // Initialize Serial for debug.
  while (!Serial) {
    ; // Wait for serial port to connect. Needed for Leonardo only.
  }
  #endif
  // Ethernet setup.
  pinMode(SS, OUTPUT);    // Initalize Ethernet SPI Select Pin.
  pinMode(RST, OUTPUT);   // Initialize Ethernet Reset Pin.
  digitalWrite(SS, LOW);  // Ethernet Select 0.
  digitalWrite(RST,HIGH); // Ethernet Reset High.
  delay(200); 
  digitalWrite(RST,LOW);  // Ethernet Reset Low.
  delay(200);
  digitalWrite(RST,HIGH); // Ethernet Reset High.
  delay(200);             // Wait for the W5500 to reset.
  Ethernet.begin(MAC);    // Initialize Ethernet.
  #ifdef DEBUG
  // Print the IP Address.
  Serial.print("My IP address: ");
  for (byte thisByte = 0; thisByte < 4; thisByte++) {
    // Print the value of each byte of the IP address.
    Serial.print(Ethernet.localIP()[thisByte], DEC);
    Serial.print("."); 
  }
  Serial.println();
  #endif
  // Initialize MQTT Items.
  mqttclient.setServer(mqtt_broker, 1883); // Setup MQTT server and Port.
  mqttclient.setCallback(callback); // Setup callback to grab MQTT topics.
  // Initialize I2C Wire Library, Set SDA and SCL ports (I2C).
  Wire.begin(); //I2CSDA, I2CSCL
}
// Grab content from MQTT broker, topic eg: "hq/garage/switch1" 
// payload will contain the values of the topics.
// length will contain how long the string is.
void callback(char* topic, byte* payload, unsigned int length) {
  #ifdef DEBUG
  // Send serial monitor the topic that arrived.
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  // Send serial monitor the topics values.
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
  #endif
  // Check for correct topic of relay1.
  if (String(topic) == rel1_topic) {
    // Fire off door1relay if "1" was received, stored in first array value of the payload.
    if ((char)payload[0] == '1') {
      // Trigger GDS Relay1 via I2C. 
      slaveDoor1Relay(I2CSADDR001,7);
      // Send MQTT Broker relay status of 0 meaning its off now.
      mqttclient.publish(rel1_topic, "0");
      #ifdef DEBUG
      Serial.println("MQTT Says, Relay triggered!");
      #endif
    }
  }
  // Check for correct topic of switch1 open.
  if (String(topic) == swi1_topic) { 
    if ((char)payload[0] == '1') {
      // Send serial monitor switch status.
      #ifdef DEBUG
      Serial.println("MQTT Says, Open Switch Hit!");
      #endif
    } else {
      #ifdef DEBUG
      // Send serial monitor switch null status.
      Serial.println("MQTT Says, Close Switch Hit!");
      //Serial.println("Door closing...");
      #endif
    }
  }
  // Check for correct topic of switch1 close.
  if (String(topic) == swi1_topic) { 
    if ((char)payload[0] == '0') {
      #ifdef DEBUG
      // Send serial monitor switch status.
      Serial.println("Close Switch Hit!");
      #endif
    } else {
    // Send serial monitor switch null status.
    //Serial.println("Door opening...");
    }
  }
  // Check for correct topic of temperature.
  if (String(topic) == temp_topic) { 
    #ifdef DEBUG
    // Send serial monitor garage temperature.
    Serial.print("MQTT Says, Garage Temperature: ");
    Serial.print((char)payload[0]);
    Serial.print((char)payload[1]);
    Serial.print((char)payload[2]);
    Serial.print((char)payload[3]);
    Serial.println((char)payload[4]);
    #endif
  }
  // Check for correct topic of position from tension bar encoder.
  if (String(topic) == posi_topic) {
    #ifdef DEBUG
    // Send serial monitor garage temperature.
    Serial.print("MQTT Says, Garage Door Position: ");
    Serial.print((char)payload[0]);
    Serial.print((char)payload[1]);
    Serial.println((char)payload[2]);
    #endif
  }
}
void reconnect() {
  //                      ( String id,     String willTopic,    uint8_t willQos, bool willRetain, String willMessage)
  //if (mqttClient.connect("espcli_garage",avai_topic,          0,               0,               "0")){
  // Loop until reconnected to MQTT Broker.
  while (!mqttclient.connected()) {
    #ifdef DEBUG
    Serial.println("Attempting MQTT Broker connection...");
    #endif
    // Attempt to connect and send MQTT Broker our module name were connecting with.
    // Note: You will see this name in the MQTT Broker log. "I2CMasterNode001"
    // If you do not want to use a username and password, change next line to
    if (mqttclient.connect(mqtt_client, mqtt_user, mqtt_pass)) {
      #ifdef DEBUG
      Serial.print("Connected to the MQTT Broker: ");
      Serial.println(mqtt_broker);
      #endif
      // Once connected, publish to "available" topic that this module is online...
      mqttclient.publish(avai_topic, "1");
      // Re-subscribe to all topics under the garage topic.
      // Note: The pound(#) symbol, this is what subscribes to all topics under it (available,relay1,switch1,position,temp).
      mqttclient.subscribe(mast_topic);
    // Connection failed. Send the serial monitor the issue why. Wait 5 seconds and try again.
    } else {
      #ifdef DEBUG
      Serial.print("Failed, rc=");
      Serial.print(mqttclient.state());
      Serial.println(" trying again in 5 seconds...");
      #endif
      // Wait 5 seconds before retrying
      delay(5000);
    }
    mqtt_connect_count ++; // Tally up how many times we have retried to connect.
    if(mqtt_connect_count > 5){ // Something is out of sync somewhere... do a system reset.
      #ifdef DEBUG
      Serial.println("Hardware is out of sync, System is resetting...");
      #endif
    }
  }
}
void requestFromSlave(int slave) {
  Wire.requestFrom(slave, 16); // Request 16 Bytes.
  while(Wire.available()>1) {
    // Status Register
    data[0] = Wire.read();
    // Data Register
    data[1] = Wire.read();
    // Data Register - Temp - MSB
    f2bt.b[0] = Wire.read();
    // Data Register - Temp
    f2bt.b[1] = Wire.read();
    // Data Register - Temp
    f2bt.b[2] = Wire.read();
    // Data Register - Temp - LSB
    f2bt.b[3] = Wire.read();
    // Data Register - Humidity - MSB
    f2bh.b[0] = Wire.read();
    // Data Register - Humidity
    f2bh.b[1] = Wire.read();
    // Data Register - Humidity
    f2bh.b[2] = Wire.read();
    // Data Register - Humidity - LSB
    f2bh.b[3] = Wire.read();
    // Data Register - Encoder
    data[10] = Wire.read();
    // Data Register - Hall Effect Open
    data[11] = Wire.read();
    // Data Register - Hall Effect Closed
    data[12] = Wire.read();
    // Mode Register
    data[13] = Wire.read();
    // Configuration Register
    data[14] = Wire.read();
    // Identification Register
    data[15] = Wire.read();
  }
}
// Change I2C slave GDS pin state.
void slaveTrig(int slaveID, int pin, int state) {
  Wire.beginTransmission(slaveID);
  Wire.write('a');
  Wire.write(pin);
  Wire.write(state);
  Wire.endTransmission();
  delayMicroseconds(10);
}
// Trigger I2C Slave GDS Relay.
void slaveDoor1Relay(int slaveID, int pin) {
  Wire.beginTransmission(slaveID);
  Wire.write('c');
  Wire.write(pin);
  Wire.write(1);
  Wire.endTransmission(slaveID);
  delay(250);
  Wire.beginTransmission(slaveID);
  Wire.write('d');
  Wire.write(pin);
  Wire.write(0);
  Wire.endTransmission(slaveID);
  delayMicroseconds(10);
}

void loop() {
  // Check if module is not connected to MQTT server, if not reconnect.
  if (!mqttclient.connected()) {
    reconnect();
  }
  // Call MQTT client loop. 
  mqttclient.loop();
  // Get data from a slave.
  requestFromSlave(I2CSADDR001);
  #ifdef DEBUG
  // Print I2C data[] to Serial Monitor.
  delay(500);
  Serial.print("Status Register: ");
  Serial.println(data[0]);
  Serial.print("Data Register: ");
  Serial.println(data[1]);
  Serial.print("TempMSB: ");
  Serial.println(f2bt.b[0],HEX);
  Serial.print("TempMid1: ");
  Serial.println(f2bt.b[1],HEX);
  Serial.print("TempMid2: ");
  Serial.println(f2bt.b[2],HEX);
  Serial.print("TempLSB: ");
  Serial.println(f2bt.b[3],HEX);
  Serial.print("The Temperature 32bit value is: ");
  Serial.println(f2bt.f);
  Serial.print("HumiMSB: ");
  Serial.println(f2bh.b[0],HEX);
  Serial.print("HumiMid1: ");
  Serial.println(f2bh.b[1],HEX);
  Serial.print("HumiMid2: ");
  Serial.println(f2bh.b[2],HEX);
  Serial.print("HumiLSB: ");
  Serial.println(f2bh.b[3],HEX);
  Serial.print("The Humidity 32bit value is: ");
  Serial.println(f2bh.f);
  Serial.print("Encoder: ");
  Serial.println(data[10]);
  Serial.print("OpenSwitch: ");
  Serial.println(data[11]);
  Serial.print("CloseSwitch: ");
  Serial.println(data[12]);
  Serial.print("Mode Register: ");
  Serial.println(data[13]);
  Serial.print("Configuration Register: ");
  Serial.println(data[14]);
  Serial.print("Identification Register: ");
  Serial.println(data[15]);
  #endif
  // Blink LED on Master.
  if (millis_now > (millis_prev4 + 1000)) {
    // save the last time you blinked the LED
    millis_prev4 = millis_now;
    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH; // Master LED state on.
      slaveTrig(I2CSADDR001,13,1); // Turn on LED on slave.
    } else {
      ledState = LOW; // Master LED state off.
      slaveTrig(I2CSADDR001,13,0); // Turn off LED on slave.
    }
    digitalWrite(ledPin, ledState); // Set the LED with the ledState of the variable.
  }
  // Capture the current millis for non-blocking delay.
  millis_now = millis();
  // Check Encoder status every 2 seconds.
  if (millis_now > (millis_prev3 + 2000)) {  
    millis_prev3 = millis_now;
    // Prevent Topic Spamming (Check if encoder is same value).
    if (TBEncLast != data[10]) {
      TBEncLast = data[10];
      mqttclient.publish(posi_topic, String(data[10]).c_str(), true);
      #ifdef DEBUG
      Serial.print("TensionBarPercent = ");
      Serial.print(data[10]);
      Serial.println("%");
      #endif
    }
  }
  // Check close switch status every 5 seconds.
  if (millis_now > (millis_prev1 + 5000)) {  
    millis_prev1 = millis_now;
    // Check if door switch closed is triggered.
    if (data[12] == 1) { // I2C data buffer 12 is close door state.
      swclosestat = 1;
      // Prevents spamming the MQTT Broker with publishes not needed.
      if (swclosestat != swcloselast) {
        swcloselast = swclosestat;
        // Publish door status of Open to the MQTT Broker.
        mqttclient.publish(swi1_topic, "1");
        #ifdef DEBUG
        Serial.print("Current Open Value: ");
        Serial.println(String(TBEncOpen));
        #endif
      }
    } else {
      swclosestat = 0;
      // Prevents spamming the MQTT server with publishes not needed.
      if (swclosestat != swcloselast) {
        swcloselast = swclosestat;
        // Publish door status of Closed to the MQTT Broker.
        // Only required if you have one switch.
        mqttclient.publish(swi1_topic, "0");
      }
    }
    // Comment this out if you only have one switch:
    // Check if door switch open is triggered.
    if (data[11] == 1) { // I2C data buffer 11 is open door state.
      swopenstat = 1;
      if (swopenstat != swopenlast) {
        swopenlast = swopenstat;
        mqttclient.publish(swi1_topic, "0");
        if (TBEncPos < 0) {
          #ifdef DEBUG
          Serial.println("Reset TensionBar Encoder to zero.");
          #endif
        }
        TBEncClosed = TBEncPos;
        #ifdef DEBUG
        Serial.print("Current Closed Value: ");
        Serial.println(String(TBEncClosed));
        #endif
      }
    } else {
      swopenstat = 0;
      if (swopenstat != swopenlast) {
        swopenlast = swopenstat;
        // Publish door status to the MQTT Broker.
        // Only required if you have one switch.
        //mqttclient.publish(swi1_topic, "1");
      }
    }
  }
  // Send MQTT Broker that this module is still alive every 5 mins and check temperature.
  if (millis_now > (millis_prev2 + 10000)){ //5 min 300000 (set faster for debugging.)
    millis_prev2 = millis_now;
    // Send alive message to MQTT Broker. 
    mqttclient.publish(avai_topic,"1");
    // Send MQTT Broker Temperature data.
    mqttclient.publish(temp_topic, String(f2bt.f).c_str(), true);
    mqttclient.publish(humi_topic, String(f2bh.f).c_str(), true);
  }
}

I2CSlaveSensors
Arduino Create Share

/*
 * Code: GDS (I2CNET) Garage Door System I2C Slave Module
 * User: NonaSuomy
 * Date: 20161111
 * Upda: 20170126
 * Desc: Retrofit Garage Door Opener with Automation and Sensors.
 *       Network I2C Master Node that communicates with I2C Slaves, which pass 
 *       sensor values to master from garage area, with an MQTT Broker that is
 *       read from the home automation server OpenHAB.
 */

// Serial items.
#define DEBUG // Comment this out to disable serial debug print.
int baud = 9600; // Serial console baud rate for debug. 74880,115200

// I2C items.
#include <Wire.h> // I2C Bus Wire Library.
#define I2CSADDR 0x21 // I2C Slave Address.
#define I2CSDA A4 // I2C Data Line on Arduino Pin A4.
#define I2CSDC A5 // I2C Clock Line on Arduino Pin A5.
// These "should" be volatile defined as they are used in an ISR.
volatile byte pinState = 0, pin = 0;
volatile byte data[16];
// Union that will take the float value and output the 4Byte value.
union float2bytes { volatile float f; volatile char b[sizeof(volatile float)]; };
float2bytes f2bt;
float2bytes f2bh;

// Hall effect switch items. 
#define CLOSEHEF 5 // Garage door is Closed sensor pin.
#define OPENHEF 6 // Garage door is Open sensor pin.
// Track switch open/close status so we don't spam.
int swopenstat, swopenlast;  
int swclosestat, swcloselast; 

// Relay items.
#define DOOR1RELAY 7 // Garage door Open/Close switch pin.

// Temperature/Humidity items.
#include <DHT.h> // DHT22 Temperature Sensor library.
#define DHTTYPE DHT22 // Select DHT22 Model of DHT Sensors.
#define DHTPIN  4 // DHT22 Sensor pin.
DHT dht(DHTPIN, DHTTYPE); // Setup the sensor.
// Setup temperature/humidity varibles.
//#define baro_corr_hpa 34.5879 // = 298m above sea level
float temp = 0.0; // Setup temperature float variable.
float hum = 0.0; // Setup humidity float variable.
//float baro = 0.0; // Setup barometer float variable.
float diff = 1.0; // Setup difference float variable.

// Encoder items.  
#include <Encoder.h> // Encoder library 1.4.1.
#define ENCODER_A 2 // Garage door tension bar encoder pin A; pin (2); Shows door percentage.
#define ENCODER_B 3 // Garage door tension bar encoder pin B; pin (3); Shows door percentage.
// Change these pin numbers to the pins connected to the encoder.
// Best Performance: Both pins have interrupt capability
// Good Performance: Only the first pin has interrupt capability
// Low Performance:  Neither pin has interrupt capability
// Avoid using pins with LEDs attached
Encoder TBEnc(ENCODER_A, ENCODER_B); // Tension Bar Encoder, Pins 4 & 5 have interrupts on the ESP8266 for Best Performance.
// Setup encoder postion varable.
long TBEncPos = -999;
long TBEncLast;
long TBEncOpen = 310;
long TBEncClosed = 0;
long TBEncPercent,TBEncPercLast;

// Setup the Arduino.
void setup() {
  #ifdef DEBUG
  Serial.begin(baud); // Initiate serial for debug.
  #endif
  Wire.begin(I2CSADDR); // Initiate the Wire library
  Wire.onRequest(requestEvent); // I2C Register request events.
  Wire.onReceive(receiveEvent); // I2C Register receive events
  // Prepare Arduino pins. 
  pinMode(13,OUTPUT); // On-board LED.
  pinMode(DOOR1RELAY, OUTPUT); // Door Relay1 Pin7/GPIO13.
  digitalWrite(DOOR1RELAY, LOW); // Door Relay1 Pin7/GPIO13 start state LOW.
  pinMode(CLOSEHEF, INPUT); // Close Switch Pin5/GPIO14.
  pinMode(OPENHEF, INPUT); // Open Switch Pin6/GPIO12.
  // Start Weather Sensor
  dht.begin(); // Start the DHT Sensor.
}

// DHT22 Check Sensor for new temperature/humidity.
bool checkBound(float newValue, float prevValue, float maxDiff) {
  return !isnan(newValue) &&
         (newValue < prevValue - maxDiff || newValue > prevValue + maxDiff);
}

// Main programming loop.
void loop() {
  // Prepare an analog value for debugging.
  int tempValue = analogRead(A0);
  pinState = map(tempValue, 0, 1023, 0, 255);

  // Setup encoder and set current value to TBEncPos.
  long newTBEnc;
  newTBEnc = TBEnc.read();
  if (newTBEnc != TBEncPos) {
    #ifdef DEBUG
    Serial.print("TensionBarEncoder = ");
    Serial.print(newTBEnc);
    Serial.println();
    #endif
    TBEncPos = newTBEnc;
  }
  // Check Encoder status every 2 seconds.
  //if (millis_now > (millis_prev3 + 2000)) {  
    //millis_prev3 = millis_now;
    char TBEncoder[8];
    // Calculate the percentage of the door position.
    //if (TBEncClosed != 0) {
    if (TBEncOpen < 10 && TBEncClosed > 100) {
      TBEncPercent = map(TBEncPos, TBEncOpen, TBEncClosed, 0, 100); // Make encoder values use 0 to 100 range.
      TBEncPercent = constrain(TBEncPercent,0,100); // Force values within 0 to 100 Range.
      itoa(TBEncPercent,TBEncoder,10); // Integer to string
      if (TBEncPercent != TBEncPercLast) { // Prevent spamming console.
        TBEncPercLast = TBEncPercent; // Prevent spamming.
        #ifdef DEBUG
        Serial.print("TensionBarPercent = ");
        Serial.print(TBEncoder);
        Serial.println("%");
        #endif
      }
      // Prevent Topic Spamming (Check if encoder is same value).
      if (TBEncLast != TBEncPos) {
        TBEncLast = TBEncPos;
        data[10] = TBEncPercent; // Byte array data[10] is set to send via I2C.
      }
    }
  //}
  // Check if door switch closed is triggered.
  if (digitalRead(CLOSEHEF) == LOW) {
    swclosestat = 1;
    // Prevents spamming console with unchanged values.
    if (swclosestat != swcloselast) {
      swcloselast = swclosestat;
      // Add door status of Open to I2C data buffer.
      data[12] = 1; // Push value to I2C Buffer data 12.
      #ifdef DEBUG
      Serial.println("Close HEF: 1");
      #endif
      // Store final open position of encoder to calculate percentage.
      TBEncOpen = TBEncPos;
      if (TBEncOpen < 10) {
        TBEncOpen = 310;
      }
      #ifdef DEBUG
      Serial.print("Current Open Value: ");
      Serial.println(String(TBEncOpen));
      #endif
    }
  } else {
    swclosestat = 0;
    // Prevents spamming console with unchanged values.
    if (swclosestat != swcloselast) {
      swcloselast = swclosestat;
      data[12] = 0; // Push value to I2C Buffer data 12.
      #ifdef DEBUG
      Serial.println("Close HEF: 0");
      #endif
    }
  }
  // Comment this out if you only have one switch:
  // Check if door switch open is triggered.
  if (digitalRead(OPENHEF) == LOW) {
    swopenstat = 1;
    if (swopenstat != swopenlast) {
      swopenlast = swopenstat;
      data[11] = 1; // Push value to I2C Buffer data 11.
      #ifdef DEBUG
      Serial.println("Open HEF: 1");
      #endif
      if (TBEncPos < 0) {
        #ifdef DEBUG
        Serial.println("Reset TensionBar Encoder to zero.");
        #endif
        TBEnc.write(0);
      }
      TBEncClosed = TBEncPos;
      #ifdef DEBUG
      Serial.print("Current Closed Value: ");
      Serial.println(String(TBEncClosed));
      #endif
    }
  } else {
    swopenstat = 0;
    if (swopenstat != swopenlast) {
      swopenlast = swopenstat;
      data[11] = 0; // Push value to I2C Buffer data 11.
      #ifdef DEBUG
      Serial.println("Open HEF: 0");
      #endif
    }
  }
  // Setup temperature/humidity floats.
  float newTemp = dht.readTemperature();
  float newHum = dht.readHumidity();
  // Check temperature is different than last check.
  if (checkBound(newTemp, f2bt.f, diff)) {
    f2bt.f = newTemp;
    #ifdef DEBUG
    Serial.print("New temperature:");
    Serial.println(String(f2bt.f).c_str());
    #endif
    // Turn temperature float into 4Byte value.
    for ( int i=0; i < sizeof(float); i++ ) {
      f2bt.b[i];
      //Serial.println(f2bt.b[i],HEX);
    }
  }
  // Check humidity is different than last check.
  if (checkBound(newHum, f2bh.f, diff)) {
    f2bh.f = newHum;
    #ifdef DEBUG
    Serial.print("New humidity:");
    Serial.println(String(f2bh.f).c_str());
    #endif
    // Turn humidity float into 4Byte value.
    for ( int i=0; i < sizeof(float); i++ ) {
      f2bh.b[i];
      //Serial.println(f2bh.b[i],HEX);
    }
  }
}
// I2C receiveEvent interrupt. 
void receiveEvent(int howMany) {
  while (Wire.available() < 2); // Wait for 2 bytes to become available.
  char caseState = Wire.read(); // Read for a state from the I2C Bus.
  switch (caseState) { // Check for received state of a,b,c,etc.
    case 'a': {
      int pin = Wire.read(); // Get Arduino pin value from I2C buffer.
      int pinState = Wire.read(); // Get value for setting the LED on/off from I2C buffer.
      pinTrig(pin, pinState); // Call the pinOn function to turn the LED on.
    }
    break;
    case 'b':{
      int pin = Wire.read(); // Get Arduino pin value from I2C buffer.
      int pinState = Wire.read(); // Get value for setting the LED on/off from I2C buffer.
      pinTrig(pin, pinState);// Call the pinOff function to turn the LED off.
    }
    break;
    case 'c':{
      int pin = Wire.read(); // Get Arduino pin value from I2C buffer.
      int pinState = Wire.read(); // Get value for setting the LED on/off from I2C buffer.
      pinTrig(pin,pinState); // Trigger relay pin on.
    }
    break;
    case 'd':{
      int pin = Wire.read(); // Get Arduino pin value from I2C buffer.
      int pinState = Wire.read(); // Get value for setting the LED on/off from I2C buffer.
      pinTrig(pin,pinState); // Trigger relay pin on.
    }
    break;
  }
}
// I2C requestEvent interrupt.
void requestEvent() { // When data is requested by the master send a byte array back.
  // I2C data buffer to store all the bytes we want to send.
  data[0] = pin; // The pin on the master we will set the analog value to from slave pin.
  data[1] = pinState; // Debug pin state.
  data[2] = f2bt.b[0]; // Temp MSB.
  data[3] = f2bt.b[1]; // Temp M1.
  data[4] = f2bt.b[2]; // Temp M2.
  data[5] = f2bt.b[3]; // Temp LSB.
  data[6] = f2bh.b[0]; // Humi MSB.
  data[7] = f2bh.b[1]; // Humi M1.
  data[8] = f2bh.b[2]; // Humi M2.
  data[9] = f2bh.b[3]; // Humi LSB.
  // Moved to their respective sections.
  //data[10] = 100; // Encoder (0 - 100).
  //data[11] = 0; // Hall Effect Open (0/1).
  //data[12] = 1; // Hall Effect Closed (0/1).
  data[13] = 234; // Mode Register.
  data[14] = 56; // Configuration Register.
  data[15] = 13; // Identification Register.
  for (byte b: data) {Wire.write(b);} // Load our data byte array into I2C buffer.
}
// Function to trigger pin state.
void pinTrig(int pin, int state) {
  digitalWrite(pin, state);
}

It may be that I wait 5 mins (set to 10s for debugging in the code right now) before sending data to the MQTT Broker, but this worked fine when I was not using I2C which the interrupt is probably causing the keepalive timeout or interrupting it when it's about to send delaying it for another 10 seconds somehow.

@knolleary
Copy link
Owner

@NonaSuomy I'm not going to pick through nearly 1000 lines of code...

I have had some reports of interrupts causing issues. I've never had to use the library with interrupts, so I don't know if that is a real issue or not.

One suggestion is to add a Serial.println before your call to mqttclient.loop(); to print out the current values of millis() - that will then give you an indication how ofter you are calling the loop function - which is how the keepalive gets serviced.

@NonaSuomy
Copy link
Author

NonaSuomy commented Jan 27, 2017

It's only 438 lines in the master where MQTT is concerned and it's well commented probably 50% less without that, another 20% serial debug and pretty linear if you clicked the first shared link it's less scary looking. The second link is for the slave which doesn't deal with MQTT it just tosses the sensor values onto the I2C bus or reads to trigger a relay.

Thanks for the tip though was more or less what I was looking for.

The areas where I have inserted the PubSubClient stuff does that all look copesetic?

The Master basically just grabs sensor values from the I2C bus slaves and then PubSubClient tosses the sensor values to appropriate subscribed topics on the MQTT Broker via Ethernet2. Nothing overly complicated.

I will see what millis discovers for timings.

@Tau-Pi
Copy link

Tau-Pi commented Jan 25, 2018

@knolleary , I know, the thread is a bit older, but concerning timeouts, I have a question.

Imagine a MQTT connection has been established between an embedded device and a broker. The embedded device's payload is about 20 MB and should be sent once a minute (just to mention something). Now think about beginning a publish and abruptly pausing the transfer due to necessary operations internally (writing/reading to/from flash, etc.). Let's say this takes half a second or maybe 2 - 5 seconds. Is there a timeout defined somewhere for this case? It's quite clear, that the client is not able to send a PINGREQ because it would interfere with the payload data.

Will the broker still be listening without getting the rest of the payload within the keepalive interval? Sure, exceeding the keepalive interval will force a disconnect.

Thanks in advance!

@brianbrown1962
Copy link

No offense will have been taken. I can assure you of that.

The client determines what keepalive is used. Mosquitto defaults are irrelevant to the behaviour of this client - mosquitto must honour what the client says it is going to use.

You cannot make any assumptions about a typical client. I have used this in a wide variety of devices and network conditions where different timeouts have been necessary.

I am not inclined to change the default value. It had been 15 seconds for 7+ years. If anything, we will make it easier to customise and not rely on editing the header file.

@knolleary , Nick, just wondering if there has been a configuration option added to your pubsubclient library to change the keepalive in code rather than in the .h file? I'm not seeing it anywhere but I could be missing it. I certainly have use cases for this and I'm sure many others too with the advent of IoT and sensors reporting to brokers far less often to conserve power, etc.
By the way, thank you for your work Sir! This library, node red, all the other works you have done...you can take credit for advancing the entire industry to where it is today, and enabling us to do what we do!

m4ng0 added a commit to m4ng0/ToscoLock that referenced this issue Jan 11, 2019
knolleary/pubsubclient#239
So I customized PubSubClient library to set the keepalive under 10 seconds
(I will look for a cleaner solution) and added a delay between wifi and
mqtt connection checks.
@ayavilevich
Copy link

This has to be an option that one can pass/change in run time. Having this hard coded doesn't work for all cases.

@knolleary
Copy link
Owner

@ayavilevich the latest version of the library allows you to set it.

https://pubsubclient.knolleary.net/api#setKeepAlive

@MoHa173
Copy link

MoHa173 commented Nov 19, 2020

@knolleary
Hello, I have the problem that I have a method called from a received message from another MQTT client. This method duration is very long and the loop is not executed during this time.
Is there any way to send the ping packet manually? My idea is to send the keepalive before calling this method to make sure the keepalive time doesn`t expires during the method.

I hope you can help me.

Thx a lot

@jooseph9
Copy link

@MoHa173 you can increases the keepalive period

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants