domingo, 13 de enero de 2019

Leer y escribir en una memoria SD

Después de mucho tiempo voy a dedicarle un post a las memorias SD. Las memorias SD nos van a permitir escribir y leer datos de forma no volátil y que perduren a través del tiempo sin necesidad de utilizar la memoria EEPROM. Esto tiene muchas ventajas como la gran capacidad de almacenamiento, el reemplazo rápido de la memoria, el uso de un sistema de archivos nos permite exportar e importar datos con una computadora, etc.

Para esto vamos a utilizar una librería que viene incluida con el software de arduino y algún módulo que nos facilite el cableado entre el microcontrolador y la tarjeta de memoria, en mi caso voy a utilizar alguna de las pantallas que tengo que ya vienen con lectores de memorias SD, aunque no vamos a sacar imágenes de la memoria para mostrar en la pantalla, si vamos a cargar una página web y a almacenar datos en la tarjeta.

Primero vamos a ver el ejemplo del almacenamiento de datos, utilizando el siguiente código:

#include <SPI.h>
#include <SD.h>

const int selectorChip = 4;

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

  Serial.println("inicializando tarjeta SD");

  //intentamos inicializar la memoria
  if(!SD.begin(selectorChip))
  {
    Serial.println("Fallo el inicio de la tarjeta.");
    //no hace nada más
    return;
  }
  Serial.println("Tarjeta iniciada.");
}

void loop()
{
  //genero los datos
  String datos = "milisegundos desde encendido: " + String(millis());
  //abro el archivo en modo escritura
  File archivo = SD.open("datos.txt", FILE_WRITE);
  //si se abrió correctamente guardo los datos.
  if(archivo)
  {
    archivo.println(datos);
    archivo.close();
  }
  else
  {
    Serial.println("error al intentar abrir el archivo datos.txt");
  }
}


Para el siguiente ejemplo vamos a utilizar un ESP8266 y el código ya utilizado en esta entrada


#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>

#include <SPI.h>
#include <SD.h>
File archivo;

ESP8266WebServer servidorweb(80);

const char* red = "red"/*ingrese aquí el nombre de id*/;
const char* contra = "contra"/*ingrese aquí la contraseña*/;
String HTMLpage = "";

void setup() {

  Serial.begin(115200);
  pinMode(2, OUTPUT);

  Serial.print("Inicializando tarjeta SD");

  if (!SD.begin(4)) {
    Serial.println("Fallo al incializar");
    return;
  }
  Serial.println("Tarjeta iniciada con exito");

  WiFi.begin(red, contra);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.print("Conectado a");
  Serial.println(red);
  Serial.print("Dirección IP:");
  Serial.println(WiFi.localIP());
  
  servidorweb.on("/", []()
  {
    String estado = servidorweb.arg("estado");
    if(estado == "ON")
    {
      digitalWrite(2, LOW);
    }
    else if(estado == "OFF")
    {
      digitalWrite(2, HIGH);
    }
    servidorweb.send(200, "text/html", paginaWeb());
  });

  servidorweb.begin();
}

void loop() {
  
  servidorweb.handleClient();
  
}

String paginaWeb()
{
  String pagina = "";
  archivo = SD.open("pagina.txt");
  if(archivo)
  {
    //leo el archivo mientras esté disponible
    while(archivo.available())
    {
      //leo cada byte del archivo mientras esté disponible
      pagina +=archivo.read();
    }

    //cierro el archivo
    archivo.close();
    return pagina;
  }
}


Si queremos cargar otras páginas lo único que tenemos que hacer es "llamar" distintos archivos y modificar la función para que recupere otras cosas.

Hay que aclarar que la librería "SD" para el ESP8266 no es la misma que usamos normalmente para los arduinos, por lo que hay que especificar la ruta de la librería de la siguiente forma:

#include "c:\SD.h"

También lo que se puede hacer es poner la librería en la misma carpeta que el programa y de ahí "llamarla" colocando:

#include "SD.h"

