Uso correcto de mqtt

Hola a todos.

Estoy usando mqtt para loggear datos de sensores (temperatura, humedad, ruido, ...). Hasta 18 valores.

Para ello uso mqtt y la librería <MQTT.h> de Joël Gähwiler

Mucha gente usa la librería pubsubclient y he visto que existe esta otra librería llamada ArduinoMqttClient, en la que trabaja también Sandeepmistry. Y bueno, estoy un poco desconcertado por cual es la buena, aunque supongo que las tres son la buena...

Los ejemplos "oficiales" de arduino usan la de Joël Gähwiler, así que también he tirado por ahí. La de ArduinoMqttClient no usa la función loop, sino que usa mqttClient.poll(). Supongo que es lo mismo...

Mi problema, es un poco saber si lo hago bien. Me tiende a desconectarse bastantes veces, y estoy un poco desconcertado.

/*
  ArduinoMqttClient - WiFi Simple Sender

  Inspired by:
  https://create.arduino.cc/projecthub/officine-innesto/control-your-iot-cloud-kit-via-mqtt-and-node-red-114b4b?ref=user&ref_id=65561&offset=0

  This example connects to a MQTT broker and publishes a message to
  a topic once a time.

  The circuit:
  - Arduino MKR 1000, MKR 1010 or Uno WiFi Rev.2 board
*/

// https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoWiFi101/ArduinoWiFi101.ino
#include <MQTT.h>

#ifdef ARDUINO_SAMD_MKRWIFI1010
#include <WiFiNINA.h>
#define WIFILIB    "WIFININA" 
#elif ARDUINO_SAMD_MKR1000
#include <WiFi101.h>
#define WIFILIB    "WIFI101" 
#else
#error unknown board
#endif

#include "variables.h"

#include "Func_MKREnv.h"
#include "arduino_secrets.h"
#include "pm25sensor.h"

#define BROKER_IP    "192.168.1.43" // important: you have to change this to your IP brocker Addres
//#define BROKER_IP    "192.168.30.10" // important: you have to change this to your IP brocker Addres
//#define BROKER_IP    "192.168.1.52" // important: you have to change this to your IP brocker Addres

const char ssid[]     = SECRET_SSID;    // Network SSID (name)
const char pass[]     = SECRET_PASS;    // Network password (use for WPA, or use as key for WEP)

const int   port      = 1883;
String      topic     = "/arduino/logData/";  
const int   sensorId  = 4;   
String      sensorLabel = "holaPlatanete";

long        logInterval =  10000; // 180000;         // 3 minutes
long        lastMillis  = -logInterval;     // to start logging from beginning
// long int goes from  -2147483648 to 2147483647

WiFiClient net;
MQTTClient client(256);                 // payload increased size from 128 to 256
String myMacAddress;

void connect() {
  Serial.print("Checking wifi...");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }

  Serial.print("Attempting to connect to the MQTT broker: ");
  Serial.println(BROKER_IP);
  while (!client.connect(myMacAddress.c_str(), MQTT_USER, MQTT_PASS)) {
    Serial.print(".");
    delay(5000); // if I got disconnected, because same name at same time, tries to connect 5 seconds after
  }
  Serial.println("\nconnected!");
 
  //client.setOptions(int keepAlive, bool cleanSession, int timeout);
  // The keepAlive option controls the keep alive logInterval in seconds (default: 10).
  // The cleanSession option controls the session retention on the broker side (default: true).
  // The timeout option controls the default timeout for all commands in milliseconds (default: 1000).  
  // client.setOptions(180, true, 1300);  
  client.setOptions((1.05 * logInterval) /1000., true, 3000); 

  client.setWill(topic.c_str(), "\"Error\":\"disconnected...\"", true, 2);
  // void setWill(const char topic[]);
  // void setWill(const char topic[], const char payload[]);
  // void setWill(const char topic[], const char payload[], bool retained, int qos);

  client.subscribe("/hello"); //SUBSCRIBE TO TOPIC /hello
  client.subscribe("/temp"); //SUBSCRIBE TO TOPIC /t
  client.subscribe("/humidity"); //SUBSCRIBE TO TOPIC /h
  client.subscribe("/arduino/setLogIntrvMin");
  client.subscribe("/arduino/setLogIntrvMili");
  client.subscribe("/arduino/getLogIntrvMili");
  client.subscribe("/arduino/getSensorLabel/myPersonalMac");
  client.subscribe("/arduino/setSensorLabel/#");  
}

