Tag Archives: Data Logger

ESP8266 – Serial WiFi Logger

14 Abr 18
Jose Nunez
, , ,
No Comments

En este proyecto corto estudiaremos el uso del módulo genérico ESP8266 como mecanismo inalámbrico para el registro de datos por WiFi en un servidor. A su vez estaremos estudiando el uso del puerto serial del ESP8266 a fin de que otro controlador pueda enviarle datos al ESP y este enviarlos de forma inalámbrica al servidor.

Este proyecto utilizará el servidor “ZUMMOBOT” que describimos en el artículo sobre Captura Masiva de Datos

El itinerario de este proyecto es el siguiente:

#1 – Conexiones y BLINK Serial
En este apartado vamos a conectar el ESP8266 a la laptop para programarlo e instalar un programa muy sencillo que solamente nos enviará datos de vuelta “ON” y “OFF” por el puerto serial a través del monitor serial de IDE ARDUINO. Esto nos permitirá entender si estamos conectándonos correctamente con el ESP8266 y si está funcionando adecuadamente.

#2 – Lector de datos (líneas) por puerto serial (tx/rx)
En esta sección estaremos programando un sketch capaz de recibir lineas de datos por el puerto serial las cuales serán reportadas de vuelta por el Monitor Serial del IDE Arduino.

#3 – Lector de comandos y datos por puerto serial (tx/rx)
En este punto usaremos un sketch que puede determinar si los datos recibidos por puerto serial corresponden a un comando que deba ejecutar el ESP8266 o a datos que deban ser transmitidos.

#4 – Envío de datos via WiFi (conexion a enrutador, y comunicación con servidor en red de área local (LAN))
Finalmente estudiaremos el sketch completo que haga las siguientes funciones:

  • Recibe datos por puerto serial.
  • Discrimina si se trata de comandos o datos a transmitir.
  • Cuando se trata de comandos, los ejecuta. Dentro de los comandos podemos mencionar SET_AP:ssid,pwd y SET_BaseURL:http://host:port/resource/
  • Cuando se trata de datos, los envía al servidor por via WiFi. En esta primera entrega solamente vamos elaborar un poco sobre la lógica; mientras que en la siguiente entrega detallaremos la comunicación WiFi.

#1 – Conexiones y Blink Serial

Se utiliza un adaptador USB Serial para PC. Las conexiones con el ESP8266 se resumen de esta manera:

ESP8266  <–> Adaptador USB/Serial para PC

VCC   <-----------  VCC (3.3V)
RST   <----
CH_PD <---R3.3K---  VCC (3.3V) (*)
TX    ------------> RX
---
RX    <-----------  TX
GPIO0 ----SW----->  GND (**)
GPIO1 -----
GND   ----------->  GND

(*) CH_PD está conectado a VCC(3.3V) por medio de un resistore de 3.3KOhm (Pull up). Existe literatura que indica una conexión directa.
(**) GPIO0 se conecta a tierra para poder programar el módulo usando el IDE de ARDUINO. En nuestro caso usamos un switch para cambiar esta configuración facilmente. Cuando GPIO0 está conectado a tierra, al encender el ESP8266 este NO EJECUTA el sketch sino que entra en modo de programación.

Una vez realizada la conexión, podemos utilizar el siguiente programa para comprobar que podemos programar el módulo y ejecutar un sketch.

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the UNO, MEGA and ZERO 
  it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN is set to
  the correct LED pin independent of which board is used.
  If you want to know what pin the on-board LED is connected to on your Arduino model, check
  the Technical Specs of your board  at https://www.arduino.cc/en/Main/Products
  
  This example code is in the public domain.

  modified 13 April 2018 by Jose Nunez
  
  modified 8 May 2014
  by Scott Fitzgerald
  
  modified 2 Sep 2016
  by Arturo Guadalupi
  
  modified 8 Sep 2016
  by Colby Newman
*/


// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.println("Setup Complete");
}

