Enviar variable float mediante MQTT

Buenas noches!

Acudo a vosotros porque necesito ayuda. Llevo poco tiempo con Arduino y estoy dando mis primeros pasos, no controlo mucho de programación y hay cosas que voy aprendiendo a medida que voy haciendo, pero aquí me he estancado un poco. Os explico:

tengo una Shield de e-Health que permite medir la temperatura corporal con un sensor que le conectas mediante un jack. Esto funciona bien y la temperatura es correcta, copio el código por si alguno lo necesita:

#include <eHealth.h>
#include <eHealthDisplay.h>

void setup() {
Serial.begin(9600);
}

void loop() {
float temperatura = eHealth.getTemperature();
Serial.print(temperatura);
delay(500);
}

Quiero enviar ese dato, el float de la temperatura, a un servidor mediante el protocolo MQTT. Al principio pensé en conectar encima del arduino la Shield de e-Health, y encima de esta Shield conectar también la Shield de la wifi. ¿Cuál es el problema? Que la Shield de la wifi necesita el pin 7 para hacer el handshake, y la Shield de e-Health necesita casi todos los pines así que no se puede hacer lo que había pensado.

Como el proyecto que estamos haciendo consta de varios arduinos, he pensado: bueno, pues mando la variable de la temperatura a otro Arduino que tenga la Shield de la wifi, y que este último se encargue de mandar el dato al servidor MQTT.

#include <SPI.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <SoftwareSerial.h>



//Datos de red y defiir status como apagado
char ssid[] = "ssid";
char pass[] = "password";
int status = WL_IDLE_STATUS;

//MQTT
char mqttUsername[] = "";      //Nombre de usuario MQTT
char mqttPassword[] = "";   //Contraseña MQTT

WiFiClient arduino;
void callback(char*topic, byte*payload, unsigned int length);
PubSubClient mqttClient("servidor",puerto,callback,arduino);

void callback(char*topic, byte*payload, unsigned int length) {
    // Allocate the correct amount of memory for the payload copy
  char*almacen;
  almacen=new char[length+1];
  int i=0;
  for(i=0;i<length;i++) {
    almacen[i]=payload[i];
  }
  almacen[i]='\0';
  //FIN DEL MENSAJE

  String msg=almacen;
  Serial.println(msg);

  if(msg == "ON"){
    digitalWrite(8,HIGH);
    mqttClient.publish("from/arduino","LED ON");
  }
  if(msg == "OFF"){
    digitalWrite(8,LOW);
    mqttClient.publish("from/arduino","LED OFF");
  }
  if(msg == "TEMPERATURA") {
    while(!Serial.available());
    float tempfl = Serial.parseFloat();
    char temp[5];
    dtostrf(temperatura_fl,4,2,temperatura);
    mqttClient.publish("from/arduino",temperatura);
  }
}