void messageReceived(String &topic, String &payload) {
  Serial.println("incoming: " + topic + " - " + payload);

  if (topic == "/arduino/setLogIntrvMin"){
    logInterval  =  payload.toInt() * 1000 * 60;
  }

  if (topic == "/arduino/setLogIntrvMili"){
    logInterval  =  payload.toInt();     
  }

  if (topic == "/getLogIntrvMili"){
    String dataString = "";
    dataString = "{\"clientMac\":\"" + myMacAddress + "\",\"sensorID\":" + String(sensorId); 
    dataString += ",\"measurementFrec\":\"" + String(logInterval) + "}";
    client.publish(topic, dataString);
  }

  // "/getSensorLabel//myPersonalMac"
  String myString = "/arduino/setSensorLabel/";
  myString += myMacAddress;
  if (topic == myString){
    sensorLabel = payload;
  }

  if (topic == "/setSensorLabel//myPersonalMac"){
    // check with IPAddress
    // sensorLabel = payload;   
  }
}

void setup() {
 //Initialize serial and wait for port to open:
  Serial.begin(9600);
  delay(3000);
 // while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
//  }

  // attempt to connect to Wifi network:
  Serial.print("Attempting to connect to WPA SSID: ");
  Serial.println(ssid);
  while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
    // failed, retry
    Serial.print(".");
    delay(5000);
  }
  Serial.println("You're connected to the network");
  Serial.println();
  
  delay(3000);

  // Initializes Enviroment Shield
  initializeEnvShield();

  printMacAddress();
  myMacAddress = getMacAddress();
  topic += myMacAddress ;

  Serial.print("MAC: ");
  Serial.println(myMacAddress);
  Serial.print("Topic: ");
  Serial.println(topic);
  Serial.println("Set broker and net.");

  client.begin(BROKER_IP, net);
  
  Serial.println("Set on message received.");
  client.onMessage(messageReceived);

  connect();
}

void loop() {
  // call loop() regularly to allow the library to send MQTT keep alives which
  // avoids being disconnected by the broker

  client.loop();
  delay(30);
  
  if (!client.connected()){
    Serial.println("Reconnecting inside loop_______________________________________________");
    connect();
  }

  // avoid having delays in loop, we'll use the strategy from BlinkWithoutDelay
  if (millis() - lastMillis >= logInterval) {
    // reads all different sensor data
    readEnvData(); 
    lastMillis = millis();
    
    String dataString = "";
    dataString = "{\"clientMac\":\"" + myMacAddress + "\",\"sensorID\":" + String(sensorId); 
    dataString  += ",\"label\":\""  + sensorLabel + "\""; 
    AddMeasurumentDataToString( dataString ); 
    dataString += "}";

    Serial.print("topic: ");
    Serial.println(topic);
    Serial.print("dataString: ");
    Serial.println(dataString);

    // see https://github.com/256dpi/arduino-mqtt
    // bool publish(const char topic[], const char payload[], bool retained, int qos);
    // client.publish(topic, dataString);
//    client.publish(topic, dataString, false, 2);
    client.publish(topic, dataString);
  }
}

Sé que hay varias cosas que no están del todo bien. Los datos acaban en el topic /arduino/logData/. Como identificador, uso la MAC.

Ahora mismo, loggea datos cada 10 segundos y a los 5 minutos se ha desconectado y vuelto a conectar, y a los 5 minutos ha vuelto a hacer lo mismo...

Los arduinos estarán etiquetados, label 1, label 2, ... y yo uso esa id como SensorID. Como nombre uso la MAC.

