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


#1

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;
}

Posible unión con proyecto Bogotá (ESP32+Honeywell+Android)
Sensor PMS1003 con pantalla
#2

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

}


Sensor PMS1003 con pantalla
#3

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.