En lugar de:

#include <SD.h>

De esta forma lo que conseguimos es que el preprocesador busque dentro de la carpeta del programa en lugar de la carpeta en donde se guardan las librerías normalmente.

De todas formas al ser tantos archivos que comparten el mismo nombre lo mejor va a ser modificar lo lo que diga SD por SD-esp de los siguientes ítem:
  • La carpeta (SD) 
  • El archivo SD.h (SD/src/SD.h)
  • El archivo SD.cpp (SD/src/SD.cpp)
  • La línea 53 del archivo SD.cpp (SD/src/SD.cpp)
  • La línea 15 del archivo File.cpp (SD/src/File.cpp)
Entonces en lugar de utilizar "#include <SD.h>" usaremos "#include <SD-esp.h>" (hay que modificar eso del programa).

Otro punto que hay que aclarar es la extensión del archivo, dado que la librería trabaja (por lo que tengo entendido) con nombres de archivo del estilo 8.3, es decir que el nombre puede tener un máximo de 8 caracteres y la extensión de 3, es por eso que no podemos utilizar un archivo ".html", pero como al final de cuentas a página es sólo texto, podemos utilizar un archivo de texto normal.

domingo, 6 de enero de 2019

Arduino con JSON

Hoy les traigo una librería capaz de integrar Json a arduino, es decir, la librería se encarga de decodificar texto en variables y viceversa. Con esta librería, por ejemplo, vamos a ser capaces de adquirir datos desde la red y utilizarlos fácilmente en nuestro arduino (sea cual sea). La idea de esta librería es facilitar la adquisición de datos de la red desde nuestro arduino.

Json tiene la siguiente sintaxis:

{"nombreDeLaVariable":"valor"}

Para más de un dato va a figurar de la siguiente forma:

{"nombre1":"valor1", "nombre2":"valor2", "nombre3":"valor3"}

Y un arreglo de datos se vería de la siguiente manera:

{"nombreGeneral":[{"nombre1":"valor1", "nombre2":"valor2"}, {"nombre1":"valor3", "nombre2":"valor4"}, {"nombre1":"valor5", "nombre2":"valor6"}]}

Aunque por lo general el Json de arriba se declara de la siguiente forma:

{
   "nombreGeneral":[
      {"nombre1":"valor1", "nombre2":"valor2"},
      {"nombre1":"valor3", "nombre2":"valor4"},
      {"nombre1":"valor5", "nombre2":"valor6"}
   ]
}

Esto es para que sea más legible dentro del programa. Por lo general le asignamos estas cadenas a alguna variable, quedando de la forma:

variable = {
   "nombreGeneral":[
      {"nombre1":"valor1", "nombre2":"valor2"},
      {"nombre1":"valor3", "nombre2":"valor4"},
      {"nombre1":"valor5", "nombre2":"valor6"}
   ]
}

Por lo tanto si queremos acceder a "valor6" utilizaríamos la siguiente cadena:

variable.nombreGeneral[2].nombre2

En donde vamos "accediendo" a los distintos niveles del objeto hasta llegar a la variable deseada. Esto puede parecer un poco confuso para el ejemplo que dí, por lo que acá les dejo otro, en donde vamos a englobar los vegetales y los vamos a separar entre frutas y verduras, y a cada vegetal le vamos a dar un nombre y un sabor:

vegetales = {
   "verduras":[
      {"nombre":"lechuga", "sabor":"semi amargo"},
      {"nombre":"zanahoria", "sabor":"semi dulce"},
      {"nombre":"radicheta", "sabor":"amargo"}
   ],
   "frutas":[
      {"nombre":"limón", "sabor":"ácido"},
      {"nombre":"mandarina", "sabor":"dulce"},
      {"nombre":"pomelo", "sabor":"amargo"}
   ]
}

