¿Cómo agregar los datos de otro firmware a la red que agrupa los sensores de un/loquer?

A partir del interés de http://www.aire.valenciasanchez.com/ por aportar sus mediciones a la red que expone un/loquer pretendemos describir la trama de datos que debe recibir el punto de llegada : http://aqa.unloquer.org:8086

Influx recibe los datos estructurados como una serie compuesta de:

  • mediciones (measurements),
  • etiquetas (tags)
  • campos (fields)

Se estructura con la sintaxis:

<measurement>,<tag>[,<tags>] <field>[,<field>] <timestamp>

Para un sensor que esta transmitiendo en vivo puede formar una trama sin el timestamp, influx asignará un timestamp apropiado al recibir la medición.

Las mediciones que se evían deben adaptarse al Line Protocol, ej:

volker0001,id=volker0001 lat=6.268115,lng=-75.543407,altitude=1801.1,course=105.55,speed=0.00,humidity=37.00,temperature=25.00,pm1=22,p
m25=31,pm10=32

Que al consultar en influx se presentara por ejemplo así:

time                altitude course humidity id         lat      lng        pm1 pm10 pm25 speed temperature
----                -------- ------ -------- --         ---      ---        --- ---- ---- ----- -----------
1513522517079314356 1801.1   105.55 37       volker0001 6.268115 -75.543407 22  31   32   0     25

Un ejemplo de implementación para el ESP (compatible con arduino) que usamos en el branch: https://github.com/unloquer/AQA/tree/influxLivePost/firmware/src

La librería ESP8266HTTPClient permite conformar un POST, ej:

int post2Influx(String url, String load) {
  if (WiFi.status() != WL_CONNECTED) { return 0; }

  http.begin(url);
  http.setTimeout(HTTP_TIMEOUT);
  //http.addHeader("Content-Type", "text/csv");
  //http.addHeader("Content-Length", String(csv.length()));

  int httpCode = http.POST(load);
  if(httpCode > 0) {
    String payload = http.getString();
    Serial.println(payload);
    Serial.println("load  sent successfully!");
  } else {
    Serial.print("[HTTP] failed, error;;;: ");
    Serial.println(http.errorToString(httpCode).c_str());
}

Llamado a la función con la trama de datos

void loop() {
  gps = getGPSData();
  plantower = getPlantowerData();
  dht11 = getDHT11Data();

  if(plantower.ready) {
    plantowerData = plantower;
    if(plantowerData.ready) {
      ledParticulateQuality(plantowerData);
      //reportWifi( plantower.pm25);
      if(gps.ready) {
        save();
        String frame = influxFrame();
        Serial.println(frame);
        post2Influx("http://aqa.unloquer.org:8086/write?db=aqa", frame);
      }
    }
}

Construcción de la trama

String influxFrame() {
  /* CSV is ordered as:
   "id","lat","lng","date","time","altitude","course","speed","humidity",
   "temperature","pm1","pm25","pm10"
  */

  // First datum is the sensor_id
  String frame = SENSOR_ID + STR_COMMA + "id=" + SENSOR_ID +  STR_SPACE;

  // Add GPS data
  char strlat[25],strlng[25];
  dtostrf(gps.lat, 3, 6, strlat);
  dtostrf(gps.lng, 3, 6, strlng);
  frame += "lat=";
  frame += strlat + STR_COMMA;
  frame += "lng=";
  frame += strlng + STR_COMMA;
  //frame += gps.date + STR_COMMA;
  //frame += gps.time + STR_COMMA;
  frame += "altitude=";
  frame += gps.altitude + STR_COMMA;
  frame += "course=";
  frame += gps.course + STR_COMMA;
  frame += "speed=";
  frame += gps.speed + STR_COMMA;

  //Add DHT11 data
  //if
    frame += "humidity=";
    frame += dht11.humidity + STR_COMMA;
    frame += "temperature=";
    frame += dht11.temperature + STR_COMMA;
  // } else {
  //   frame += "humidity=" + STR_NULL + STR_COMMA + "temperature=" + STR_NULL + STR_COMMA;
  // }

  // Add Plantower data
    // if
    frame += "pm1=";
    frame += plantower.pm1 + STR_COMMA;
    frame += "pm25=";
    frame += plantower.pm25 + STR_COMMA;
    frame += "pm10=";
    frame += plantower.pm10;
  // } else {
  //   frame += "pm1=" + STR_NULL + STR_COMMA + "pm25=" + STR_NULL + STR_COMMA + "pm10=" + STR_NULL;
  // }

  return frame;
}

Ya logré hacer que el sistema de http://www.aire.valenciasanchez.com/ envíe las mediciones a la base de datos aqa de unloquer. Encontré algunos problemas de estabilidad en el sensor original luego de modificarlo para soportar esta integración por lo que decidí usar un segundo Arduino (Arduino MEGA 2560+Ethernet shield) para que leyera los datos de mi Base de Datos y los enviará a la base de datos de unloquer.

Incluyo el código completo abajo. Básicamente este programa hace uso de una librería del servicio Thingspeak para leer los datos y luego los formatea como se explica arriba para poder conectarse a InfluxDB y almacenar un dato cada 30 segundos.

He documentado el código, pero responderé cualquier duda o comentario.

/*
Leer de Thingspeak, postear en InfluxDB
Este programa lee el último dato posteado por http://aire.valenciasanchez.com en Thingspeak
y lo postea en el sistema de unlocker (http://aqa.unloquer.org:8888/sources/1/dashboards/3)
Creado por carlos@valenciasanchez.com con base en ejemplos:
-Thingspeak Library for Arduino.
-Sending data to InfluxDB using Arduino over HTTP: https://goo.gl/BZNEXV
*/

#include <Ethernet.h>
#include “ThingSpeak.h”

/** Iniciar el Ethernet client*/
EthernetClient client;

/** Dirección IP por defecto */
const IPAddress eth_default_ip( 10, 0, 0, 33);

/** MAC Address del ethernet shield */
byte eth_mac[] = { 0x90, 0xA2, 0xDA, 0x0E, 0xB9, 0xF5 };

/** Dirección IP del servidor de InfluxDB */
const byte eth_server[] = {45, 55, 140, 71};

/** InfluxDB HTTP port */
const int eth_port = 8086;

/** Tamaño del buffer para contenido HTTP */
const int bufferSize = 2048;

/** Un arreglo llenado con carácteres nulos. Será nuestro buffer */
char buf[bufferSize] = {’\0’};

/** Un string para lo que vamos a enviar y otras variables */
String payload;
String t;
String h;
String p;
int humic;
int temp;
int pm;

/*
Este es el canal de monitoreo de aire de Envigado
https://thingspeak.com/channels/270125. Monitorea temperatura, humedad y PM2.5
Hay otros campos que llevan un promedio móvil para filtrar variabilidad.

Temperatura está en el field 1
Humedad está en el field 2
Promedio móvil de PM 2.5 está en el field 4
*/

unsigned long weatherStationChannelNumber = 270125;
unsigned int temperatureFieldNumber = 1;
unsigned int humidityFieldNumber = 2;
unsigned int pm25FieldNumber = 4;

/**

  • Arrancar el ethernet shield como cliente
    */
    bool eth_start(){
    Ethernet.begin(eth_mac, eth_default_ip);
    delay(2000); //delay to allow connection to be done

    //ver si nos podemos conectar al servidor
    int conState = client.connect(eth_server, eth_port);

    if(conState > 0) {
    Serial.println(“Connected to InfluxDB server”);
    client.stop();
    return true;
    }

//mostrar el mensaje de error y return false
Serial.print(“Could not connect to InfluxDB Server, Error #”);
Serial.println(conState);
return false;
}

/**

  • Enviar datos HTTP a InfluxDB
    /
    void eth_send_data(char
    data, int dataSize) {
    //Conectarse al servidor de InfluxDB
    int conState = client.connect(eth_server, eth_port);

if(conState <= 0) { //comprobar si se logró conectar
Serial.print(“No se conectó al servidor de InfluxDB, Error #”);
Serial.println(conState);
return;
}

//Enviar el encabezado de HTTP y el buffer
client.println(“POST /write?db=aqa HTTP/1.1”);
client.println(“Host: aqa.unloquer.org”);
client.println(“User-Agent: Arduino/1.0”);
client.println(“Connection: close”);
client.println(“Content-Type: application/x-www-form-urlencoded”);
client.print("Content-Length: ");
client.println(dataSize);
client.println();
client.println(data);

delay(50); //esperar a que el servidor procese los datos

//Leer la respuesta del servidor y cerrar la sesión
Serial.println(“Respuesta de InfluxDB”);
while(client.available()) { //recibir un char
Serial.print((char)client.read());
}
Serial.println(); //línea en blanco

client.stop();
}

/**

  • Configurar conexiones
    */
    void setup() {
    //Interface serial para ver errores
    Serial.begin(115200);
    delay(1000);
    eth_start();
    ThingSpeak.begin(client);
    }

/**

  • Ciclo principal
    */
    void loop() {
    // Poner en blanco el mensaje a enviar
    String payload="";

// Leer los últimos valores del canal en Thingspeak
float temperatureInF = ThingSpeak.readFloatField(weatherStationChannelNumber, temperatureFieldNumber);
float humidityInF = ThingSpeak.readFloatField(weatherStationChannelNumber, humidityFieldNumber);
float pm25InF = ThingSpeak.readFloatField(weatherStationChannelNumber, pm25FieldNumber);

Serial.print(“Temperatura: “);
Serial.print(temperatureInF);
Serial.println(” Grados C”);
Serial.print(“Humedad: “);
Serial.print(humidityInF);
Serial.println(” %”);
Serial.print(“PM2.5: “);
Serial.print(pm25InF);
Serial.println(” ug/m3”);

//convertir datos de float a string y adicionarlos al mensaje
String h = String(humidityInF,2);
String t = String(temperatureInF,2);
String p = String(pm25InF,2);

//concatenar los datos al string con las constantes
payload=“valenciasanchez,id=valenciasanchez lat=6.169601,lng=-75.569200,altitude=1708.4,course=0,speed=0.00,humidity=”+h+",temperature="+t+",pm1=0,pm25="+p+",pm10=0";

// Convertir a char
// El “largo total” va con un espacio extra para el terminador null
int payload_len = payload.length() + 1;

// Preparar el arreglo
char char_array[payload_len];

//Copiarlo encima
payload.toCharArray(char_array, payload_len);

//ahora enviar los datos
//este es el número de caracteres a escribir en el buffer, regresa el valor de sprintf
int numChars = 0;
numChars = sprintf(buf,char_array);
//numChars = sprintf(buf,“valenciasanchez,id=valenciasanchez lat=6.169601,lng=-75.569200,altitude=1708.4,course=0,speed=0.00,humidity=37.00,temperature=25.00,pm1=0,pm25=31,pm10=0”);
//Mostrar el buffer para ver como se ve
Serial.print("Enviando lo siguiente a InfluxDB: ");
Serial.println(buf);
//enviar a InfluxDB
eth_send_data(buf, numChars);

//hay que vaciar el buffer para el siguiente envío
memset(buf, ‘\0’, bufferSize);
delay(30000); //Esperar el mismo período de tiempo de captura de datos. 30 segundos

}

1 me gusta

Carlos, ¡qué bien!
Ya vi la celda que agregaste en el dashboard y esta muy estable.

Veo que Thingspeak tiene API que puede entregar los datos, porque quizá se podría hacer un script para leer desde el API y mandar a influx con curl, algo como:

Tomar los datos de tu API (por ejemplo para consultar el valor de PM2.5): https://api.thingspeak.com/channels/270125/fields/3.json?results=2 luego construir la trama de datos, de pronto @brolin nos puede ayudar con este paso y finalmente los mandas a influxdb con:

curl -i -XPOST 'http://aqa.unloquer.org/write?db=<db>' --data-binary '<trama de datos>'

Lograrlo con un script podría liberarte el Arduino.

Veo que esto dejó de funcionar y quisiera rehabilitarlo. Algunas preguntas:

  1. Creo que lo cambió fue la dirección del servidor, puedo considerar que es solo eso? o se han hecho cambios recientes en la Base de Datos o versión del software que da considerar.

  2. AireEnvigado tiene ahora 5 sensores, 3 propios y dos de Sergio Restrepo. Puedo enviar datos de todos? Recuerden que inicialmente solo enviaba datos de PM2.5 de 1 sensor.

  3. Debería postear el ultimo dato medido o debería mejor postear el promedio móvil de 24 horas? Pregunto porque el segundo es el usado en todos los estándares, pero si se quiere un análisis detallado, el último dato podría ser más util.

Muchas Gracias.

Carlos Valencia.

Hola Carlos, gracias por el interés de seguir compartiendo los datos.

  1. Es preferible que lo hagas con el dominio en lugar de la ip así si vuelve a cambiar el servidor no se nos rompe el envío de datos. El dominio hay es http://aqa.unloquer.org:8086/write?db=aqa

  2. Claro que si puedes reportar los 5 sensores.

  3. Nosotros consideramos postear el último dato, luego si es necesario sacar los promedios se puede hacer

Esperamos sean útiles las respuestas.

Y gracias!

Gracias. Ya está funcionando. Son 5 sensores reportando datos de PM2.5, hay 3 que además reportan PM10. Proximamente estaré adicionando Humedad y Temperatura a 2 de ellos, en el momento solo uno reporta esas medidas.

Todos reportan correctamente las coordenadas de ubicación y la altitud.

Saludos.

1 me gusta

Ya se visualizan en el mapa también :grin: Entre todos vamos contribuyendo

http://daquina.io/aqaVizEstaciones/
imagen

Gracias!

Gracias a ustedes. Esa visualización está excelente, estuve bien el proyecto en https://github.com/daquina-io , es posible que lo use para algunos mapas de los sensores de Envigado?

Claro que lo puedes usar, hay mucho por mejorarle.

Gracias, pero tienes mas detalles de como instalarlo y configurarlo? Gracias.

Voy a tratar de detallarlo en el README del repositorio pero, suponiendo que usas git, clonas el repositorio, haces checkout al branch influxEstaciones, y suponiendo que usas R y Shiny arrancas la aplicación con runApp().

Al parecer los datos y los queries fueron eliminados recientemente. En el momento no hay datos de los sensores de AireEnvigado. Saben si cambiò algo?

Hola @CAVA

los datos pasados no han sido eliminados mira. Los sensores transmitieron hasta el día 12 de abril. Lo que si sucedió es que alguien borró los querys en el dashboard.

Yo revisaría si los sensores están transmitiendo y es curioso que todos los sensaros dejaron de transmitir al mismo tiempo.

Cualquier cosa en que podamos ayudar para restablecer los sensores nos decís

Gracias. Ya lo reestableci, estos datos son enviados por un Arduino MEGA y el viernes 12 se desconectó. Saludos.

Un favor, encuentro que la visualizacion en http://daquina.io/aqaVizEstaciones/ dejò de funcionar.

Corrijo, veo que fue migrado a http://daquina.io/aqaviz/

Y quedò muy bien!!