// the loop function runs over and over again forever
void loop() {
  Serial.println("ON");
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  Serial.println("OFF");
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

 


#2 – Lector de datos (líneas) por puerto serial (tx/rx)

El siguiente programa leerá una linea de datos a la vez y la mostrará en el monitor serial del IDE de ARDUINO. Nótese que el programa está hecho para buscar los caracteres \r\n (retorno de carro, nueva línea) que es la forma en que se finalizan las líneas enviadas por el comando Serial.println() de ARDUINO.

#define DEBUG_MODE true

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("SETUP COMPLETE!");
}

void loop() {
  String serialData = readSerial();
  if (isNotEmpty(serialData)) {
    logMessageln("Data Found: |" + serialData + "|");
  }
}

boolean isNotEmpty(String data) {
  return data != "";
}
String readSerial() {
  String serialData = "";
  if (Serial.available()) {
    //THIS IS INTENDED TO READ LINES WRITTEN BY ANOTHER ARDUINO USING Serial.println() function
    //WHICH WILL END COMMUNICAITON WITH \r\n 
    //https://www.arduino.cc/en/Serial/Println
    
    serialData = Serial.readStringUntil('\r\n');
  }

  return serialData;
}

void logMessageln(String message) {
  if (DEBUG_MODE) {
    String timestamp = String(millis());
    Serial.println(timestamp + "-" + message);
  }
}

 


#3 – Lector de comandos y datos por puerto serial (tx/rx)

Este sketch es muy similar al anterior pero además de leer el puerto serial, nos permite diferenciar entre lineas de comando (que comienzan con CMD:) y lineas de datos (que no comienzan con CMD:)

/**
 * 
 *  THIS SKETCH WILL READ LINES FROM SERIAL PORT. THEN DETERMINE IF THEY ARE A COMMAND (BEGINS WITH CMD) 
 *  OR IF THEY ARE JUST TEXT (DONT BEGIN WITH CMD)
 *  
 *  THIS IS INTENDED TO READ LINES WRITTEN BY ANOTHER ARDUINO USING Serial.println() function
 *  WHICH WILL END COMMUNICAITON WITH \r\n 
 *  https://www.arduino.cc/en/Serial/Println
 *  
**/
 
#define DEBUG_MODE true // SET TO false to avoid verbose messages

/*
 * THE SETUP
 */
void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("SETUP COMPLETE!");
}

/**
 * THE LOOP
 */
void loop() {
  String serialData = readSerial();
  if (isNotEmpty(serialData)) {
    if(isCommand(serialData)){
      String commandData = extractCommand(serialData);
      logMessageln("Command Found: |" + commandData + "|");
    } else{
      logMessageln("Data Found: |" + serialData + "|");
    }
  }
}

boolean isNotEmpty(String data) {
  return data != "";
}

boolean isCommand (String data) {
  return data.startsWith("CMD:");
}

String extractCommand (String data){
  if (isCommand(data)){
    return data.substring(4);
  } else {
    return "";
  }
}
String readSerial() {
  String serialData = "";
  if (Serial.available()){
    serialData = Serial.readStringUntil('\r\n');
  }

  return serialData;
}

void logMessageln(String message) {
  if (DEBUG_MODE) {
    String timestamp = String(millis());
    Serial.println(timestamp + "-" + message);
  }
}

 


#4 – Envío de datos via WiFi (conexion a enrutador, y comunicación con servidor en red de área local (LAN))

Ya para finalizar, les compartimos el script que hace posible conectarse a cualquier red y cualquier servidor con capacidad de respuesta HTTP de su red de area local (LAN).

Para una mayor comprensión del código vamos a mostrar dos listados. Este primer listado solo muestra la lógica de ejecución sin entrar en detalles de la comunicación WiFi.

/**
 * 
 *  THIS SKETCH WILL READ LINES FROM SERIAL PORT. THEN DETERMINE IF THEY ARE A COMMAND (SET_AP: or SET_BaseURL:) 
 *  OR IF THEY ARE JUST TEXT (DONT BEGIN WITH EITHER SET_AP: nor SET_BaseURL:)
 *  
 *  THIS IS INTENDED TO READ LINES WRITTEN BY ANOTHER ARDUINO USING Serial.println() function
 *  WHICH WILL END COMMUNICAITON WITH \r\n 
 *  https://www.arduino.cc/en/Serial/Println
 *  
**/
 