Entonces si queremos obtener el sabor de la mandarina siempre vamos a comenzar escribiendo el nombre de nuestra variable, que en este caso es "vegetales", luego vamos por el siguiente identificador, que es una fruta, y sabemos que la mandarina se encuentra en la posición 1 (porque se empieza contando desde el 0), entonces nos dirigimos a esa posición, y por último vamos al sabor, entonces nos quedaría de la forma: "vegetales.frutas[1].sabor".
La estructura que yo definí no es única, por lo que no hay límites de identificadores que coloquemos, a las verduras podríamos agregarle el color o la textura, mientras que a las frutas podríamos ponerle la cantidad de azúcar en gramos o la forma.
El Json es muy versátil y tiene muchas aplicaciones más sobre todo en javaScript, dónde podemos poner hasta funciones como valor.

Yendo a arduino, acá les dejo un código que utiliza los vegetales de arriba y muestra todos los datos por puerto serie.


#include <ArduinoJson.h>

void setup() {
  // Inicialiazmos el puerto serie
  Serial.begin(9600);
  while(!Serial){}

  /*
   * Acá le asignamos una memoria para que la librería
   * trabaje.
   * 
   * Dentro de los símbolos de mayor y menos le vamos
   * a pasar la cantidad de bytes que le queremos asig-
   * nar, dependiendo de la longitud de los datos con
   * los que vamos a trabajar tendremos que asignar más
   * o menos memora
   */
  StaticJsonBuffer<600> jsonBuffer;

  char json[] = 
  
  "{" 
    "\"verduras\":["
      "{\"nombre\":\"lechuga\",\"sabor\":\"semi amargo\"},"
      "{\"nombre\":\"zanahoria\",\"sabor\":\"semi dulce\"},"
      "{\"nombre\":\"radicheta\",\"sabor\":\"amargo\"}"
    "],"
    "\"frutas\":["
      "{\"nombre\":\"limon\",\"sabor\":\"acido\"},"
      "{\"nombre\":\"mandarina\",\"sabor\":\"dulce\"},"
      "{\"nombre\":\"pomelo\",\"sabor\":\"amargo\"}"
    "]"
  "}";

  //Creamos el objeto bajo el nombre vegetales
  JsonObject& vegetales = jsonBuffer.parseObject(json);
  
  //Verificamos que todo se generó de forma correcta
  if (!vegetales.success()) {
    Serial.println("fallo: parseObject()");
    return;
  }


  /* Mostramos los valores.
   * 
   * Como dato, antes de trabajar con los datos hay
   * que pasarlos a una variable del tipo "const char*"
   * para que funcione correctamente.
   */

  Serial.println("");
  for(int i = 0; i < 3; i++)
  {
    const char* nombre = vegetales["verduras"][i]["nombre"];
    const char* sabor = vegetales["verduras"][i]["sabor"];
    Serial.print("verdura: ");
    Serial.print(nombre);
    Serial.print(", sabor: ");
    Serial.println(sabor);
  }
  for(int i = 0; i < 3; i++)
  {
    const char* nombre = vegetales["frutas"][i]["nombre"];
    const char* sabor = vegetales["frutas"][i]["sabor"];
    Serial.print("fruta: ");
    Serial.print(nombre);
    Serial.print(", sabor: ");
    Serial.println(sabor);
  }
}

void loop() {
  //no utilizado
}


Si necesitamos saber cuánta memoria asignarle al buffer de los datos Json, está el  sitio arduinojson.org/v5/assistant en donde podremos ingresar el texto que se va a utilizar como Json y nos da el tamaño a asignar. Además, más abajo, nos va a dar una porción de código que ya nos genera una variable por cada elemento del texto Json.

Además esta librería tiene un generador de Json que con lo que expliqué arriba y el ejemplo de la librería debería bastar para que se entienda.



domingo, 30 de diciembre de 2018

Servidor web con ESP8266

Hoy les traigo la continuación del servidor simple, la idea es poder interactuar con el micro a través de una interfaz web. En este ejemplo vamos a encender y apagar el led del ESP8266 de forma remota, también se pueden agregar hojas de estilo y me imagino que también se puede agregar código en javaScript (después de todo lo que hay que hacer es mandar texto al navegador para que lo interprete), el único cuidado que hay que tener es escribir el html, el css y lo que necesiten uno a continuación del otro.