Como broquer uso Mosqitto, y desde NodeRed controlo el flujo de datos, exporto a InfluxDB y visualizo con Grafana. Como quiero loggear más datos por el día que por la noche, desde NodeRed he puesto un disparador, que entre las 7h y 8h le dice a todos los arduinos que deben loggear cada p.ej. 5 minutos. Luego a las 18h, se vuelve a disparar y mandar a los arduinos loggear cada 20 minutos. Ahora estoy trabajando con un arduino, pero la idea es llegar a usar hasta 18. Para diferenciarlos, debe cambiar el número ID. El resto lo hago desde Node Red (como darle un nombre con sentido).

No uso un mensaje por medición, sino que meto todas las mediciones de los diferentes sensores (el código aún no está acabado) en un string (por eso aumento el tamaño del payload a 256).

Navegando por ahí, me he encontrado con aquí recomienda hacer un delay de 30ms antes de cada loop. Por eso lo acabo de poner, aunque se me sigue desconectando cada 5 o menos minutos. He empezado a leer y usar mqtt hace casi tres semanas, y mola muchísimo.

El servidor está en un RPI3.

¿Algunos consejos? El código aún no está terminado.

Una librería MQTT envia paquetes de datos, no entiendo como puedes suponer que hay una buena y otras malas. Para mi funcionan o no funcionan.
Si la librería 1 funciona entonces Okay. Si falla uso otra librería. No deberías complicarte mucho. Tienes para elegir, elige lo mas estable y confiable.

Ahora con paquetes cada 10 segundos creo que estas saturando el sistema pero solo es una suposición. Porque simplemente no agrandas el tiempo de envio de datos a digamos 30 segundos y observas como se comporta el sistema.
Lo haces con cada librería y tomas nota del comportamiento y luego podrás sacar conclusiones comparando resultados.

Muchas gracias Surbyte. Lo haré.

Me he dado cuenta que he usado un usuario equivocado al subir el post. Era una cuenta de prueba...

Bueno. El arduino ha estado 2horas sin desconectarse. Supongo que es buena señal.

Lo que sí que ha ocurrido, es que el led naranja ha comenzado a parpadear. El arduino está conectado a un cargador de móvil fijo.

¿A qué puede ser debido?

¿Overflow dentro del código? No he encontrado soluciones claras en la red.

Uso una placa mkr1010 y un EnvShield.

Hola a todos.

Llevo 24 horas con 4 arduinos subiendo datos cada 5 minutos por medio de MQTT a un servidor RPI, y funciona muy bien. Solamente se han desconectado 3 una vez y 1 dos veces. Han vuelto a conectarse a los 5 minutos.

Analizando el log de mosquitto, sale que tengo ocasionalmente un TimeOut. Por lo tanto aumentaré ese valor (p.ej. a 2.3 veces LogTime).

Pregunta:

¿Porqué en mis arduinos empiezan a parpadear el led naranja a la hora y media aproximadamente después de subir el código? ¿Puede ser debido a algún overflow de alguna variable? ¿Puede ser debido a que hago uso excesivo del String-Object?

Respecto a qué librería Arduino usar, la ArduinoMqttClient está en fase alfa (creo, puede que me equivoque). Tengo la sensación de que está todavía en pruebas. Lo que me extraña, es que los ejemplos oficiales no llaman a la función de conectarse, en caso de desconexión (hay un fork de un fablat italiano, que lo tiene implementado, pero no encuentro ahora el link; simplemente reconectan en caso de no estar conectados). Me extraña que no esté en el ejemplo oficial. Además en muchos sitios recomiendan meter un delay en el loop principal (p.ej. delay(100)). Tampoco está puesto de forma clara en la doc de ArduinoMqttClient, pero sí en la de MQTT.h.

¿Bueno, algún consejo sobre el led naranja?

Revisa el keepAlive en la configuracion de la librería MQTT... puede ser que si tienes un proceso en Arduino que dura más de ese tiempo el servidor MQTT interprete que se ha desconectado. Y otra fuente de errores son los errores del ESP (cuando se fríe y se reinicia hace que haya una desconexión)

Yo uso la de PubSubClient y me va fenomenal.

Gracias bokeauss!!!