#define DEBUG_MODE true // SET TO false to avoid verbose messages
String APName = "";
String APPWD  = "";
String BaseURL = "";
/*
 * THE SETUP
 */
void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("SETUP COMPLETE!");
}

/**
 * THE LOOP
 */
void loop() {
  String serialData = readSerial();
  if (isNotEmpty(serialData)) {
    if(isCommand(serialData)){
      executeCommand(serialData);
    } else{
      sendDataToWiFi(serialData);
    }
  }
}

boolean isNotEmpty(String data) {
  return data != "";
}

boolean isCommand (String data) {
  return data.startsWith("SET_AP:")
  ||     data.startsWith("SET_BaseURL:");
}

String executeCommand (String data){
  if(data.startsWith("SET_AP:")){
    logMessageln("SET_AP command found!");
    int commaLocation = data.indexOf(",");
    APName = data.substring(7,commaLocation);
    APPWD  = data.substring(commaLocation+1);
    logMessageln("APName set to: " + APName);
    logMessageln("APPWD  set to: " + APPWD);
  } else if (data.startsWith("SET_BaseURL:")) {
    logMessageln("SET_BaseURL command found!");
    BaseURL = data.substring(12);
    logMessageln("BaseURL set to: " + BaseURL);
  }
}

String readSerial() {
  String serialData = "";
  if (Serial.available()){
    serialData = Serial.readStringUntil('\r\n');
  }

  return serialData;
}

void sendDataToWiFi(String data){
  if(APName=="") {
    logMessageln("AP Not Defined! use SET_AP:ssid,pwd command to set it up");
    return;
  }

  if(BaseURL ==""){
    logMessageln("BaseURL Not Defined! use SET_BaseURL:http://host:port/route/resource command to set it up");
    return;    
  }
  
  logMessageln("Data to be sent via WiFi...");
  logMessageln("Network: " + APName + "|" + APPWD);
  String urlToCall = BaseURL + "?" + data;
  logMessageln("Request: GET " + urlToCall); 
}

void logMessageln(String message) {
  if (DEBUG_MODE) {
    String timestamp = String(millis());
    Serial.println(timestamp + "-" + message);
  }
}

Esto muestra un poco los pasos lógicos para nuestro receptor de datos via puerto serial. El siguiente listado muestra todos los detalles de la comunicación WiFi.

Utilizamos las bibliotecas ESP8266WiFi y ESP8266HTTPClient para facilitar la generación de solicitudes HTTP de tipo GET.

Durante el SETUP del sketch nos conectamos a una red WIFI determinada por las variables globales APName y APPWD.

Luego durante el proceso de LOOP, recibimos datos en la forma de “querystrings”; es decir, llave_1=valor_1&llave_2=valor_2&llave_n-valor_n

Usando estos datos se construye un URL que será enviado al servidor usando la variable global BaseURL y el método HTTP GET.

(!) La lógica referente a cambiar de red WiFi usando el comando SET_AP:ssid,pwd no funciona todavía; pues nos falta explorar un poco la forma adecuada de cambiar de red WiFi.

/**

THIS SKETCH WILL READ LINES FROM SERIAL PORT. THEN DETERMINE IF THEY ARE A COMMAND (SET_AP: or SET_BaseURL:)
OR IF THEY ARE JUST TEXT (DONT BEGIN WITH EITHER SET_AP: nor SET_BaseURL:)

THIS IS INTENDED TO READ LINES WRITTEN BY ANOTHER ARDUINO USING Serial.println() function
WHICH WILL END COMMUNICAITON WITH \r\n
https://www.arduino.cc/en/Serial/Println

**/

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

#define DEBUG_MODE true // SET TO false to avoid verbose messages

String VERSION = "20180415-1644";
String APName = "wifi ssid goes here";
String APPWD = "wifi password goes here";
String BaseURL = "http://192.168.0.3/zummobotdb/set.php";