Para recuperar variables de la página web hay que hacerlo de la siguiente forma:

  servidorweb.on("/", []()
  {
    String estado = servidorweb.arg("estado");
    if(estado == "ON")
    {
      digitalWrite(2, LOW);
    }
    else if(estado == "OFF")
    {
      digitalWrite(2, HIGH);
    }
    servidorweb.send(200, "text/html", HTMLpage);
  });

con el ".arg()" recuperamos el valor de la variable que pongamos entre paréntesis, luego podremos trabajar en función de eso.

El código total quedaría de la siguente forma:


#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>

ESP8266WebServer servidorweb(80);

const char* red = "red"/*ingrese aquí el nombre de id*/;
const char* contra = "contra"/*ingrese aquí la contraseña*/;
String HTMLpage =

  "<!DOCTYPEhtml>"
  "<html>"
    "<head>"
      "<title>"
        "Tutorial de servidor web"
      "</title>"
    "</head>"
    "<h3>"
      "Esto es un servidor web (de ejemplo)<br>"
      " con el micro ESP8266 para controlar<br>"
      " el led integrado en la placa."
    "</h3>"
    "<form action=\"/\" method=\"post\">"
      "<td><button name=\"estado\" type=\"submit\" class = \"button\" value=\"ON\">ON</button></td>"
      "<td><button name=\"estado\" type=\"submit\" class = \"button\" value=\"OFF\">OFF</button></td>"
    "</form>"
  "<html>";

void setup() {

  Serial.begin(115200);
  pinMode(2, OUTPUT);

  WiFi.begin(red, contra);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.print("Conectado a");
  Serial.println(red);
  Serial.print("Dirección IP:");
  Serial.println(WiFi.localIP());
  
  servidorweb.on("/", []()
  {
    String estado = servidorweb.arg("estado");
    if(estado == "ON")
    {
      digitalWrite(2, LOW);
    }
    else if(estado == "OFF")
    {
      digitalWrite(2, HIGH);
    }
    servidorweb.send(200, "text/html", HTMLpage);
  });

  servidorweb.begin();
}

void loop() {
  
  servidorweb.handleClient();
  
}

Recordemos que la página va a estar montada sobre la dirección ip 192.168.4.1, por lo que hay que ingresar ahí para ver la página. Si bien la página y el código son muy sencillos, como mencioné antes, se puede agregar javaScript y css para que la página sea más interesante, pero hay que tener en cuenta que si queremos agregar librerías o frameworks de JS la memoria del microcontrolador va a empezar a escasear. En teoría se podría agregar una memoria externa (ej. una microSD) para almacenar la página web con todas las librerías y demás. A continuación les dejo cómo luce la página que acabamos de hacer.


Básicamente cuando presionamos los botones se "manda" al argumento "estado" el valor "ON" u "OFF" el cuál el servidor va a procesar para prender o apagar el led. Como los estados del ESP8266 están negados el estado low va a encender el led y el high lo va a apagar.

domingo, 23 de diciembre de 2018

Módulo GPS

Hoy les traigo el módulo gps neo6m.
Primero hablemos sobre lo que es el gps, es un sistema de posicionamiento global (uno de varios), el cual está compuesto por una costelación de satélites que emiten señales con ciertos datos, como por ejemplo la hora. Luego estos datos se procesan, conociendo la identifiación de cada satélite, la hora, la intensidad de la señal recibida y algunas cosas más, se puede conocer la posición del dispositivo que recibe estas señales; esto se logra con trigonometría, por lo que para conocer la posición exacta de algo como mínimo se van a necesitar tres satélites, y cuantos más haya el sistema va a ser más preciso.
El gps utiliza longitud y latitud y divide a la tierra en 4 partes, cada una de 90 grados, si obvservamos un mapa:



podremos ver que estas divisiones ya existen hace tiempo, por lo que agregando unos cuantos ceros detrás de la coma obtendremos distancias muy precisas, cada grado corresponde a 111,1 km (es periódico ese valor, es decir que tiene infinitos unos detrás de la coma) y por ejemplo un metro son 0,000009 grados, cien metros son 0,0009 grados y así sucesivamente. Cuántos más ceros haya detrás de la coma, más precisas van a ser las mediciones.
Para comenzar a utilizar el módulo, primero vamos a utilizar un programa que nos permita utilizar un arduino como un puente serie entre el dispositivo y la computadora (básicamente generamos un puerto serie en dos pines del arduino, en el cuál vamos a conectar el gps, y luego conectamos el arduino a la computadora, no hay que conectar el módulo de gps a los pines 0 y 1), lo que nos va a mandar el gps va a ser algo similar a lo siguiente:

$GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68
225446 Time of fix 22:54:46 UTC
A Navigation receiver warning (A = OK, V = warning)
4916.45,N Latitude 49 deg. 16.45 min North = 49°16'45''
12311.12,W Longitude 123 deg. 11.12 min West = 123°11'12''
000.5 Speed over ground, Knots
054.7 Course Made Good, True
191194 Date of fix 19 November 1994
020.3,E Magnetic variation 20.3 deg East
*68 mandatory checksum

Como hay un montón de datos, lo más fácil es utilizar una librería para interpretarlos, puesto que ya alguien se encargó de depurar esos datos y fue lo suficientemente gentil para compartirlo con el mundo vamos a utilizarla, se llama tinyGPS, vamos a utilizar uno de los tres ejemplos:

#include <SoftwareSerial.h>
#include <TinyGPS.h>

/* 
   This sample code demonstrates the normal use of a TinyGPS object.
   It requires the use of SoftwareSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx).
*/

TinyGPS gps;
SoftwareSerial ss(4, 3);

void setup()
{
  Serial.begin(115200);
  ss.begin(4800);
  
  Serial.print("Simple TinyGPS library v. "); Serial.println(TinyGPS::library_version());
  Serial.println("by Mikal Hart");
  Serial.println();
}

void loop()
{
  bool newData = false;
  unsigned long chars;
  unsigned short sentences, failed;

  // For one second we parse GPS data and report some key values
  for (unsigned long start = millis(); millis() - start < 1000;)
  {
    while (ss.available())
    {
      char c = ss.read();
      // Serial.write(c); // uncomment this line if you want to see the GPS data flowing
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }
  }

  if (newData)
  {
    float flat, flon;
    unsigned long age;
    gps.f_get_position(&flat, &flon, &age);
    Serial.print("LAT=");
    Serial.print(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flat, 6);
    Serial.print(" LON=");
    Serial.print(flon == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flon, 6);
    Serial.print(" SAT=");
    Serial.print(gps.satellites() == TinyGPS::GPS_INVALID_SATELLITES ? 0 : gps.satellites());
    Serial.print(" PREC=");
    Serial.print(gps.hdop() == TinyGPS::GPS_INVALID_HDOP ? 0 : gps.hdop());
  }
 
  gps.stats(&chars, &sentences, &failed);
  Serial.print(" CHARS=");
  Serial.print(chars);
  Serial.print(" SENTENCES=");
  Serial.print(sentences);
  Serial.print(" CSUM ERR=");
  Serial.println(failed);
  if (chars == 0)
  {
    Serial.println("** No characters received from GPS: check wiring **");
  }
}

Este ejemplo nos entrega los datos separados, por lo que podríamos implementarlo en cualquier proyecto que necesite gps.
Hay otra librería llamada tinyGPS plus, del mismo creador, que en teoría nos ofrece más cosas, como por ejemplo usar el glonass (el análogo ruso del gps).

domingo, 16 de diciembre de 2018

ESP8266 y ILI9341