Mis parámetros son:

  keepAlive = int(1.02 * (logInterval / 1000.));
 // logInterval : ms entre log y log 
 // cleanSession : true (default)
  // timeout : 3000 (ms)
  client.setOptions(keepAlive, true, 3000);

En las últimas 24 horas se han desconectado 2 de 4, por 5 minutos.

Mi log de mosquitto en una de esas desconexiones es así:

1579153982: Client 3C71BF96A634 has exceeded timeout, disconnecting.
1579153982: Socket error on client 3C71BF96A634, disconnecting.
1579153983: New connection from 192.168.1.50 on port 1883.
1579153983: New client connected from 192.168.1.50 as 3C71BF96A634 (c1, k183, u'admin').

1579131260 = (Thu, 16 Jan 2020 05:53:02 +0000 in RFC 822, 1036, 1123, 2822)

Pregunta tonta:

  • Son los datos largos más propensos a perderse (payload de 220 char p.ej.)? (he ampliado el max. payload a 256)

  • Es preferible mandar en cada medición varios mensajes, con los diferentes valores? (mido hasta 18 valores; puedo mandar 9 en un payload y otros 9 en otro payload) Subo los datos luego a InfluxDB y txt local mediante NodeRed.

  • Porqué motivo se me ponen a parpadear los mkr1010 con el led naranja, como si fuesen árboles de navidad?

payload de 220 caracteres? qué estas enviando?

Lo del led naranja no te lo puedo responder porque no tengo ningún MKR

Mi payload es un string tipo JSon, que contiene aprox. 18 valores de mediciones y su label, tipo