/**********************************************************/
void setup() {
   Serial.begin(115200);
   delay(1000);
   logMessageln("HI! WELCOME TO serial_wifi_logger.ino " + VERSION);
   for (int i = 5; i > 0; i--) {
      logMessageln("Countdown: " + String(i));
      delay(1000);
   }
   logMessageln("SETUP!");
   logMessageln("WELCOME TO serial_wifi_logger.ino " + VERSION);
   logMessageln("Default APName|PWD:" + APName + "|" + APPWD );

   connectToWiFiAP(APName, APPWD);
   logMessageln("SETUP COMPLETE!");
}

/**********************************************************/
String connectToWiFiAP(String ssid, String pwd) {
   logMessageln("ConnectToWiFiAP(" + ssid + "," + pwd + ")");
   char _ssid[30];
   char _pwd[30];

   logMessageln("Converting Strings to Char Arrays...");
   ssid.toCharArray(_ssid, sizeof(_ssid));
   pwd.toCharArray(_pwd, sizeof(_pwd));

   logMessageln("WIFIMODE: STA");
   WiFi.mode(WIFI_OFF);
   WiFi.mode(WIFI_STA);

   logMessageln("WiFi.begin('" + String(_ssid) + "','" + String(_pwd) + "')");
   WiFi.begin(_ssid, _pwd);

   logMessageln("Conecting to WiFi AP...");
   while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
   }

   logMessageln("Connected!");
}

/**********************************************************/
void loop() {
   String serialData = readSerial();
   if (isNotEmpty(serialData)) {
      if (isCommand(serialData)) {
         executeCommand(serialData);
      } else {
         sendDataToWiFi(serialData);
      }
   }
}

boolean isNotEmpty(String data) {
   return data != "";
}

boolean isCommand (String data) {
   return data.startsWith("SET_AP:")
   || data.startsWith("SET_BaseURL:");
}

String executeCommand (String data) {
   if (data.startsWith("SET_AP:")) {
      logMessageln("SET_AP command found!");
      int commaLocation = data.indexOf(",");
      APName = data.substring(7, commaLocation);
      APPWD = data.substring(commaLocation + 1);
      logMessageln("APName set to: " + APName);
      logMessageln("APPWD set to: " + APPWD);

      connectToWiFiAP(APName, APPWD);
   } else if (data.startsWith("SET_BaseURL:")) {
      logMessageln("SET_BaseURL command found!");
      BaseURL = data.substring(12);
      logMessageln("BaseURL set to: " + BaseURL);
   }
}

String readSerial() {
   String serialData = "";
   if (Serial.available()) {
      serialData = Serial.readStringUntil('\r\n');
   }

   serialData.trim(); //THIS IS IMPORTANT TO AVOID ISSUES WITH ENDING /r/n
   return serialData;
}

void sendDataToWiFi(String data) {
   logMessageln("sendDataToWiFi('" + data + "'); (Length: " + String(data.length()) + ")");
   if (BaseURL == "") {
      logMessageln("BaseURL Not Defined! use SET_BaseURL:http://host:port/route/resource command to set it up");
      return;
   }

   HTTPClient http;
   String urlToCall = BaseURL + "?" + data;
   logMessageln("URL to call: " + urlToCall);

   logMessageln("http.begin(" + urlToCall + ")");
   http.begin(urlToCall); //HTTP

   logMessageln("Request: GET " + urlToCall);
   int httpCode = http.GET();
   logMessageln("http.GET() executed!" );

   // httpCode will be negative on error
   logMessageln("HTTPCode: " + String(httpCode));
   if (httpCode > 0) {
      // HTTP header has been send and Server response header has been handled
      // file found at server //200 098
      if (httpCode == HTTP_CODE_OK) {
         String payload = http.getString();
         logMessageln("RESPONSE PAYLOAD: ");
         logMessageln(payload);
         logMessageln("------------------");
      } else {
         logMessageln("HTTP Code is not " + String(HTTP_CODE_OK));
      }
   } else {
      logMessageln("[HTTP] GET... failed, error: " + http.errorToString(httpCode));
   }

   http.end();
}

void logMessageln(String message) {
   if (DEBUG_MODE) {
      String timestamp = String(millis());
      Serial.println(timestamp + "-" + message);
   }
}