Hoy les traigo cómo utilizar el display gráfico (y a color!) basado en el driver ILI9341, el display gráfico en cuestión es uno de 2,2" y cuenta con un lector de memorias SD (que de momento no vamos a utilizar). Se maneja bajo el protocolo de comunicación SPI. La diferencia más notoria entre este display y el SPFD5408 es la velocidad con que se maneja, si bien el SPFD5408, al igual que el ILI9341, admite el uso de SPI como protocolo de comunicación lo que pude ver es que la velocidad con la que se refresca la pantalla es muchísimo más alta. Otra clara ventaja es que usa menos pines (aunque no probé el SPI de la SPFD5408, en su configuración "base" de hardware la cantidad de pines es menor), aunque no todo son ventajas, el ILI9341 no cuenta con una una entrada táctil, por lo que debemos desarrollar algo para que poder interactuar con el micro de ser necesario.


Como vamos a utilizar el ESP8266 para usar con este módulo vamos a necesitar una librería especial para este micro. El mapa de los pines utilizados es el siguiente:

DISPLAY        NodeMCU

SDO/MISO      D6 (no conecta si no se "lee" el display)
LED                 VIN (or 5V, see below)
SCK                 D5
SDI/MOSI       D7
DC (RS/AO)   D3
RESET            D4 (o RST, depende de la configuración)
CS                   D8 (o RST, depende de la configuración)
GND               GND (0V)
VCC               5V or 3.3V

Como es muy sencillo utilizar este display con los códigos de ejemplo que vienen con la librería debería bastar para que puedan entender la base. De todas formas les comento que esta librería posee un archivo que sirve para realizar algunas configuraciones, como por ejemplo:

*qué driver tiene la pantalla (en nuestro caso es la ILI9341)
*configuraciones especiales para ciertos drivers
*modificar los pines que se utilizan para la comunicación (los pines que se pueden evitar usar)
*qué fuentes se utilizan
*la frecuencia del SPI

Hay bastantes configuraciones disponibles con esta librería para adaptarla al proyecto que deseemos (más que nada para la optimización de la memoria del microcontrolador, y velocidad de la pantalla).

Algunos de los comandos útiles para el uso de este display son los siguientes:

  • drawPixel(x, y, color): este comando dibuja un pixel en la posición X e Y (que son del tipo UINT) y de color especificado en esa variable (también UINT, el color va a ser de 16 bits, 5 rojos, 6 verdes y 5 azules, utiliza la codificación RGB565 y se pueden generar con este programita)
  • fillScreen(color): útil para llenar toda la pantalla con un color.
  • drawLine(x0, y0, x1, y1, color): dibuja una línea entre los puntos (x0, y0) y (x1, y1) del color especificado.
  • draw/fillRect(x, y, medidaX, medidaY, color): dibuja un rectángulo con punto de inicio en (x, y) y de medidas de medidaX y medidaY, si es draw lo dibuja sin fondo, y si es fill es con fondo, el color hace referencia al color de lo que dibuja.
  • draw/fillRect(x, y, r, color): dibuja un círculo con centro en (x, y) de radio r y con el color pasa lo mismos que con el punto anterior.
  • drawString(texto, x, y, fuente): escribe el texto con puntos iniciales en (x, y) y con una fuente que como un valor genérico podría ser el 2.

Hay una lista muy larga de comandos que habría que explicar uno a uno, pero con los explicados arriba debería bastar para entender la dinámica básica del funcionamiento de la librería.

domingo, 9 de diciembre de 2018

Alarma de gas y humo con gsm

Antes que nada, no me hago cargo de cómo se use la información de este post, es meramente educativa.

Hoy les traigo un código que tengo hace bastante (lamentablemente no tengo fotos del sensor y demás), funciona con un módulo detector de gas y humo (de la serie mq-xxx), el módulo gsm800 y un arduino nano. El código es sencillo, cuando se lee que la cantidad de gas en el aire supera cierto valor, el micro activa una alarma visual y sonora, además de desconectar un pin (esto es porque la idea era usarlo con una electroválvula controlada con un relé, por lo que el pin que se desconecta debería ir al relé) y luego de un tiempo (10 segundos) si se sigue detectando gas o humo se envía un mensaje de texto al número programado.
Este equipo está diseñado para bajos caudales de gas (de humo no porque no existe riesgo de que explote e inutilice el equipo), por ejemplo una hornalla o en el caso más extremos una estufa.