{"clientMac":"840D8E1E6E89",""sensorID":1,"label":"secretaria","temp":23.25919,"hum":54.15326,"noiseLevel":23, "dB":23,"ppm2.5":4,... } y así hasta 18 valores. Tengo que redactarlo de forma más formal, pero tengo calidad del aire, ppm2.5, ppm5, ppm10, eCO2, VOC, ... Bueno, bastantes valores.

Algo así. Hasta ahora no me había preocupado de hacerlo corto. En cuanto a los float, puedo restringir la cantidad de decimales.

Ahora, estoy enviando en cada medición un string, formado por todos los datos de medición. Esto lo recibe NodeRed, que le añade el tiempo, y lo guardo en un fichero de texto. Así tengo los datos de medición ordenados en el txt.

Además lo guardo en influxDB.

Mi pregunta sería: es recomendable mandar cada medición en un mensaje, tipo:

{"clientMac":"840D8E1E6E89",""sensorID":1,"label":"secretaria","temp":23.25919}
{"clientMac":"840D8E1E6E89",""sensorID":1,"label":"secretaria","hum":54.15326}
{"clientMac":"840D8E1E6E89",""sensorID":1,"label":"secretaria","dB":23}
{"clientMac":"840D8E1E6E89",""sensorID":1,"label":"secretaria","ppm2.5":4}

El fichero de texto, quedaría a cachos, y es más engorroso tratarlo directamente en excel. Evidentemente, a influxDB le dá igual.

¿Me aconsejais mandar cachitos? Si sí, ¿porqué? Desde luego, si le pasa algo a una medición, solamente pierdo ese valor... Bueno, no tengo experiencia en eso.

¿Cómo lo hacéis vosotros?

Olvida mi comentario sobre el tamaño del payload. Ahora que leo tu respuesta tienes razón.
Todo eso lo envia un solo MKR? No puedes parcializarlo?

Gracias surbyte.

No. El proyecto consiste en monitorizar todos esos valores en las diferentes aulas del colegio durante todo un año o más. Bueno, durante todo el tiempo que sea necesario, para obtener valores fiables que expliquen el clima del cole.

Mi idea es con los chavales desarrollar un solo código, y luego a través de NodeRed (de forma centralizada) asignar un label a cada arduino. Los chavales harán el proyecto de medir la temperatura con el EnvShield (ejemplo de Arduino), subirán los datos al entorno de arduino (así cada uno lo ve en su usuario), y al final subirán los datos al servidor.

Surbyte: ¿Recomiendas parcializar los datos y enviar paquetes más pequeños?

Ahhh tu ya habias venido con este tema!! Ahora lo recuerdo.
Bueno, hay que hacer que funcione.
Yo como otros podemos intentar probar el código pero usando otra placa y entonces no se si es válido para tu proyecto esa prueba.

Sí surbyte. Gracias :slight_smile:

  • ¿Teneis alguna experiencia mandando payloads largos (250bytes) durante semanas?

  • ¿Puede ser negativo usar el objeto String de forma masiva en un loop? (lo digo por la reserva continua de memoria dinámica, y no usar arrays fijos, a pesar de conocer la longitud del array).

  • Lo que me mosquea, es en que los mkr1010 empieza a parpadear el led naranja a las 2 o 3 horas de estar en funcionamiento. Siguen funcionando, mandando datos, que parecen estar correctos. ¿Os importa si abro otro hilo respecto a eso en el foro inglés dedicado a mkr1010 (no sé si está bien visto tener varios hilos abiertos)?

Llevo casi dos semana con tres mkr1010 y dos librerías diferentes, y va bastante bien. En ocasiones algún arduino se desconecta, pero vuelve a conectarse (algún día sí, otro no). Me irrita que no recibo mensajes de LastWill. O no he mirado bien, o no sé... Para acotar, estoy enviando mensajes cortos, de unos 40 caracteres.

Ayer cambié mi RPI3 por una RPI4, y además he actualizado a la última versión de mosquitto y NodeRed.

Durante esta semana mandaré payloads largos a los arduinitos, e intentaré evitar la función string ( a ver si ayuda al parpadeo naranja).

Bueno, muchas gracias y abrazos a tod@s :wink:

No puedes abrir otro hilo en el foro inglés. Debes cerrar este y abrir algo similar si te apetece.

Ayer cambié mi RPI3 por una RPI4, y además he actualizado a la última versión de mosquitto y NodeRed.

Esto si que no cambia nada.

¿Teneis alguna experiencia mandando payloads largos (250bytes) durante semanas?

Yo no creo haber superado 100 bytes x payload. Pero no tuve problemas. Recibía en mi RPi3 e incluso en una RPi2 luego todo lo terminé instalando en una notebook vieja a la que le instale UBUNTU

¿Puede ser negativo usar el objeto String de forma masiva en un loop? (lo digo por la reserva continua de memoria dinámica, y no usar arrays fijos, a pesar de conocer la longitud del array).

Todo String mal manejado puede dar problemas. Solo debes asegurarte que al final de su uso lo pongas a 0 con un simple String nombreVariable = "";
He visto comparaciones donde el mito del String esta sobredimensionado. E incluso su uso hoy es mas eficiente que otras opciones. Pero ese es otro tema.

Coloca en tu código algo que te diga si tu memoria esta alcanzando limites de tu RAM y observa ese comportamiento.
El led Naranja que tanto mencionas no se que indica.

Estas en tu derecho de cerrar el hilo cuando lo indiques.
Me envias un privado cuando lo consideres.

Hola otra vez. Gracias por vuestra ayuda!

surbyte:
Todo String mal manejado puede dar problemas. Solo debes asegurarte que al final de su uso lo pongas a 0 con un simple String nombreVariable = "";
He visto comparaciones donde el mito del String esta sobredimensionado. E incluso su uso hoy es mas eficiente que otras opciones. Pero ese es otro tema.

Quieres decir, que en todo lugar que use el objeto string, debo poner al final del bucle el string a "". Ok, lo probaré, pero no lo he visto en ningún sitio...

Que os parece este link

Habla sobre ese tema, y recomienda arrays fijos de caracteres.

surbyte:
Coloca en tu código algo que te diga si tu memoria esta alcanzando limites de tu RAM y observa ese comportamiento.

¡Haré primero eso! Y luego terminar los Strings con "".

No es exactamente asi.
Usalo como gustes pero al final si ese String forma parte de concatenaciones solo para evitar que siga sumando caracteres al final de tu loop lo pones a 0 de esa forma

String nombreVariable 
 = "";

OK.

Cierro el hilo. Creo que ya está claro.

Muchísimas gracias a tod@s!!!