void reconnect() {
  // Loop until we're reconnected
  while (!mqttClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    mqttClient.connect("arduino",mqttUsername,mqttPassword);
    if (mqttClient.connected()) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      mqttClient.publish("from/arduino","hello world");
      // ... and resubscribe
      mqttClient.subscribe("to/arduino");
    } else {
      Serial.print("failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup()
{
  Serial.begin(9600);
  pinMode(8,OUTPUT);
//WIFI
  Serial.println("Intentando conectarse a la red...");
  status = WiFi.begin(ssid,pass);
  //Si no te has conectado, párate aquí
  if (status != WL_CONNECTED) {
    Serial.println("No se ha podido conectar a la red");
    while(true);
  }
  //Si te conectas...
  else {
    Serial.println("Conectado a la red");
  }
//WIFI

}

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

El problema lo tengo en esta parte:

 if(msg == "TEMPERATURA") {
    while(!Serial.available());
    float tempfl = Serial.parseFloat();
    char temp[5];
    dtostrf(temperatura_fl,4,2,temperatura);
    mqttClient.publish("from/arduino",temperatura);
  }

mqttClient.publish() pide que el payload que mande sea un char, y tras estar un rato investigando he escrito ese código. El problema es que la temperatura que me manda, al principio es de 28 grados, y tras esto me empieza a marear dándome valores de 0º, 0.1 etc., valores que no corresponden con la realidad.

A ver si alguno me puede echar una mano, que me estoy volviendo loco!

Pongo también el link de la librería que uso para enviar datos mediante MQTT, se llama PubSubClient y la página oficial es esta: http://pubsubclient.knolleary.net

Un saludo y gracias de antemano.

Yo veo un leve error ahi, mira
defines tempfl y luego usas tempera_fl como tu variable float.
debes usar

    float tempfl = Serial.parseFloat();
    char temp[5];
    dtostrf(tempfl ,4,2,temp);
    mqttClient.publish("from/arduino",temp);

Tienes razón, anduve como loco haciendo pruebas y el código está mal. De todas maneras, tras sustituirlo al código que pongo debajo, sigue sin funcionar: no me transmite ningún dato al servidor MQTT :confused:

  if(msg == "TEMPERATURA") {
    while(!Serial.available());
    float tempfl = Serial.parseFloat();
    char temp[5];
    dtostrf(tempfl,4,2,temp);
    mqttClient.publish("from/arduino",temp);
  }

Seguimos investigando... a ver si alguien me puede echar un cable.

Saludos!

has probado quitando el ! de Serial.available() ?

jose:
has probado quitando el ! de Serial.available() ?

¡Qué grande! Había descubierto hace un poco que el problema era la comunicación entre los dos arduinos, y ya me iba a poner a salsear. Quitándole el signo de exclamación se comunican correctamente y al menos el Arduino con la wifi Shield me envía los datos.

Pero ahora tengo otro problema, y es que el valor que me da es de 0 grados todo el rato. A investigar...

Gracias otra vez!

Acabo de darme cuenta de que el problema está en la comunicación entre los dos arduinos. Cuando quiero copiar el float de la temperatura, no lo copia, siendo el valor de este = 0 en el Arduino con la Shield de la wifi. Y claro, si el float es 0, por mucho que convierta luego a un char... este sigue siendo cero.

float tempfl = Serial.parseFloat();

Esta parte estaría bien?

No me queda claro como conectas los arduinos,porque en el codigo que muestras incluyes la libreria softwareserial pero no veo que definas un objeto de este tipo ni que lo inicialices.Por otro lado ,defines un array de 5 elementos ,pero con

   dtostrf(tempfl,4,2,temp);

le estas diciendo que dedique 4 caracteres para la parte entera y 2 para la decimal + el final de cadena y no se si la coma ocuparia un caracter mas.....

jose:
No me queda claro como conectas los arduinos,porque en el codigo que muestras incluyes la libreria softwareserial pero no veo que definas un objeto de este tipo ni que lo inicialices.Por otro lado ,defines un array de 5 elementos ,pero con

   dtostrf(tempfl,4,2,temp);

le estas diciendo que dedique 4 caracteres para la parte entera y 2 para la decimal + el final de cadena y no se si la coma ocuparia un caracter mas.....

Tienes razón con ambas cosas: la librería de SoftwareSerial la usaba sin sentido puesto que comunico los dos arduinos mediante los pines 0 y 1 (TX para el arduino con la Shield del termómetro, y RX para el de la Shield wifi). Además, junto las masas de los dos arduinos con otro cable, por lo que conecto ambos arduinos con dos cables.

Respecto a lo del array de 5 elementos, también tienes razón, mal usado. Ahora he definido un array de 6 elementos (00.00 (5 valores) + final de cadena).

Así he conseguido que mande bien el dato! Pero sólo una vez, a la segunda ya me empieza a marear:

Ahora el código está así:

  if(msg == "TEMPERATURA") {
    Serial.flush();
    while(Serial.available() == 0);
    float tempfl = Serial.parseFloat();
    char temp[6];
    dtostrf(tempfl,2,2,temp);
    mqttClient.publish("from/arduino",temp);
  }

No sé si será por el dtostrf()... habrá que investigar.

Saludos y gracias!

Pero no puedes usar los pines 0 y 1 para comunicacion si estas usando el Serial Monitor...Tienes que darle un repaso a la referencia del objeto Serial para ver como funciona:

En particular y suponiendo que estas usando la UNO:

All Arduino boards have at least one serial port (also known as a UART or USART): Serial. It communicates on digital pins 0 (RX) and 1 (TX) as well as with the computer via USB. Thus, if you use these functions, you cannot also use pins 0 and 1 for digital input or output.

En ese mismo enlace tienes los metodos del objeto Serial,nunca he usado .flush() ,no me queda claro que hace ahora ,en versiones antiguas limpiaba el buffer de entrada ,si miras el metodo .available() Serial.available() - Arduino Reference te dice que devuelve el numero de bytes disponibles para leer del buffer de entrada,luego ,y te conviene tambien un repaso a los comandos condicionales,con esto:     while(Serial.available() == 0); le estas diciendo lee un float mientras la cantidad de bytes disponibles para leer sea 0 ,o sea solo entrara a leer cuando no haya nada que leer....
Posibles soluciones ,si vas a usar los pines 0 y 1 para Serial Monitor ,define un objeto SoftwareSerial https://www.arduino.cc/en/Reference/SoftwareSerial con dos pines que te sobren del arduino con el sensor e idem con el del wifi,cablea cruzando cables rx con tx ,tx con rx.Define con que frecuencia y que cantidad de lecturas mandaras de uno a otro ,por ejemplo si no mandas lecturas continuamente ,sino cada x segundos,puedes hacer esto:

if(msg == "TEMPERATURA") {
    if(Serial.available()){
      float tempfl = Serial.parseFloat();
      char temp[6];
      dtostrf(tempfl,2,2,temp);
      mqttClient.publish("from/arduino",temp);
    }
  }

Si vas a mandar muchas lecturas seguidas o bien se pueden acumular varias lecturas en el buffer,separalas con algun caracter o un espacio,para que parseFloat() pueda distinguir entre diferentes lecturas.

jose:
Pero no puedes usar los pines 0 y 1 para comunicacion si estas usando el Serial Monitor...Tienes que darle un repaso a la referencia del objeto Serial para ver como funciona:
Serial - Arduino Reference
En particular y suponiendo que estas usando la UNO:En ese mismo enlace tienes los metodos del objeto Serial,nunca he usado .flush() ,no me queda claro que hace ahora ,en versiones antiguas limpiaba el buffer de entrada ,si miras el metodo .available() Serial.available() - Arduino Reference te dice que devuelve el numero de bytes disponibles para leer del buffer de entrada,luego ,y te conviene tambien un repaso a los comandos condicionales,con esto:     while(Serial.available() == 0); le estas diciendo lee un float mientras la cantidad de bytes disponibles para leer sea 0 ,o sea solo entrara a leer cuando no haya nada que leer....
Posibles soluciones ,si vas a usar los pines 0 y 1 para Serial Monitor ,define un objeto SoftwareSerial https://www.arduino.cc/en/Reference/SoftwareSerial con dos pines que te sobren del arduino con el sensor e idem con el del wifi,cablea cruzando cables rx con tx ,tx con rx.Define con que frecuencia y que cantidad de lecturas mandaras de uno a otro ,por ejemplo si no mandas lecturas continuamente ,sino cada x segundos,puedes hacer esto:

if(msg == "TEMPERATURA") {

if(Serial.available());
    float tempfl = Serial.parseFloat();
    char temp[6];
    dtostrf(tempfl,2,2,temp);
    mqttClient.publish("from/arduino",temp);
  }



Si vas a mandar muchas lecturas seguidas o bien se pueden acumular varias lecturas en el buffer,separalas con algun caracter o un espacio,para que parseFloat() pueda distinguir entre diferentes lecturas.

Muchas gracias por la respuesta, no sabes lo bien que me viene!

Hoy tengo una tarde liada así que no sé si podré enredar mucho con los arduinos, pero como mucho mañana a la mañana probaré. Lo único, una pregunta: cuando dices que esto está mal:

    while(Serial.available() == 0);

Porque le estoy diciendo que lea un float del puerto serie cuando en realidad no hay nada, porque Serial.available lo que te dice es la cantidad de bytes es de 0... fíjate que hay un punto y coma. Tenía entendido que si pones un punto y coma, se queda en esa línea hasta que esa condición deje de cumplirse, como si pusiéramos:

while(Serial.available() == 0) 
{}

De esta manera, ¿no estamos diciendo que mientras Serial.available dé 0 bytes como respuesta, que no haga nada? ¿O estoy equivocado?

Un saludo.

Cierto ,se me ha pasado que no habias puesto llaves detras del while ,pero tal como lo has puesto,lo que haces entonces es bloquear ahi el programa mientras no haya datos ,lo cual tampoco es correcto.Lo idea seria :
-pregunto si hay datos disponibles para leer ,si los hay ,los leo ,sino sigo ejecutando el resto del codigo.
Corrijo lo que te puse arriba y aqui:

if(msg == "TEMPERATURA") {
    if(Serial.available()) {
      float tempfl = Serial.parseFloat();
      char temp[6];
      dtostrf(tempfl,2,2,temp);
      mqttClient.publish("from/arduino",temp);
    }
  }