Obviamente este equipo no está destinado a armarse porque tiene muchísimas fallas, desde un principio no tiene ninguna redundancia, por lo que si falla algo no hay una segunda instancia verificando.

A continuación les dejo el código:


#include <SoftwareSerial.h>

SoftwareSerial mySerial(10, 11); // RX, TX
long tiempo;

int pin_led_verm = 3; //led indicador de electrovalvula de gas conectada
int pin_buzzer = 2;   //sonido y señalización luminosa indicadora de alarma
int pin_d0 = 4;
int pin_a0 = A7;
int nivel_sensor = 100;

void setup ()
{
  pinMode(pin_d0, INPUT);
  pinMode(pin_a0, INPUT);
  pinMode(pin_led_verm, OUTPUT);
  pinMode(pin_buzzer, OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  Serial.begin(9600);

//------------------------------------------------------//  

  mySerial.begin(9600);
  delay(20000);
  Serial.println("encendido");
  comando ("at");
  delay(20000);
  comando ("ATI+CMEE=2");         //pone el modo de los errores en texto
  comando ("at");                 //
  comando ("ati");                //obtiene nombre del módulo y versión
  comando ("at+ccid");            //obtiene el número de la tarjeta sim
  comando ("at+cbc");             //obtiene el estado de la batería
  comando ("at+csq");             //obtiene la fuerza de la señal
  comando ("at+cops?");           //obtiene la conección de la red
  mySerial.print("AT+CMGF=1\r");  //modo texto
  Serial.println("fin del informe");

}

void loop ()
{
  int valor_digital = digitalRead(pin_d0);
  int valor_analogico = analogRead(pin_a0);
  Serial.print("valor digital: ");
  Serial.print(valor_digital);
  Serial.print(" valor analogico: ");
  Serial.println(valor_analogico);
  if(valor_analogico > nivel_sensor)
  {
    digitalWrite(pin_led_verm, LOW);  //corta electroválvula de gas
    digitalWrite(pin_buzzer, HIGH);   //suena alarma visual y audible

    /*
     * verifico que haya durante 10 segundos gas antes de enviar
     * el mensaje.
     */
    for(int i = 0; i < 11; i++)
    {
      digitalWrite(13, LOW);
      if(i == 10 && analogRead(pin_a0) > nivel_sensor)
      {
        digitalWrite(13, HIGH);
        Serial.print("mandando mensaje");
        mensaje ();
        Serial.print("mensaje enviado");
      }
      delay(800);
    }
  }
  else
  {
    digitalWrite(pin_led_verm, HIGH); //válvula conectada
    digitalWrite(pin_buzzer, LOW); //Sonido y led silenciado
  }
}

void mensaje ()
{
  /*
   * si se quieren agregar varios números hay que
   * hacerlo en la variable numero y separarlos por
   * comas.
   */
  char* numero[]={"54911*******"};
  
  Serial.println("envio de sms");

  delay(200);

  for(int i = 0; i < sizeof(numero); i++)
  {
    Serial.println(i);
    delay(200);
    mySerial.print ("AT+CMGS=\"+");
    mySerial.print (numero[i]);
    mySerial.println ("\"");
    delay(200);
    mySerial.write('\r');
    delay(200);
    mySerial.print("alarma de gas o humo");
    delay(300);
    mySerial.write(0x1A);
    delay(7000);
  }
  Serial.println("fin del informe");
}

void comando (char com[40])
{
  mySerial.write(com);
  mySerial.write('\r');
  delay(200);
  while (mySerial.available())
  {
    Serial.write(mySerial.read());
  }
}