Cuando Espressif lanzó al mercado los primeros módulos wifi con el integrado ESP8266 y el firmware con el que manejarlo usando órdenes AT, lo que nos interesaba a los usuarios era integrarlo en montajes con microcontroladores y los problemas se reducían a conocer la (antes) oscura tabla de órdenes AT del ESP8266, las necesidades de alimentación o la actualización del firmware del ESP8266.
Después llegaron rápidamente alternativas para programar el ESP8266 e implementaciones en módulos wifi de formatos muy diferentes que planteaban otras inquietudes: qué módulo wifi ESP8266 elegir en función del alcance de las distintas antenas (incluyendo las externas) o la integración física en nuestros montajes de estos nuevos módulos.
Seguramente por atender a todos esos cambios puede que no se haya insistido en lo más elemental, la gestión más básica del módulo wifi ESP8266. Aunque en polaridad.es se puede encontrar información sobre el uso del ESP8266 y hay algunas aplicaciones con vocación de explicar de manera genérica el funcionamiento del módulo wifi ESP8266 usando órdenes AT, especialmente en el artículo sobre la librería para hacer consultas HTTP desde Arduino con el módulo wifi ESP8266, las impresiones de los lectores permiten pensar que sería útil añadir algo más de información de base que ayude a los usuarios del ESP8266 a realizar sus propias implementaciones.
Tratar las operaciones básicas para trabajar con el ESP8266 y proponer soluciones genéricas es un objetivo varias partes muy diferentes; para ayudar a seguir el contenido del artículo, el siguiente índice puede servir de guía:
- Controlar el módulo wifi ESP8266 desde el ordenador por el puerto serie
- Actualizar el firmware con esptool
- Enviar órdenes al módulo
- Recibir datos desde el ESP8266
- Analizar la respuesta buscando textos en el contenido
- Limitar el tiempo de espera de recepción de la respuesta
- Ejecutar una operación compleja definida por varias órdenes AT
Controlar el módulo wifi ESP8266 desde el ordenador por el puerto serie
Desde una placa Arduino y utilizando su IDE es posible monitorizar el funcionamiento de un módulo wifi ESP8266, enviar las órdenes AT del ESP8266 y ver la respuesta pero resulta mucho más cómodo hacerlo desde un ordenador con una aplicación de tipo terminal.
Dependiendo de qué placa Arduino se utilice, puede que solamente haya disponible un puerto serie hardware, lo que añade una pequeña incomodidad al envío y la recepción. Cambiar la velocidad de las comunicaciones es mucho más cómodo en una aplicación de comunicaciones serie desde un ordenador y algunas placas Arduino (y en algunas circunstancias) no soportan bien las velocidades más altas de las comunicaciones serie, especialmente 115200 baudios, que es la velocidad por defecto de las últimas versiones del firmware.
Sobre qué programa utilizar para monitorizar el ESP8266 utilizando el puerto serie, hay muchos para elegir según necesidades y preferencias; últimamente estoy utilizando más el clásico CuteCom (el de la captura de pantalla de arriba) porque me resulta muy cómodo para repetir ciertas órdenes AT del módulo wifi ESP8266 en las pruebas de los proyectos.
Ya se han dado aquí algunas recomendaciones sobre programas que funcionen como consola serie; por ejemplo, al hablar de PuTTY para el control de dispositivos serie UART desde el ordenador. PuTTY, además de ser una aplicación excelente, está disponible para la mayoría de sistemas operativos de escritorio. Además, como PuTTY puede utilizarse para hacer de consola tanto con el puerto serie como con los familia de protocolos de Internet (TCP/IP), incluyendo los que funcionan sobre TLS, se convierte en herramienta habitual que devuelve con creces el (poco) tiempo empleado en configurarla y en acostumbrarse a su uso.
Además del software de comunicaciones serie, para conectar el módulo wifi ESP8266 al puerto USB de un ordenador también hace falta un conversor USB a serie TTL. Igual que en el caso del software, existen varias versiones, desde las que solo se utilizan para convertir el puerto USB en un puerto serie TTL (que se puede conseguir desde un Euro) hasta los que pueden emular diferentes protocolos (como SPI o I2C).
Igual que un programa que funcione como una consola serie, el hardware para comunicar el ordenador por USB con un circuito lógico (no solo el ESP8266) será una herramienta habitual en el trabajo de un desarrollador de aplicaciones microcontroladas, merece la pena tenerlo en la caja de herramientas cuanto antes y trabajar con el módulo wifi ESP8266 es una excelente ocasión para hacerse con uno.
El conversor USB a UART TTL también se puede utilizar para monitorizar el comportamiento de un circuito que use el ESP8266, para hacerlo, se conectan en serie a la entrada de datos (RX) del conversor las salidas que se quieran monitorizar con un diodo rápido (el 1N4148, por ejemplo) y una resistencia (2K2, por ejemplo) en paralelo entre ellos. Un montaje de este tipo funciona como un sniffer serie hardware.
Aunque el sniffer de la imagen de arriba es ciertamente rudimentario (entre otras cosas no dispone de buffer) es suficiente para monitorizar el funcionamiento de un montaje con Arduino y el ESP8266.
Quitando del esquema anterior el sniffer queda el esquema que muestra cómo conectar un módulo wifi ESP8266 a una placa Arduino. Además de alimentarlo a 3V3, hay que conectar a nivel alto el pin de reset y el de activación del integrado (enable). Por supuesto, el pin RX de uno debe conectar al TX del otro.
Para simplificar el diagrama anterior, se ha representado una placa Arduino alimentada a 3V3 y de la que se supone una tensión en el puerto serie también de 3V3. Si se utiliza un microcontrolador con un nivel de señal en el puerto serie diferente (normalmente 5 V) será necesario, para no dañar el ESP8266, utilizar un conversor de nivel como los de los esquemas de abajo. Este circuito es el que se encuentra con frecuencia en muchas implementaciones de módulos comerciales listos para usar.
Actualizar el firmware del ESP8266
Las órdenes AT del ESP8266, su terminación, la velocidad por defecto del módulo… dependen de la versión del firmware del ESP8266. Lo más recomendable es asegurarse de disponer la misma versión en todos los módulos y, de ser posible, que sea la última versión.
Desafortunadamente, la mayoría de los modelos de módulos wifi ESP8266 solo disponen de 4 Mbit, por lo que no se les puede instalar la versión más reciente. La última versión (oficial) de firmware que puede instalarse en módulos wifi ESP8266 con 4 Mbit (la mayoría) es la 0.9.4 que incluye la versión 0.2 de las órdenes AT del ESP8266.
En resumen, para actualizar el firmware es necesario:
-
Descargar la versión correspondiente del firmware. La última versión (oficial) para un módulo que disponga de 4 Mbit de memoria, se encuentra en la carpeta de Espressif de github. En la web de Espressif puede descargarse la versión más reciente del firmware, pero es muy importante verificar que el módulo sobre el que se instala tiene suficiente memoria.
-
Descargar la última versión de la herramienta de instalación del firmware. Mi favorita es esptool que está escrita en Python, por lo que funciona en cualquier plataforma. Además de descargarse, también se puede instalar con
pip install esptool
(opip2
opython -m pip
…). Por supuesto, Espressif también ofrece su propia herramienta pero actualmente solo está disponible para Windows. -
Preparar los archivos descargados; descomprimirlos en una carpeta accesible y, si procede, hacer ejecutable la herramienta esptool, en mi caso, desde GNU/Linux, con
chmod +x esptool
-
Conectar el módulo al ordenador usando un conversor USB UART TTL que trabaje a 3V3 o utilizar un conversor de nivel si funciona a 5 V. Además de la alimentación, habrá que conectar TX a RX del conversor USB UART TTL, RX a TX, GPIO0 a nivel bajo (GND) y puede que GPIO2 a nivel alto (en mis pruebas ha funcionado tanto conectándolo a nivel algo como desconectado). Si el módulo tiene libre la conexión GPIO15 (como ocurre en el ESP-12) debe conectarse a nivel bajo. RESET, que normalmente estaría a nivel alto durante el funcionamiento, puede dejarse sin conectar o conectar a nivel alto por medio de una resistencia (10K, por ejemplo), ya que antes de empezar la grabación puede ser necesario reiniciar el dispositivo conectándolo a nivel bajo.
Al alimentar el módulo ya estará disponible para actualizarlo pero, si se muestra un error de conexión, será necesario resetearlo conectando RESET a nivel bajo un instante y dejándolo luego al aire (sin conectar) para el proceso de actualización.
El módulo tiene picos de consumo de medio amperio (hasta 600 mA, según algunos usuarios) así que es importante utilizar una fuente de alimentación capaz de soportar este consumo, especialmente para actualizar el firmware. -
Ejecutar la herramienta para actualizar el firmware. En mi caso, he grabado en la misma carpeta la herramienta y los documentos del firmware en el paso 3, así que ejecuto desde la consola:
cd ~/Datos/firmwareESP8266
(cambiar a la carpeta que contiene la herramienta y el firmware)./esptool.py --baud 115200 --port /dev/ttyUSB0 write_flash \
0x00000 ./boot_v1.1.bin \
0x01000 ./user1.bin \
0x7C000 ./esp_init_data_default.bin \
0x7E000 ./blank.bin
--baud
establece la velocidad del ESP8266 (115200 baudios en mi caso) y--port
el puerto serie al que se conecta (en mi caso, emulado, el primer USB). Los diferentes documentos que forman el firmware van detrás dewrite_flash
precedidos de la dirección, siendo el documento user1.bin el que contiene la carga útil de la actualización.
Enviar órdenes al módulo wifi ESP8266
Para controlar el ESP8266 desde un ordenador habrá que empezar por configurar la aplicación para lo que bastará con ① elegir el puerto al que se conecte el conversor USB UART TTL, algo como /dev/USB0
en GNU/Linux y similares o algo como COM6
en Windows, ② elegir la velocidad a la que esté configurado el ESP8266, seguramente 115200 baudios, ③ establecer 8 bits de datos más uno de parada, sin paridad ni handshake, y ④ configurar el fin de línea, dependiendo del firmware, casi siempre CR+LF.
Una vez configurada la aplicación (o, en su caso, almacenada y seleccionada) queda abrir la conexión («open device» y «open», respectivamente, en las capturas de pantalla de los ejemplos arriba con CuteCom y PuTTY) y ya se puede empezar a enviar órdenes al ESP8266.
Como puede verse en la tabla de órdenes AT del ESP8266, el formato para activar, desactivar, establecer un valor y consultarlo es bastante predecible pero, en general, no es fácil recordarlos todos y es probable que sea necesario tenerla a mano para consultarla.
La forma de enviar órdenes AT al módulo wifi ESP8266 desde Arduino es muy sencilla: ① configurar las comunicaciones con Serial.begin(115200);
(o Serial1, Serial2… en las placas con varios puertos serie hardware) y ② enviar las órdenes usando el formato Serial.print(orden+"\r\n");
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#define PUERTO_SERIE Serial // Objeto serie que corresponde a puerto serie hardware al que está conectado el módulo wifi ESP8266 #define VELOCIDAD_ESP8266 115200 // Velocidad, en baudios, a la que está configurado el ESP8266 #define IDENTIFICADOR_WIFI "polaridad.es" // SSID (Service Set Identifier) #define CLAVE_WIFI "54lLij1RiTn3MEd3v41C" // Clave del punto de acceso wifi al que se conecta el ESP8266 void setup() { PUERTO_SERIE.begin(VELOCIDAD_ESP8266); PUERTO_SERIE.print ( "AT+CWJAP=\""+ String(IDENTIFICADOR_WIFI)+ "\",\""+ String(CLAVE_WIFI)+ "\"\r\n" ); } void loop() { } |
El ejemplo anterior muestra cómo enviar las órdenes AT del módulo wifi ESP8266 desde Arduino. En este caso se ilustra AT+CWJAP
, que sirve para conectar a un punto de acceso. Esta orden utiliza como argumentos el identificador del punto de acceso (SSID) y la clave, ambos entre comillas, por lo que se convierten en un objeto Srtring
y se encierran entre comillas usando el código de escape (\"
). Para terminar la orden se usa \r\n
que corresponde con CR
y LF
.
Para recordar que el puerto serie no siempre se identifica con Serial
(en determinadas placas puede ser Serial1
, Serial2
…) se ha definido el objeto del puerto usado asignándolo a la macro PUERTO_SERIE
. Detectando el tipo de placa usada podría añadirse un poco de inteligencia a la selección del puerto serie; más adelante se repasará cómo puede averiguar el tipo de Arduino. El resto de definiciones son las habituales que permiten «dar nombre» a los valores constantes para evitar repetirlos (y equivocarse) y hacen más sencillo cambiarlos.
El ejemplo anterior se supone que conecta el módulo wifi ESP8266 al punto de acceso indicado pero ¿Estaba ya conectado anteriormente? ¿Ha funcionado la conexión? Para saberlo, necesitamos «escuchar» lo que dice el ESP8266
Recibir datos desde el módulo wifi ESP8266
Conectando al ordenador el sniffer de datos explicado más arriba se puede ver lo que Arduino ha enviado al ESP8266 y la respuesta del mismo. Para leer desde Arduino y procesar en él la información será necesario detectar con Serial.available()
si ha llegado algún dato y en tal caso cargarlo con Serial.read()
. En el siguiente ejemplo se muestra cómo leer la respuesta de AT+CWJAP?
, que informará si hay una conexión con algún punto de acceso.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#define PUERTO_ESP8266 Serial // Objeto serie que corresponde a puerto serie hardware al que está conectado el módulo wifi ESP8266 #define VELOCIDAD_ESP8266 115200 // Velocidad, en baudios, a la que está configurado el ESP8266 char letra_recibida; void setup() { PUERTO_ESP8266.begin(VELOCIDAD_ESP8266); PUERTO_ESP8266.print("AT+CWJAP?\r\n"); } void loop() { while(PUERTO_ESP8266.available()) { letra_recibida=PUERTO_ESP8266.read(); } } |
Como en una placa Arduino Uno (y en otras) abrir el monitor serie resetea el programa, se puede aprovechar para ver en la consola serie de Arduino la información que envía al ESP8266 como muestra la captura de pantalla de la imagen de abajo.
Analizar la respuesta enviada por el módulo wifi ESP8266
Ya se ha visto cómo leer la información que llega a Arduino desde el ESP8266. El problema con el que hay que tratar es que no se sabe cuándo empezará a llegar, cuánto tardará en llegar, qué longitud tendrá… y no es muy eficiente estar esperando a que la respuesta del ESP8266 se reciba sin dejar que el microcontrolador realice otras tareas mientras tanto.
Una forma sencilla de gestionar esta circunstancia es iterar sobre los datos recibidos buscando respuestas concretas con las que, por ejemplo, activar indicadores (banderas o variables booleanas) que determinarán si se sigue buscando en el texto recibido y qué acciones deben realizarse en función de la información que llega desde el ESP8266. Mientras llega la respuesta el microcontrolador puede dedicarse a otras tareas, por ejemplo, recibir datos de sensores y procesarlos.
Buscar un texto en la información recibida desde el ESP8266
Para ir buscando en el texto que llega desde el ESP8266 se puede ir comparando cada letra recibida con la que corresponda al mensaje que se busca. Será necesario usar un contador (o un puntero) que señale a la letra que toque comparar; si el carácter que llega desde el ESP8266 es igual al que se está examinando en el mensaje, el contador avanza, si es diferente se inicializa.
Para saber que se ha llegado al final se consulta el siguiente carácter del mensaje buscado, que será cero (\0
) o se almacena la longitud del mensaje para, comparándola con el contador, saber si se ha terminado de comparar y por tanto el módulo wifi ESP8266 ha enviado el mensaje buscado.
En el siguiente ejemplo se utiliza la orden AT+CWLAP
que devolverá una lista de los puntos de acceso y dentro de ellos se busca uno llamado «wifi polaridad.es». Aunque se ha optado por verificar que el último carácter sea cero, como el buffer solamente almacena el texto buscado y se conoce su longitud, se podría también comprobar si se han recibido tal número de letras correctas. Con un LED conectado al pin 2 se informa de que se ha encontrado el texto que se esperaba.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define ORDEN "AT+CWLAP\r\n" // Buscar los puntos de acceso wifi disponibles. (Dependiendo de la versión del firmware) Las órdenes terminan en CR+LF #define MENSAJE_BUSCADO "wifi polaridad.es" // Ver si está disponible el punto de acceso llamado "wifi polaridad.es" #define LONGITUD_MENSAJE 18 // Se necesita guardar, al menos, 17 letras y el terminador \0 #define PIN_LED_ENCONTRADO 2 // Pin al que se conecta el LED que informa de que se ha encontrado el texto (el punto de acceso buscado está disponible) #include <string.h> // strncpy boolean esperando=true; char buffer_mensaje; char mensaje[LONGITUD_MENSAJE]; byte posicion_mensaje=0; void setup() { pinMode(PIN_LED_ENCONTRADO,OUTPUT); digitalWrite(PIN_LED_ENCONTRADO,LOW); // Apagar el LED (por ahora no se ha encontrado el mensaje) strncpy(mensaje,MENSAJE_BUSCADO,LONGITUD_MENSAJE); // sizeof(mensaje) SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 SERIE.print(ORDEN); // Enviar la orden (consultar los puntos de acceso disponibles) al módulo wifi ESP8266 } void loop() { if(esperando) // Buscará infinitamente hasta que llegue el texto esperado (y si no existe el punto de acceso nunca llegará ¡Es un ejemplo!) { while(SERIE.available()) // Si ha llegado algún dato por el puerto serie… { buffer_mensaje=SERIE.read(); // …almacenarlo en el buffer if(buffer_mensaje==mensaje[posicion_mensaje]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { posicion_mensaje++; // Pasar a la siguiente letra del mensaje if(mensaje[posicion_mensaje]==0) // ¿Ha terminado de analizarse todo el mensaje? (la última letra de la cadena de texto es \0) { esperando=false; // Si se ha terminado de analizar con éxito todo el mensaje ya no se está esperando digitalWrite(PIN_LED_ENCONTRADO,HIGH); // Encender el LED para indicar que se ha encontrado el texto buscado } } else // Si la letra que ha llegado por el puerto serie no corresponde con la buscada del mensaje… { posicion_mensaje=0; // …empezar desde la primera letra del texto buscado } } } } |
En el código del ejemplo anterior también puede verse una forma de elegir el puerto serie dependiendo del tipo de placa Arduino utilizada. En este ejemplo se supone que se dispone de tres tipos de placas para el proyecto: una Arduino Uno, una Arduino Mega 2560 y una Arduino Leonardo. Si se trabaja con una Arduino Uno se usará Serial
y en caso contrario Serial1
.
Si se trabaja con una placa Arduino Leonardo se puede usar el mismo método para detener el programa y esperar a que la consola (el puerto serie asociado a Serial
) esté disponible.
1 2 3 4 5 6 7 8 9 10 11 |
#ifdef ARDUINO_AVR_LEONARDO #define SERIE Serial1 #define ESPERA_CONSOLA while(!Serial){} /* Esperar a la consola */ #else #define ESPERA_CONSOLA /* Si no es un Arduino Leonardo no hace falta esperar a la consola */ #ifdef ARDUINO_AVR_MEGA2560 #define SERIE Serial1 #else // En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí #define SERIE Serial #endif #endif |
Buscar varios textos en la respuesta del ESP8266
El código del ejemplo anterior sirve para buscar un texto en la información enviada por el ESP8266 pero si la respuesta puede incluir diferente información dependiendo del funcionamiento. Supongamos, para empezar por un caso sencillo en el próximo ejemplo, que el texto enviado por el MCU ESP8266 es OK
cuando la operación se realiza correctamente y ERROR
en caso contrario, como ocurre con la orden AT+CWJAP?
, que sirve para verificar si el módulo wifi ESP8266 ya está conectado a un punto de acceso.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define ORDEN "AT+CWJAP?\r\n" // Verificar que está conectado a un punto de acceso #define CANTIDAD_MENSAJES 2 // Se distingue entre dos mensajes: acierto y error que se identificarán con true y false #define MENSAJE_ACIERTO "OK" #define MENSAJE_ERROR "ERROR" #define LONGITUD_MENSAJE 6 // Se necesita guardar, al menos, 5 letras y el terminador \0 (Un pequeño desperdicio al hacer una matriz en la que todos los elementos ocupan como el mayor. Es admisible porque son muy pocos elementos y así no se complica este ejemplo inicial) #define PIN_LED_ACIERTO 2 // Pin al que se conecta el LED que informa del acierto #define PIN_LED_ERROR 3 // Pin al que se conecta el LED que informa del error #include <string.h> // strncpy boolean esperando=true; // Todavía no se ha encontrado el final del mensaje char buffer_mensaje; // Para almacenar la última letra cargada desde el ESP8266 byte led_estado[CANTIDAD_MENSAJES]; // Un LED (pin) para cada estado char mensaje[CANTIDAD_MENSAJES][LONGITUD_MENSAJE]; // Mensajes de acierto y error byte posicion_mensaje[CANTIDAD_MENSAJES]; // Un contador de posición para cada mensaje void setup() { led_estado[true]=PIN_LED_ACIERTO; led_estado[false]=PIN_LED_ERROR; for(byte numero_mensaje=0;numero_mensaje<CANTIDAD_MENSAJES;numero_mensaje++) { pinMode(led_estado[numero_mensaje],OUTPUT); // Establecer el pin del LED digitalWrite(led_estado[numero_mensaje],LOW); // Apagar el LED posicion_mensaje[numero_mensaje]=0; // Inicializar a cero el número de letra a analizar de cada mensaje } strncpy(mensaje[true],MENSAJE_ACIERTO,LONGITUD_MENSAJE); // Preparar el mensaje de acierto strncpy(mensaje[false],MENSAJE_ERROR,LONGITUD_MENSAJE); // Preparar el mensaje de error SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 SERIE.print(ORDEN); // Enviar la orden (verificar si existe conexión a un punto de acceso) al módulo wifi ESP8266 } void loop() { if(esperando) // Buscará infinitamente hasta que llegue el texto esperado (y si no existe el punto de acceso nunca llegará ¡Es un ejemplo!) { while(SERIE.available()) // Si ha llegado algún dato por el puerto serie… { buffer_mensaje=SERIE.read(); // …almacenarlo en el buffer for(byte numero_mensaje=0;numero_mensaje<CANTIDAD_MENSAJES;numero_mensaje++) { if(buffer_mensaje==mensaje[numero_mensaje][posicion_mensaje[numero_mensaje]]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { posicion_mensaje[numero_mensaje]++; // Pasar a la siguiente letra del mensaje if(mensaje[numero_mensaje][posicion_mensaje[numero_mensaje]]==0) // ¿Ha terminado de analizarse todo el mensaje actual? (la última letra de la cadena de texto es \0) { esperando=false; // Si se ha encontrado algún mensaje y ya no se está esperando digitalWrite(led_estado[numero_mensaje],HIGH); // Encender el LED correspondiente al mensaje encontrado } } else // Si la letra que ha llegado por el puerto serie no corresponde con la buscada del mensaje… { posicion_mensaje[numero_mensaje]=0; // …empezar desde la primera letra del texto buscado } } } } } |
Esta nueva implementación del mismo método, que busca la coincidencia con varios posibles mensajes, permite elegir entre diferentes acciones en función de la respuesta recibida desde el ESP8266, simplemente encender el LED que corresponda.
Limitar el tiempo de espera de recepción de una respuesta
Hasta ahora no se ha hecho referencia a una cuestión relevante: el tiempo de espera máximo (timeout) antes de dar por fracasada una operación. Si por cualquier circunstancia se pierde la conexión con el módulo wifi ESP8266, el módulo con el punto de acceso, el punto de acceso con Internet o, por ejemplo, un hipotético servidor no está disponible, el programa puede quedar bloqueado en un punto esperando indefinidamente, por lo que habrá que articular una respuesta a tales circunstancias. El tiempo de espera máximo podrá configurarse para toda la aplicación, normalmente será más «generoso» en ese caso, o se pueden programar tiempos de espera individuales para cada operación.
Para comprobar que ha pasado (al menos) cierto intervalo de tiempo se suele restar de la «hora» actual la «hora» del momento en el que inicia la cuenta y se verifica que la diferencia sea mayor que el límite buscado. Esta «hora» no tiene que ser el tiempo real, habitualmente corresponde con el intervalo que ha transcurrido desde que el MCU empieza a contar el tiempo; esto no afecta al programa ya que lo que interesa es tiempo transcurrido y no el absoluto.
Habitualmente, para comprobar si ha transcurrido cierto intervalo, se utiliza la expresión del tipo:
1 |
(unsigned long)(millis()-milisegundos_al_empezar)>intervalo_de_tiempo |
La variable milisegundos_al_empezar
contiene el valor de millis()
de cierto momento de la ejecución desde el que se cronometra, por lo que no es raro que su nombre haga referencia a la palabra «cronómetro». La variable intervalo_de_tiempo
contiene la cantidad de milisegundos máxima que hace cierta la expresión anterior, es decir, que representa el timeout; es habitual que sea una constante (o una macro) y, como en el caso anterior, es frecuente que en su nombre aparezca la palabra «TIMEOUT». Si se trabaja con intervalos muy cortos se puede utilizar micros()
en lugar de millis()
(microsegundos en lugar de milisegundos) aunque es mucho menos habitual y mucho menos preciso.
1 |
(unsigned long)(millis()-cronometro)>TIMEOUT |
Un entero largo en Arduino (unsigned long
) ocupa 4 bytes (32 bits), así que el valor mayor que puede representar es 4294967295 (2 elevado a 32 menos uno, por empezar en cero). En una placa Arduino que esté funcionando continuamente el contador de milisegundos se reiniciará (volverá a ser cero) cada 50 días aproximadamente. Al restar con tipos de datos sin signo se reproduce el mismo comportamiento (dar la vuelta al contador) por lo que es viable controlar indefinidamente el timeout.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
#if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define ORDEN "AT+CWJAP?\r\n" // Verificar que está conectado a un punto de acceso #define CANTIDAD_MENSAJES 2 // Se distingue entre dos mensajes: acierto y error que se identificarán con true y false #define MENSAJE_ACIERTO "OK" #define MENSAJE_ERROR "ERROR" #define LONGITUD_MENSAJE 6 // Se necesita guardar, al menos, 5 letras y el terminador \0 (Un pequeño desperdicio al hacer una matriz en la que todos los elementos ocupan como el mayor. Es admisible porque son muy pocos elementos y así no se complica este ejemplo inicial) #define PIN_LED_ACIERTO 2 // Pin al que se conecta el LED que informa del acierto #define PIN_LED_ERROR 3 // Pin al que se conecta el LED que informa del error #define TIMEOUT 5000 // Espera 5 segundos la respuesta del ESP8266 (y su análisis) antes de desistir #include <string.h> // strncpy boolean esperando=true; // Todavía no se ha encontrado el final del mensaje boolean encontrado=false; // Salvo que se encuentre el punto de acceso se considera que la operación ha fracasado char buffer_mensaje; // Para almacenar la última letra cargada desde el ESP8266 byte led_estado[CANTIDAD_MENSAJES]; // Un LED (pin) para cada estado char mensaje[CANTIDAD_MENSAJES][LONGITUD_MENSAJE]; // Mensajes de acierto y error byte posicion_mensaje[CANTIDAD_MENSAJES]; // Un contador de posición para cada mensaje unsigned long cronometro; void setup() { led_estado[true]=PIN_LED_ACIERTO; led_estado[false]=PIN_LED_ERROR; for(byte numero_mensaje=0;numero_mensaje<CANTIDAD_MENSAJES;numero_mensaje++) { pinMode(led_estado[numero_mensaje],OUTPUT); // Establecer el pin del LED digitalWrite(led_estado[numero_mensaje],LOW); // Apagar el LED posicion_mensaje[numero_mensaje]=0; // Inicializar a cero el número de letra a analizar de cada mensaje } strncpy(mensaje[true],MENSAJE_ACIERTO,LONGITUD_MENSAJE); // Preparar el mensaje de acierto strncpy(mensaje[false],MENSAJE_ERROR,LONGITUD_MENSAJE); // Preparar el mensaje de error SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 SERIE.print(ORDEN); // Enviar la orden (verificar si existe conexión a un punto de acceso) al módulo wifi ESP8266 cronometro=millis(); } void loop() { if(esperando) // Buscará infinitamente hasta que llegue el texto esperado (y si no existe el punto de acceso nunca llegará ¡Es un ejemplo!) { while(SERIE.available()) // Si ha llegado algún dato por el puerto serie… { buffer_mensaje=SERIE.read(); // …almacenarlo en el buffer for(byte numero_mensaje=0;numero_mensaje<CANTIDAD_MENSAJES;numero_mensaje++) { if(buffer_mensaje==mensaje[numero_mensaje][posicion_mensaje[numero_mensaje]]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { posicion_mensaje[numero_mensaje]++; // Pasar a la siguiente letra del mensaje if(mensaje[numero_mensaje][posicion_mensaje[numero_mensaje]]==0) // ¿Ha terminado de analizarse todo el mensaje actual? (la última letra de la cadena de texto es \0) { encontrado=numero_mensaje; // (numero_mensaje!=0) Hay conexión con el punto de acceso esperando=false; // Si se ha encontrado algún mensaje y ya no se está esperando digitalWrite(led_estado[numero_mensaje],HIGH); // Encender el LED correspondiente al mensaje encontrado } } else // Si la letra que ha llegado por el puerto serie no corresponde con la buscada del mensaje… { posicion_mensaje[numero_mensaje]=0; // …empezar desde la primera letra del texto buscado } } } if((unsigned long)(millis()-cronometro)>TIMEOUT&&!encontrado) // Se ha superado el tiempo de espera y no hay conexión (se ha verificado que no hay o no ha llegado respuesta) { digitalWrite(PIN_LED_ERROR,HIGH); // Si ha superado el tiempo de espera encender el LED de error esperando=false; } } } |
El código anterior muestra una implementación muy básica de la limitación del tiempo de espera incorporando las líneas marcadas con respecto de ejemplo que le precede. Como la verificación del timeout se realiza después de procesar los datos que llegan desde el módulo wifi ESP8266, puede darse por buena la operación aunque la recepción tarde más que el tiempo de espera impuesto.
Ejecutar una operación compleja definida por varias órdenes AT
Para tener una referencia de ejemplo de la finalidad de la aplicación que explota el módulo wifi ESP8266, supongamos que se trata de almacenar información en una base de datos a la que se accede por medio de un servicio web para mantener un registro de la temperatura. El siguiente código lee un sensor conectado a una entrada analógica cada cierto intervalo de tiempo, calcula el valor medio y, transcurrido un intervalo de tiempo mayor, lo envía al servidor web (al estilo IoT) por medio de una petición HTTP (POST, GET…).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#define PIN_TEMPERATURA A0 // Pin analógico al que se conecta la salida del sensor de temperatura LM35 #define INTERVALO_LECTURA_TEMPERATURA 30000 // Leer la temperatura cada 30 segundos (30*1000) #define INTERVALO_GRABACION_TEMPERATURA 300000 // Grabar la media de la temperatura cada 5 minutos (5*60*1000) unsigned long muestras=0; // Número de veces que se ha medido la temperatura (para calcular la media) float temperatura; // de -55.0 °C a +150 °C | de -550 mV a +1500 mV | de 0 V a 2.050 V | analogRead*5.0/1023.0*100-55.0 -> analogRead/2.46-55.0 float media_temperaturas=0.0; unsigned long cronometro_lectura_temperatura; unsigned long cronometro_grabacion_temperatura; void setup() { Serial.begin(9600); cronometro_lectura_temperatura=millis(); cronometro_grabacion_temperatura=millis(); } void loop() { if((unsigned long)(millis()-cronometro_lectura_temperatura)>INTERVALO_LECTURA_TEMPERATURA) { cronometro_lectura_temperatura=millis(); temperatura=analogRead(PIN_TEMPERATURA)/2.46-55.0; muestras++; media_temperaturas=(float)temperatura/(float)muestras+media_temperaturas*(float)(muestras-1)/(float)(muestras); } if((unsigned long)(millis()-cronometro_grabacion_temperatura)>INTERVALO_GRABACION_TEMPERATURA) { cronometro_grabacion_temperatura=millis(); // Aquí iría la parte del código que graba la temperatura. Para verificar el funcionamiento, en este ejemplo simplemente se muestra en la consola Serial.println("\nTemperatura media "+String(temperatura,DEC)+" °C ("+String((float)millis()/1000.0,DEC)+" s)\n"); } } |
En este ejemplo de grabación de temperaturas, se accede a un servidor web cada cinco minutos. Aunque la disponibilidad no sea especialmente alta es de esperar que la propuesta funcionara pero si fuera necesaria una frecuencia de grabación mayor habría que implementar otros recursos, por ejemplo, se podría utilizar un buffer de datos que esperan enviarse, para mandar varios cuando el servidor pueda atender y acopiarlos para cuando no esté disponible. Si la frecuencia con la que se necesita grabar los datos fuera aún mayor habría que plantear otro tipo de protocolos como alternativa al HTTP o incluso sustituir TCP por UDP para poder enviar a la velocidad requerida la mayoría de los datos aún a costa de perder alguno.
Las operaciones que forman la tarea que se trata de realizar para ir enviando la temperatura serían:
- Reiniciar el módulo wifi
- Desconectar del punto de acceso actual (por si existiera una conexión por defecto)
- Establecer la configuración. Para el ejemplo se supone que hay que configurar el modo de conexión (simple) y el papel en las comunicaciones wifi (estación)
- Conectar al punto de acceso
- Verificar que la conexión es correcta (en realidad, este es el punto de entrada) Si no hay conexión, empezar el proceso desde el principio
- Conectar al servidor
- Enviar la petición HTTP con los datos que deben almacenarse
El orden de las operaciones no tiene que ser exactamente así (aunque lo sea la operativa) y cada paso puede necesitar varias órdenes AT del ESP8266, por ejemplo, la configuración de la lista anterior necesitaría dos: AT+CIPMUX=0
y AT+CWMODE=1
.
Una estructura de datos para representar operaciones sobre el ESP8266
En los ejemplos anteriores, aunque de forma muy básica, ya se insinúa una solución genérica al problema: utilizar una estructura de datos que almacene las posibles respuestas y las acciones que deben tomarse en cada caso; enviar una acción, esperar una respuesta y proceder conforme a lo que la respuesta signifique. Como cada operación compleja requerirá varias órdenes AT del ESP8266, la estructura de datos deberá enlazar una operación con otras, siguientes o anteriores, las que corresponda realizar en cada caso en función de la respuesta del ESP8266.
En los ejemplos anteriores se buscaba un mensaje dentro de la respuesta del ESP8266 y se interpretaba como acierto o error. Además de una recepción (y análisis) de todo el texto recibido, para tener un mínimo genérico es recomendable atender también a la finalización del mensaje o, dicho de otro modo, a la disponibilidad del módulo wifi ESP8266 para recibir nuevas órdenes. De esta manera, el cambio a un estado que podríamos llamar, por ejemplo, «wifi disponible», podría ser recibir el nombre del punto de acceso y recibir el texto ERROR
o el texto OK
significaría que el ESP8266 ha terminado la respuesta y que se puede enviar ya la siguiente orden AT al ESP8266.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
// inicializar_operaciones.h // 0 Reiniciar el módulo wifi ESP8266 operacion[REINICIAR_ESP8266]="AT+RST"; mensaje[REINICIAR_ESP8266][FALLO]=mensaje_fallo; mensaje[REINICIAR_ESP8266][ACIERTO]="ready\r\n"; mensaje[REINICIAR_ESP8266][LITERAL]=mensaje_vacio; siguiente_operacion[REINICIAR_ESP8266][FALLO]=REINICIAR_ESP8266; siguiente_operacion[REINICIAR_ESP8266][ACIERTO]=DESCONECTAR_WIFI; siguiente_operacion[REINICIAR_ESP8266][LITERAL]=DESCONECTAR_WIFI; configuracion[REINICIAR_ESP8266]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[REINICIAR_ESP8266]=10000; // 1 Desconectar del punto de acceso por defecto (si fuera el caso) operacion[DESCONECTAR_WIFI]="AT+CWQAP"; mensaje[DESCONECTAR_WIFI][FALLO]=mensaje_fallo; mensaje[DESCONECTAR_WIFI][ACIERTO]=mensaje_acierto; mensaje[DESCONECTAR_WIFI][LITERAL]=mensaje_vacio; siguiente_operacion[DESCONECTAR_WIFI][FALLO]=REINICIAR_ESP8266; siguiente_operacion[DESCONECTAR_WIFI][ACIERTO]=MODO_ESTACION; siguiente_operacion[DESCONECTAR_WIFI][LITERAL]=MODO_ESTACION; configuracion[DESCONECTAR_WIFI]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[DESCONECTAR_WIFI]=2500; // 2 Establecer el modo de estación (no punto de acceso) operacion[MODO_ESTACION]="AT+CWMODE=1"; mensaje[MODO_ESTACION][FALLO]=mensaje_fallo; mensaje[MODO_ESTACION][ACIERTO]=mensaje_acierto; mensaje[MODO_ESTACION][LITERAL]=mensaje_vacio; siguiente_operacion[MODO_ESTACION][FALLO]=REINICIAR_ESP8266; siguiente_operacion[MODO_ESTACION][ACIERTO]=MODO_SIMPLE; siguiente_operacion[MODO_ESTACION][LITERAL]=MODO_SIMPLE; configuracion[MODO_ESTACION]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[MODO_ESTACION]=2500; // 3 Establecer el modo de conexión simple operacion[MODO_SIMPLE]="AT+CIPMUX=0"; mensaje[MODO_SIMPLE][FALLO]=mensaje_fallo; mensaje[MODO_SIMPLE][ACIERTO]=mensaje_acierto; mensaje[MODO_SIMPLE][LITERAL]=mensaje_vacio; siguiente_operacion[MODO_SIMPLE][FALLO]=REINICIAR_ESP8266; siguiente_operacion[MODO_SIMPLE][ACIERTO]=CONECTAR_WIFI; siguiente_operacion[MODO_SIMPLE][LITERAL]=CONECTAR_WIFI; configuracion[MODO_SIMPLE]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[MODO_SIMPLE]=2500; // 4 Conectar al punto de acceso operacion[CONECTAR_WIFI]="AT+CWJAP=\"polaridad.es\",\"54lLij1RiTn3MEd3v41C\""; mensaje[CONECTAR_WIFI][FALLO]=mensaje_fallo; mensaje[CONECTAR_WIFI][ACIERTO]=mensaje_acierto; mensaje[CONECTAR_WIFI][LITERAL]=mensaje_vacio; siguiente_operacion[CONECTAR_WIFI][FALLO]=REINICIAR_ESP8266; siguiente_operacion[CONECTAR_WIFI][ACIERTO]=VERIFICAR_CONEXION; siguiente_operacion[CONECTAR_WIFI][LITERAL]=VERIFICAR_CONEXION; configuracion[CONECTAR_WIFI]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[CONECTAR_WIFI]=20000; // 5 Verificar si hay conexión operacion[VERIFICAR_CONEXION]="AT+CIPSTATUS"; mensaje[VERIFICAR_CONEXION][FALLO]=mensaje_fallo; mensaje[VERIFICAR_CONEXION][ACIERTO]=mensaje_acierto; mensaje[VERIFICAR_CONEXION][LITERAL]="STATUS:5"; siguiente_operacion[VERIFICAR_CONEXION][FALLO]=REINICIAR_ESP8266; siguiente_operacion[VERIFICAR_CONEXION][ACIERTO]=REINICIAR_ESP8266; siguiente_operacion[VERIFICAR_CONEXION][LITERAL]=CONECTAR_SERVIDOR; configuracion[VERIFICAR_CONEXION]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[VERIFICAR_CONEXION]=5000; // 6 Conectar al servidor operacion[CONECTAR_SERVIDOR]="AT+CIPSTART=\"TCP\",\"servidoriot.com\",80"; mensaje[CONECTAR_SERVIDOR][FALLO]=mensaje_fallo; mensaje[CONECTAR_SERVIDOR][ACIERTO]=mensaje_acierto; mensaje[CONECTAR_SERVIDOR][LITERAL]="CONNECT"; siguiente_operacion[CONECTAR_SERVIDOR][FALLO]=REINICIAR_ESP8266; siguiente_operacion[CONECTAR_SERVIDOR][ACIERTO]=INFORMAR_CANTIDAD; siguiente_operacion[CONECTAR_SERVIDOR][LITERAL]=INFORMAR_CANTIDAD; configuracion[CONECTAR_SERVIDOR]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[CONECTAR_SERVIDOR]=15000; // 7 Avisar de la cantidad de datos que se envían operacion[INFORMAR_CANTIDAD]="AT+CIPSEND="; mensaje[INFORMAR_CANTIDAD][FALLO]=mensaje_vacio; mensaje[INFORMAR_CANTIDAD][ACIERTO]=mensaje_acierto; mensaje[INFORMAR_CANTIDAD][LITERAL]=mensaje_vacio; siguiente_operacion[INFORMAR_CANTIDAD][FALLO]=REINICIAR_ESP8266; siguiente_operacion[INFORMAR_CANTIDAD][ACIERTO]=ENVIAR_CANTIDAD; siguiente_operacion[INFORMAR_CANTIDAD][LITERAL]=ENVIAR_CANTIDAD; configuracion[INFORMAR_CANTIDAD]=NO_ESPERAR_RESPUESTA; timeout[INFORMAR_CANTIDAD]=1000; // 8 Enviar cantidad //operacion[ENVIAR_CANTIDAD]="123"; // Definido para cada envío mensaje[ENVIAR_CANTIDAD][FALLO]=sin_enlace; mensaje[ENVIAR_CANTIDAD][ACIERTO]=">"; mensaje[ENVIAR_CANTIDAD][LITERAL]=mensaje_vacio; siguiente_operacion[ENVIAR_CANTIDAD][FALLO]=REINICIAR_ESP8266; siguiente_operacion[ENVIAR_CANTIDAD][ACIERTO]=ENVIAR_PREFIJO_PETICION; siguiente_operacion[ENVIAR_CANTIDAD][LITERAL]=ENVIAR_PREFIJO_PETICION; configuracion[ENVIAR_CANTIDAD]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[ENVIAR_CANTIDAD]=5000; // 9 Enviar el prefijo de la petición operacion[ENVIAR_PREFIJO_PETICION]="GET /frigo03/almacenar_temperatura.php?temperatura="; mensaje[ENVIAR_PREFIJO_PETICION][FALLO]=mensaje_vacio; mensaje[ENVIAR_PREFIJO_PETICION][ACIERTO]=mensaje_vacio; mensaje[ENVIAR_PREFIJO_PETICION][LITERAL]=mensaje_vacio; siguiente_operacion[ENVIAR_PREFIJO_PETICION][FALLO]=REINICIAR_ESP8266; siguiente_operacion[ENVIAR_PREFIJO_PETICION][ACIERTO]=ENVIAR_DATOS; siguiente_operacion[ENVIAR_PREFIJO_PETICION][LITERAL]=ENVIAR_DATOS; configuracion[ENVIAR_PREFIJO_PETICION]=NO_ESPERAR_RESPUESTA; timeout[ENVIAR_PREFIJO_PETICION]=10000; // 10 Enviar la temperatura //operacion[ENVIAR_DATOS]="+000.00"; // Definido para cada envío mensaje[ENVIAR_DATOS][FALLO]=mensaje_vacio; mensaje[ENVIAR_DATOS][ACIERTO]=mensaje_vacio; mensaje[ENVIAR_DATOS][LITERAL]=mensaje_vacio; siguiente_operacion[ENVIAR_DATOS][FALLO]=REINICIAR_ESP8266; siguiente_operacion[ENVIAR_DATOS][ACIERTO]=ENVIAR_SUFIJO_PETICION; siguiente_operacion[ENVIAR_DATOS][LITERAL]=ENVIAR_SUFIJO_PETICION; configuracion[ENVIAR_DATOS]=NO_ESPERAR_RESPUESTA; timeout[ENVIAR_DATOS]=5000; // 11 Enviar el sufijo de la petición operacion[ENVIAR_SUFIJO_PETICION]=" HTTP/1.1\r\nHost: www.servidoriot.com\r\nUser-Agent: ESP8266\r\nConnection: close\r\n\r\n"; mensaje[ENVIAR_SUFIJO_PETICION][FALLO]=sin_enlace; mensaje[ENVIAR_SUFIJO_PETICION][ACIERTO]="CLOSED\r\n\r\nOK\r\n"; mensaje[ENVIAR_SUFIJO_PETICION][LITERAL]=mensaje_vacio; // "SEND OK" siguiente_operacion[ENVIAR_SUFIJO_PETICION][FALLO]=REINICIAR_ESP8266; siguiente_operacion[ENVIAR_SUFIJO_PETICION][ACIERTO]=VERIFICAR_CONEXION; siguiente_operacion[ENVIAR_SUFIJO_PETICION][LITERAL]=VERIFICAR_CONEXION; configuracion[ENVIAR_SUFIJO_PETICION]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[ENVIAR_SUFIJO_PETICION]=20000; |
El código anterior utiliza un vector (operacion
) para almacenar el texto de las sucesivas operaciones que forman la tarea completa. Se usa una matriz bidimensional (mensaje
) con las tres respuestas que se analizan. Como explicaba más arriba, es necesario buscar los mensajes que representan el final de la respuesta además del mensaje que represente una respuesta correcta o errónea. No todas las operaciones tendrán el mismo número de respuestas posibles; cuando haya menos respuestas se puede utilizar un mensaje vacío que consuma el menor número posible de ciclos en su análisis (aún así no es la forma más óptima). Lógicamente, será necesario que el mínimo de respuestas buscado (tres en el ejemplo) recoja todas las posibilidades de funcionamiento, aunque no sean todas las posibles.
Al hablar de las posibles respuestas ya se aprecia que este ejemplo no es muy útil para recibir datos con un formato arbitrario desde un módulo wifi ESP8266, pero es que, en el contexto del uso con microcontroladores no es habitual; lo más frecuente es enviar datos datos recabados por los sensores que tienen conectados y/o recibir información sobre qué hacer con los actuadores que controla. Una información muy tasada, que se puede predecir muy bien.
En la estructura de datos anterior, al igual que se hace para expresar las posibles repuestas que se analizan, para determinar la operación que debe realizarse en cada caso se usa también una matriz bidimensional (siguiente_operacion
). En concreto, se ha elegido responder a tres tipos de mensajes: ① un texto arbitrario (LITERAL
) para verificar si existe conexión al punto de acceso wifi y al servidor, ② un texto para detectar errores en el proceso (FALLO
) y ③ un texto que indica que la operación se ha realizado satisfactoriamente (ACIERTO
).
Por último, hay dos vectores más para establecer el tiempo de espera máximo antes de desistir (timeout
) y especificar (configuracion
) si la operación termina sin esperar respuesta (ESPERAR_RESPUESTA
) y los mensajes que indican el final de la comunicación. Este último vector, para ilustrar un ejemplo de cómo se podría ahorrar memoria memoria, trabaja con los bits de un byte de configuración para indicar los diferentes estados.
Las primeras órdenes AT del ESP8266 de la estructura de datos esperan siempre respuesta, que puede ser el mensaje de acierto o error. Cuando se produce un error se reinicia el módulo y se vuelve a empezar y si el mensaje indica que la operación es correcta se pasa a la siguiente.
Cuando se ha conectado con el servidor, el patrón cambia. En este caso es necesario ① enviar la longitud del paquete de datos que se quiere transmitir y ② componer la petición HTTP con un texto fijo más el valor (de la temperatura) que se envía para ser almacenado en el servidor. La preparación de estos datos se realiza en cada envío y es necesario partir en dos (avisar de la longitud) o tres (enviar la petición HTTP) la orden AT del ESP8266. Solamente la última de las partes en las que se divide la operación esperará respuesta.
En este caso funcionará sin problemas (puede que avisando de que el módulo está ocupado) pero cuando la longitud de los datos sea mayor será necesario partir en trozos más pequeños los bloques de datos e incluso puede que sea necesario implementar una espera, como se hace con la lectura de la temperatura, para dar tiempo el módulo a enviar los datos si llenar su buffer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define REINICIAR_ESP8266 0 // Índice del vector de operaciones que representa la orden de reinicio del módulo wifi ESP8266 #define DESCONECTAR_WIFI 1 #define MODO_ESTACION 2 #define MODO_SIMPLE 3 #define CONECTAR_WIFI 4 #define VERIFICAR_CONEXION 5 #define CONECTAR_SERVIDOR 6 #define INFORMAR_CANTIDAD 7 #define ENVIAR_CANTIDAD 8 #define ENVIAR_PREFIJO_PETICION 9 #define ENVIAR_DATOS 10 #define ENVIAR_SUFIJO_PETICION 11 #define CANTIDAD_OPERACIONES 12 // Cantidad de operaciones que forma el cuerpo de la aplicación #define FALLO 0 // Índice del vector de respuestas que representa el mensaje de error #define FALLO_TERMINA 0B00000001 // 1<<FALLO #define ACIERTO 1 #define ACIERTO_TERMINA 0B00000010 // 1<<ACIERTO #define LITERAL 2 #define LITERAL_TERMINA 0B00000100 // 1<<LITERAL #define CANTIDAD_RESPUESTAS 3 // Cantidad de posibles respuestas que se buscan en el texto recibido desde el ESP8266 #define ESPERAR_RESPUESTA 0B00001000 // 1<<CANTIDAD_RESPUESTAS #define NO_ESPERAR_RESPUESTA 0B00000000 #define ENVIAR_OPERACION esperando_respuesta=true;SERIE.print(operacion[operacion_actual]);if(configuracion[operacion_actual]&ESPERAR_RESPUESTA){SERIE.print("\r\n");}for(unsigned char numero_respuesta=0;numero_respuesta<CANTIDAD_RESPUESTAS;numero_respuesta++){numero_caracter[numero_respuesta]=0;}cronometro_esp8266=millis(); |
Junto con otras macros que ya se han explicado antes, en el código de ejemplo de arriba se muestra cómo se definen los diferentes estados con los que especificar si se debe esperar respuesta y en su caso qué mensaje indica que ha terminado.
Como en diferentes puntos del código se enviará una operación (cuando toque enviar la media de la temperatura, si se supera el tiempo de espera de una operación, al terminar correctamente la operación actual…) pero se establece de forma global cómo hacerlo, se ha definido una macro ENVIAR_OPERACION
que agrupa los pasos que intervienen en el envío.
1 2 3 4 5 6 7 8 9 10 11 12 |
// La macro ENVIAR_OPERACION corresponde con las operaciones: esperando_respuesta=true; SERIE.print(operacion[operacion_actual]); if(configuracion[operacion_actual]&ESPERAR_RESPUESTA) { SERIE.print("\r\n"); } for(unsigned char numero_respuesta=0;numero_respuesta<CANTIDAD_RESPUESTAS;numero_respuesta++) { numero_caracter[numero_respuesta]=0; } cronometro_esp8266=millis(); |
El siguiente es el código del programa principal del ejemplo. La tarea más externa es la que se encarga de ir muestreando la temperatura para calcular la media y, cada cierto lapso de tiempo, se envía al servidor utilizando el módulo wifi ESP8266. Una vez enviada cada operación se analiza la respuesta para determinar cuál es la siguiente o si se ha terminado la tarea de envío de información.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
#include "ESP8266_operacion_compleja_varias_ordenes_AT.h" #define PIN_TEMPERATURA A0 // Pin analógico al que se conecta la salida del sensor de temperatura LM35 #define INTERVALO_LECTURA_TEMPERATURA 30000 // Leer la temperatura cada 30 segundos (30*1000) #define INTERVALO_GRABACION_TEMPERATURA 300000 // Grabar la media de la temperatura cada 5 minutos (5*60*1000) unsigned long muestras=0; // Número de veces que se ha medido la temperatura (para calcular la media) float temperatura; // de -55.0 °C a +150 °C | de 0 V a 2.050 V | de -550 mV a +1500 mV | analogRead*5.0/1023.0*100-55.0 analogRead/2.46-55.0 float media_temperaturas=0.0; unsigned long cronometro_lectura_temperatura; unsigned long cronometro_grabacion_temperatura; char *mensaje_fallo="ERROR\r\n"; char *mensaje_acierto="OK\r\n"; char *sin_enlace="link is not\r\n"; char *mensaje_vacio="\f"; char *operacion[CANTIDAD_OPERACIONES]; // Matriz de punteros a constantes de caracteres con las operaciones que se envían al ESP8266 (no solo órdenes AT, aunque seguro que algunas son órdenes AT) char *mensaje[CANTIDAD_OPERACIONES][CANTIDAD_RESPUESTAS]; // Mensajes de respuesta unsigned char siguiente_operacion[CANTIDAD_OPERACIONES][CANTIDAD_RESPUESTAS]; unsigned char configuracion[CANTIDAD_OPERACIONES]; unsigned int timeout[CANTIDAD_OPERACIONES]; unsigned char numero_caracter[CANTIDAD_RESPUESTAS]; unsigned int longitud_peticion; char texto_longitud_peticion[4]; // 3 caracteres para almacenar la longitud de la petición en formato texto char valor_enviado[9]; // signo + 4 enteros + punto + 2 decimales + \0 = 9 unsigned long cronometro_esp8266; unsigned char operacion_actual; // Número de operación que se está procesando unsigned char proxima_operacion; // Siguiente operación que se procesará cuando termine la actual char lectura_serie; boolean grabando_datos=false; boolean esperando_respuesta; void setup() { #include "inicializar_operaciones.h" longitud_peticion=strlen(operacion[ENVIAR_PREFIJO_PETICION])+strlen(operacion[ENVIAR_SUFIJO_PETICION]); SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 //delay(8000); // En fase de pruebas se puede introducir un tiempo de espera para conectar una consola/sniffer cronometro_lectura_temperatura=millis(); cronometro_grabacion_temperatura=millis(); } void loop() { if((unsigned long)(millis()-cronometro_lectura_temperatura)>INTERVALO_LECTURA_TEMPERATURA) { cronometro_lectura_temperatura=millis(); temperatura=analogRead(PIN_TEMPERATURA)/2.46-55.0; muestras++; media_temperaturas=(float)temperatura/(float)muestras+media_temperaturas*(float)(muestras-1)/(float)(muestras); } if(grabando_datos) { if((unsigned long)(millis()-cronometro_esp8266)>timeout[operacion_actual]) // Si se ha superado el tiempo de espera máximo { operacion_actual=siguiente_operacion[operacion_actual][FALLO]; // Pasar a la operación correspondiente al error ENVIAR_OPERACION } else // Si no se ha superado el tiempo de espera máximo { if(configuracion[operacion_actual]&ESPERAR_RESPUESTA) // Si la siguiente operación depende de la respuesta a la actual desde el ESP8266 hay que leer la información que llegue desde el puerto serie { while(SERIE.available()) { lectura_serie=SERIE.read(); for(unsigned char numero_respuesta=0;numero_respuesta<CANTIDAD_RESPUESTAS;numero_respuesta++) // Comparar la letra cargada desde el puerto serie con la correspondiente de los mensajes disponibles { if(lectura_serie==mensaje[operacion_actual][numero_respuesta][numero_caracter[numero_respuesta]]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { numero_caracter[numero_respuesta]++; // Como el carácter coincide, se puede comparar con el siguiente lo próximo que llegue por el puerto serie if(mensaje[operacion_actual][numero_respuesta][numero_caracter[numero_respuesta]]==0) // Si el carácter que toca es \0 es que se ha terminado de analizar el mensaje { if(esperando_respuesta) // Todavía no se ha encontrado un mensaje que determine la siguiente operación { proxima_operacion=siguiente_operacion[operacion_actual][numero_respuesta]; // La próxima operación que habrá que procesar será la que indique el mensaje encontrado para la operación actual esperando_respuesta=false; // Ya se ha encontrado un mensaje que determina la siguiente operación } if(configuracion[operacion_actual]&(1<<numero_respuesta)) // Si el mensaje encontrado es uno de los que terminan la operación… { if(operacion_actual+1==CANTIDAD_OPERACIONES) // Se ha completado la última operación de la tarea compleja { grabando_datos=false; // Se ha terminado la tarea compleja (grabar datos en el servidor) } else // No es la última operación de la tarea compleja, hay que seguir realizando otras operaciones { operacion_actual=proxima_operacion; // Ejecutar la siguiente operación ENVIAR_OPERACION } } } } else // Si la letra recibida no es igual que la correspondiente del mensaje { numero_caracter[numero_respuesta]=0; // Empezar a comparar desde la primera letra del mensaje } } } } else // Si no hay que esperar datos desde el puerto serie { operacion_actual=siguiente_operacion[operacion_actual][ACIERTO]; ENVIAR_OPERACION } } } else { if((unsigned long)(millis()-cronometro_grabacion_temperatura)>INTERVALO_GRABACION_TEMPERATURA) { cronometro_grabacion_temperatura=millis(); dtostrf(media_temperaturas,4,2,valor_enviado); // snprintf(valor_enviado,9,"%+3.2f",media_temperaturas); // printf y derivadas no funcionan en AVR snprintf(texto_longitud_peticion,4,"%d",longitud_peticion+strlen(valor_enviado)); grabando_datos=true; operacion_actual=VERIFICAR_CONEXION; operacion[ENVIAR_DATOS]=valor_enviado; operacion[ENVIAR_CANTIDAD]=texto_longitud_peticion; ENVIAR_OPERACION } } } |
Lógicamente, sobre el código anterior cabe efectuar varias acciones de optimización pero, como se trata de un ejemplo para entender cómo se puede utilizar el ESP8266 de forma genérica, solamente merece la pena detenerse en algunos aspectos, el primero es la estructura de datos. Parece que lo lógico es utilizar una estructura de datos del lenguaje de programación (struct
) para representar la información que se procesa: las órdenes AT del ESP8266 y los mensajes que se analizan.
Usar una estructura (struct
) para almacenar los datos en lugar de las matrices del ejemplo (partiendo de ellas) es trivial y, aunque puede resultar en un código más elegante no supone ninguna mejora en el resultado. La verdadera alternativa que plantea el uso de struct
es implementar, como se explica más abajo, longitudes variables en las estructuras que datos «interiores» que son referidas por ellas. De esta forma, por ejemplo, no sería necesario que una operación tuviera un número fijo de respuestas a analizar.
Este planteamiento sugiere que es la mejor forma de implementar la solución pero el inconveniente es que sería necesario utilizar asignación dinámica de memoria, una práctica de riesgo trabajando con un microcontrolador que exige medir cuidadosamente cuánta memoria se utilizará en tiempo de ejecución, ya que difícilmente el compilador podrá avisarnos de esto y cabe la posibilidad cierta de agotar la memoria (o la pila) con el consecuencias fatales para la ejecución del programa.
En la línea de optimizar el código, es interesante recordar que, en un programa de este tipo, que utiliza una gran cantidad de texto, se puede ahorrar espacio de la memoria SRAM almacenando las cadenas de texto en la memoria de programa (flash) con la macro F()
. En las siguientes capturas de pantalla puede verse la diferente distribución de memoria de programa y dinámica con un uso normal del texto y utilizando la macro F()
.
Con respecto a las acciones que se ejecutan según la información que llegue desde el módulo wifi ESP8266, como alternativa a comprobar el mensaje desde el código y realizar una u otra según lo recibido, pueden almacenarse en esta estructura de datos punteros a las funciones que realizan cada tarea en lugar de indicadores de estado (banderas) que avisen de un determinado estado que la aplicación se encarga de gestionar, por ejemplo, dentro del bucle principal.
El siguiente es un ejemplo de estructuras para almacenar los datos de las peticiones al ESP8266 (el tipo de datos operacion_esp8266
) y sus respuestas (el tipo de datos respuesta_esp8266
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
typedef struct estructura_operacion_esp8266 operacion_esp8266; // Se define el tipo de datos operacion_esp8266 que corresponde con la estructura (struct) llamada estructura_operacion_esp8266 que se define más adelante typedef struct estructura_respuesta_esp8266 respuesta_esp8266; // Se define el tipo de datos respuesta_esp8266 que corresponde con la estructura (struct) llamada struct estructura_respuesta_esp8266 que se define más adelante struct estructura_operacion_esp8266 { char *peticion; // Datos que se envían al ESP8266 para iniciar una operación (como una orden AT, pero también algo como la petición a un servidor…) unsigned char cantidad_respuestas; // Número de posibles respuestas del ESP8266 que se van a analizar, que puede ser variable en cada petición (no solo OK y ERROR) unsigned char timeout; // Tiempo (segundos) que se espera la respuesta del ESP8266 antes de desistir respuesta_esp8266 *respuesta; // Respuestas (estructura) que se esperan de esta operación }; struct estructura_respuesta_esp8266 { char *mensaje; // Mensaje que se espera recibir desde el ESP8266 unsigned char posicion_mensaje; // Posición (letra) que se está comparando con la recibida desde el ESP8266 //boolean estado; // El estado se representa por un valor booleano (por ejemplo ¿se ha encontrado ya esta respuesta? para no seguir buscándola) //unsigned char *estado; // El estado se representa con un texto (de longitud variable) unsigned char estado; // El estado se establece con 8 banderas, una por bit operacion_esp8266 *operacion; // Puntero a operación que se ejecutará si se encuentra esta respuesta en el mensaje devuelto por el ESP8266 }; operacion_esp8266 comprobar_conexion; respuesta_esp8266 respuesta_OK; respuesta_esp8266 respuesta_ERROR; |
Como la estructura que representa la operación (los datos que se envían al módulo wifi ESP8266) hace referencia a la estructura con la que se definen las respuestas, y la estructura de las respuesta a la estructura de las operaciones, es necesario declarar ambas primero, al definir el nuevo tipo de datos, y después definir su contenido.
El anterior ejemplo considera que en el programa que lo incluye se ha optado por usar un indicador de estado, que deberá corresponder con una variable accesible desde el código que se encargue de realizar unas u otras operaciones según indique dicho valor. Si en la respuesta del ESP8266 que se analiza se encuentra determinado texto, el estado toma el valor que indica la estructura de la respuesta correspondiente.
Como se ha dicho antes, otra alternativa, ya sea para sustituir o para complementar a un indicador de estado, sería almacenar en la estructura referencia una función (un puntero) que se llamaría al encontrar cierto texto en la respuesta desde el módulo wifi ESP8266.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
typedef struct estructura_operacion_esp8266 operacion_esp8266; // Se define el tipo de datos operacion_esp8266 que corresponde con la estructura (struct) llamada estructura_operacion_esp8266 que se define más adelante typedef struct estructura_respuesta_esp8266 respuesta_esp8266; // Se define el tipo de datos respuesta_esp8266 que corresponde con la estructura (struct) llamada struct estructura_respuesta_esp8266 que se define más adelante struct estructura_operacion_esp8266 { char *peticion; // Datos que se envían al ESP8266 para iniciar una operación (como una orden AT, pero también algo como la petición a un servidor…) unsigned char cantidad_respuestas; // Número de posibles respuestas del ESP8266 que se van a analizar, que puede ser variable en cada petición (no solo OK y ERROR) unsigned char timeout; // Tiempo (segundos) que se espera la respuesta del ESP8266 antes de desistir respuesta_esp8266 *respuesta; // Respuestas (estructura) que se esperan de esta operación }; struct estructura_respuesta_esp8266 { char *mensaje; // Mensaje que se espera recibir desde el ESP8266 unsigned char posicion_mensaje; // Posición (letra) que se está comparando con la recibida desde el ESP8266 //boolean estado; // El estado se representa por un valor booleano (por ejemplo ¿se ha encontrado ya esta respuesta? para no seguir buscándola) //unsigned char *estado; // El estado se representa con un texto (de longitud variable) unsigned char estado; // El estado se establece con 8 banderas, una por bit float (*accion)(unsigned char,unsigned char); // Puntero a la función que se llama si se encuentra la respuesta operacion_esp8266 *operacion; // Puntero a operación que se ejecutará si se encuentra esta respuesta en el mensaje devuelto por el ESP8266 }; operacion_esp8266 comprobar_conexion; respuesta_esp8266 respuesta_OK; respuesta_esp8266 respuesta_ERROR; |
En el ejemplo anterior se ha añadido a la estructura de datos que sirve para procesar la respuesta procedente del módulo wifi ESP8266 un puntero a una (supuesta) función que devuelve un dato de tipo float
(podría ser el valor ponderado de una lectura analógica) y a la que se le aportan como argumentos dos bytes (dos unsigned char
que podrían ser el pin desde el que se lee la entrada analógica y el que activa el ENABLE de un hipotético integrado).
En el desarrollo para MCU, al contrario de lo que se ocurre en el estilo de desarrollo para sistemas mayores, no es tan infrecuente utilizar variables globales al definir el comportamiento (global) de la aplicación que controla un montaje, así que no será especialmente raro encontrar este tipo de definiciones como funciones sin parámetros y que no devuelven valores, algo como void (*accion)();
Si se trabaja con esta forma de representar los datos, usando struct
de datos de longitud variable, será necesario asignar de forma dinámica la memoria con malloc()
(o new()
, si se usan objetos), que utilizará como parámetro la cantidad de memoria asignada y devolverá un puntero al principio de la zona de memoria que se reserva. Con sizeof()
sobre el tipo que se almacenan, multiplicado por el número de elementos usados, se puede obtener la cantidad de memoria que se necesita. En las capturas de pantalla de abajo puede verse un ejemplo con y sin usar malloc()
; cuidado con la memoria usada por el programa en el primer caso, necesita cargar la librería que contiene esta función.
Si las operaciones sobre el módulo wifi ESP8266 variaran a lo largo de la ejecución del programa será necesario ir liberando la memoria que no se utiliza con free()
(o delete()
, en el caso de ser objetos). Aunque es razonable esperar que el compilador (GCC) optimizará el programa para evitar el fraccionamiento de la memoria, seguramente el rendimiento no será tan óptimo como trabajar con memoria asignada de forma estática.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
error_operacion.mensaje="ERROR\r\n"; error_operacion.termina_operacion=true; error_operacion.operacion=&reiniciar_esp8266; error_enlace.mensaje="link is not\r\n"; error_enlace.termina_operacion=true; error_enlace.operacion=&reiniciar_esp8266; reiniciar_esp8266_correcto.mensaje="ready\r\n"; reiniciar_esp8266_correcto.termina_operacion=true; reiniciar_esp8266_correcto.operacion=&desconectar_wifi; desconectar_wifi_correcto.mensaje=mensaje_acierto; desconectar_wifi_correcto.termina_operacion=true; desconectar_wifi_correcto.operacion=&establecer_modo_estacion; establecer_modo_estacion_correcto.mensaje=mensaje_acierto; establecer_modo_estacion_correcto.termina_operacion=true; establecer_modo_estacion_correcto.operacion=&establecer_modo_simple; establecer_modo_simple_correcto.mensaje=mensaje_acierto; establecer_modo_simple_correcto.termina_operacion=true; establecer_modo_simple_correcto.operacion=&conectar_wifi; conectar_wifi_correcto.mensaje=mensaje_acierto; conectar_wifi_correcto.termina_operacion=true; conectar_wifi_correcto.operacion=&verificar_conexion; verificar_conexion_correcto.mensaje="STATUS:5"; verificar_conexion_correcto.termina_operacion=false; verificar_conexion_correcto.operacion=&conectar_servidor; verificar_conexion_terminado.mensaje=mensaje_acierto; verificar_conexion_terminado.termina_operacion=true; verificar_conexion_terminado.operacion=&reiniciar_esp8266; conectar_servidor_correcto.mensaje="CONNECT"; conectar_servidor_correcto.termina_operacion=false; conectar_servidor_correcto.operacion=&informar_cantidad; conectar_servidor_terminado.mensaje=mensaje_acierto; conectar_servidor_terminado.termina_operacion=true; conectar_servidor_terminado.operacion=&reiniciar_esp8266; informar_cantidad_correcto.mensaje=mensaje_acierto; informar_cantidad_correcto.termina_operacion=true; informar_cantidad_correcto.operacion=&enviar_cantidad; enviar_cantidad_correcto.mensaje=">"; enviar_cantidad_correcto.termina_operacion=true; enviar_cantidad_correcto.operacion=&enviar_prefijo; enviar_prefijo_correcto.operacion=&enviar_datos; enviar_datos_correcto.operacion=&enviar_sufijo; enviar_sufijo_correcto.mensaje="CLOSED\r\n\r\nOK\r\n"; enviar_sufijo_correcto.termina_operacion=true; enviar_sufijo_correcto.operacion=&verificar_conexion; reiniciar_esp8266.peticion="AT+RST"; reiniciar_esp8266.timeout=15000; reiniciar_esp8266.cantidad_respuestas=2; reiniciar_esp8266.respuesta=malloc(sizeof(respuesta_esp8266*)*reiniciar_esp8266.cantidad_respuestas); reiniciar_esp8266.respuesta[FALLO]=&error_operacion; reiniciar_esp8266.respuesta[ACIERTO]=&reiniciar_esp8266_correcto; desconectar_wifi.peticion="AT+CWQAP"; desconectar_wifi.timeout=2500; desconectar_wifi.cantidad_respuestas=2; desconectar_wifi.respuesta=malloc(sizeof(respuesta_esp8266*)*desconectar_wifi.cantidad_respuestas); desconectar_wifi.respuesta[FALLO]=&error_operacion; desconectar_wifi.respuesta[ACIERTO]=&desconectar_wifi_correcto; establecer_modo_estacion.peticion="AT+CWMODE=1"; establecer_modo_estacion.timeout=2500; establecer_modo_estacion.cantidad_respuestas=2; establecer_modo_estacion.respuesta=malloc(sizeof(respuesta_esp8266*)*establecer_modo_estacion.cantidad_respuestas); establecer_modo_estacion.respuesta[FALLO]=&error_operacion; establecer_modo_estacion.respuesta[ACIERTO]=&establecer_modo_estacion_correcto; establecer_modo_simple.peticion="AT+CIPMUX=0"; establecer_modo_simple.timeout=2500; establecer_modo_simple.cantidad_respuestas=2; establecer_modo_simple.respuesta=malloc(sizeof(respuesta_esp8266*)*establecer_modo_simple.cantidad_respuestas); establecer_modo_simple.respuesta[FALLO]=&error_operacion; establecer_modo_simple.respuesta[ACIERTO]=&establecer_modo_simple_correcto; conectar_wifi.peticion="AT+CWJAP=\"polaridad.es\",\"54lLij1RiTn3MEd3v41C\"";; conectar_wifi.timeout=20000; conectar_wifi.cantidad_respuestas=2; conectar_wifi.respuesta=malloc(sizeof(respuesta_esp8266*)*conectar_wifi.cantidad_respuestas); conectar_wifi.respuesta[FALLO]=&error_operacion; conectar_wifi.respuesta[ACIERTO]=&conectar_wifi_correcto; verificar_conexion.peticion="AT+CIPSTATUS"; verificar_conexion.timeout=5000; verificar_conexion.cantidad_respuestas=3; verificar_conexion.respuesta=malloc(sizeof(respuesta_esp8266*)*verificar_conexion.cantidad_respuestas); verificar_conexion.respuesta[FALLO]=&error_operacion; verificar_conexion.respuesta[ACIERTO]=&verificar_conexion_correcto; verificar_conexion.respuesta[OTRO_MENSAJE]=&verificar_conexion_terminado; conectar_servidor.peticion="AT+CIPSTART=\"TCP\",\"servidoriot.com\",80"; conectar_servidor.timeout=15000; conectar_servidor.cantidad_respuestas=3; conectar_servidor.respuesta=malloc(sizeof(respuesta_esp8266*)*conectar_servidor.cantidad_respuestas); conectar_servidor.respuesta[FALLO]=&error_operacion; conectar_servidor.respuesta[ACIERTO]=&conectar_servidor_correcto; conectar_servidor.respuesta[OTRO_MENSAJE]=&conectar_servidor_terminado; // OK, no significa que haya conexión pero sí termina la operación informar_cantidad.peticion="AT+CIPSEND="; informar_cantidad.timeout=1000; informar_cantidad.cantidad_respuestas=1; informar_cantidad.respuesta=malloc(sizeof(respuesta_esp8266*)*informar_cantidad.cantidad_respuestas); informar_cantidad.respuesta[0]=&informar_cantidad_correcto; //enviar_cantidad.peticion=""; // Se asigna cuando se conoce el valor que se va a enviar y se puede calcular la longitud que ocupa (número de caracteres) enviar_cantidad.timeout=5000; enviar_cantidad.cantidad_respuestas=2; enviar_cantidad.respuesta=malloc(sizeof(respuesta_esp8266*)*enviar_cantidad.cantidad_respuestas); enviar_cantidad.respuesta[FALLO]=&error_enlace; enviar_cantidad.respuesta[ACIERTO]=&enviar_cantidad_correcto; enviar_prefijo.peticion="GET /frigo03/almacenar_temperatura.php?temperatura="; enviar_prefijo.timeout=10000; enviar_prefijo.cantidad_respuestas=1; enviar_prefijo.respuesta=malloc(sizeof(respuesta_esp8266*)*enviar_prefijo.cantidad_respuestas); enviar_prefijo.respuesta[0]=&enviar_prefijo_correcto; //enviar_datos.peticion=""; // Se asigna en cuando se conoce el valor que se va a enviar enviar_datos.timeout=5000; enviar_datos.cantidad_respuestas=1; enviar_datos.respuesta=malloc(sizeof(respuesta_esp8266*)*enviar_datos.cantidad_respuestas); enviar_datos.respuesta[0]=&enviar_datos_correcto; enviar_sufijo.peticion=" HTTP/1.1\r\nHost: www.servidoriot.com\r\nUser-Agent: ESP8266\r\nConnection: close\r\n\r\n"; enviar_sufijo.timeout=20000; enviar_sufijo.cantidad_respuestas=2; enviar_sufijo.respuesta=malloc(sizeof(respuesta_esp8266*)*enviar_sufijo.cantidad_respuestas); enviar_sufijo.respuesta[FALLO]=&error_enlace; enviar_sufijo.respuesta[ACIERTO]=&enviar_sufijo_correcto; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
#if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define FALLO 0 // Índice del vector de respuestas que representa el mensaje de error #define FALLO_TERMINA 0B00000001 // 1<<FALLO #define ACIERTO 1 #define ACIERTO_TERMINA 0B00000010 // 1<<ACIERTO #define OTRO_MENSAJE 2 #define OTRO_MENSAJE_TERMINA 0B00000100 // 1<<OTRO_MENSAJE #define CANTIDAD_RESPUESTAS 3 // Cantidad de posibles respuestas que se buscan en el texto recibido desde el ESP8266 #define ESPERAR_RESPUESTA 0B00001000 // 1<<CANTIDAD_RESPUESTAS #define NO_ESPERAR_RESPUESTA 0B00000000 #define ENVIAR_OPERACION esperando_respuesta=true;SERIE.print((*operacion_actual).peticion);if((*operacion_actual).cantidad_respuestas>1){SERIE.print("\r\n");for(unsigned char numero_respuesta=0;numero_respuesta<(*operacion_actual).cantidad_respuestas;numero_respuesta++){numero_caracter[numero_respuesta]=0;}}cronometro_esp8266=millis(); #define PIN_TEMPERATURA A0 // Pin analógico al que se conecta la salida del sensor de temperatura LM35 #define INTERVALO_LECTURA_TEMPERATURA 30000 // Leer la temperatura cada 30 segundos (30*1000) #define INTERVALO_GRABACION_TEMPERATURA 300000 // Grabar la media de la temperatura cada 5 minutos (5*60*1000) unsigned long muestras=0; // Número de veces que se ha medido la temperatura (para calcular la media) float temperatura; // de -55.0 °C a +150 °C | de 0 V a 2.050 V | de -550 mV a +1500 mV | analogRead*5.0/1023.0*100-55.0 analogRead/2.46-55.0 float media_temperaturas=0.0; unsigned long cronometro_lectura_temperatura; unsigned long cronometro_grabacion_temperatura; char *mensaje_acierto="OK\r\n"; typedef struct estructura_operacion_esp8266 operacion_esp8266; // Se define el tipo de datos operacion_esp8266 que corresponde con la estructura (struct) llamada estructura_operacion_esp8266 que se define más adelante typedef struct estructura_respuesta_esp8266 respuesta_esp8266; // Se define el tipo de datos respuesta_esp8266 que corresponde con la estructura (struct) llamada struct estructura_respuesta_esp8266 que se define más adelante struct estructura_operacion_esp8266 { char *peticion; // Datos que se envían al ESP8266 para iniciar una operación (como una orden AT, pero también algo como la petición a un servidor…) unsigned int timeout; // Tiempo (segundos) que se espera la respuesta del ESP8266 antes de desistir bool espera_respuesta; // Si no espera respuesta en cuanto se termine de enviar la orden se puede pasar a la siguiente unsigned char cantidad_respuestas; // Número de posibles respuestas del ESP8266 que se van a analizar, que puede ser variable en cada petición (no solo OK y ERROR) respuesta_esp8266 **respuesta; // Respuestas (estructura) que se esperan de esta operación }; struct estructura_respuesta_esp8266 { char *mensaje; // Mensaje que se espera recibir desde el ESP8266 bool termina_operacion; // Cuando se termina de leer el mensaje ha terminado la operación operacion_esp8266 *operacion; // Puntero a operación que se ejecutará si se encuentra esta respuesta en el mensaje devuelto por el ESP8266 }; operacion_esp8266 *operacion_actual; // Operación sobre el ESP8266 que se está ejecutando actualmente operacion_esp8266 *proxima_operacion; // Siguiente operación que se procesará cuando termine la actual unsigned int longitud_peticion; // Número de caracteres que ocupa la petición HTTP char texto_longitud_peticion[4]; // 3 caracteres para almacenar la longitud de la petición en formato texto char valor_enviado[9]; // signo + 4 enteros + punto + 2 decimales + \0 = 9 unsigned long cronometro_esp8266; // Cronómetro para controlar el tiempo máximo de respuesta del ESP8266 antes de desistir char lectura_serie; // buffer con el carácter leído desde el ESP8266 boolean grabando_datos=false; // Verdadero cuando han terminado todas las operaciones necesarias para grabar los datos boolean esperando_respuesta; // Verdadero si aún no se ha encontrado una de los mensajes que indica que ha terminado la respuesta unsigned char numero_caracter[CANTIDAD_RESPUESTAS]; // Número de orden de la letra del mensaje-respuesta que se está almacenando (una matriz de, como máximo, el mayor número de respuestas posible) operacion_esp8266 reiniciar_esp8266; // Reiniciar el módulo wifi ESP8266 operacion_esp8266 desconectar_wifi; // Desconectar del punto de acceso por defecto (si fuera el caso) operacion_esp8266 establecer_modo_estacion; // Establecer el modo de estación (no punto de acceso) operacion_esp8266 establecer_modo_simple; // Establecer el modo de conexión simple operacion_esp8266 conectar_wifi; // Conectar al punto de acceso operacion_esp8266 verificar_conexion; // Verificar si hay conexión operacion_esp8266 conectar_servidor; // Conectar al servidor operacion_esp8266 informar_cantidad; // Avisar de la cantidad de datos que se envían operacion_esp8266 enviar_cantidad; // Enviar cantidad operacion_esp8266 enviar_prefijo; // Enviar el prefijo de la petición operacion_esp8266 enviar_datos; // Enviar la temperatura operacion_esp8266 enviar_sufijo; // Enviar el sufijo de la petición respuesta_esp8266 error_operacion; // Todas las respuestas "ERROR" reinician el módulo wifi ESP8266 respuesta_esp8266 error_enlace; // Las respuestas "link is not" también reinician el módulo wifi ESP8266 respuesta_esp8266 reiniciar_esp8266_correcto; respuesta_esp8266 desconectar_wifi_correcto; respuesta_esp8266 establecer_modo_estacion_correcto; respuesta_esp8266 establecer_modo_simple_correcto; respuesta_esp8266 conectar_wifi_correcto; respuesta_esp8266 verificar_conexion_correcto; respuesta_esp8266 verificar_conexion_terminado; respuesta_esp8266 conectar_servidor_correcto; respuesta_esp8266 conectar_servidor_terminado; respuesta_esp8266 informar_cantidad_correcto; respuesta_esp8266 enviar_cantidad_correcto; respuesta_esp8266 enviar_prefijo_correcto; respuesta_esp8266 enviar_datos_correcto; respuesta_esp8266 enviar_sufijo_correcto; void setup() { #include "inicializar_operaciones.h" longitud_peticion=strlen(enviar_prefijo.peticion)+strlen(enviar_sufijo.peticion); SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 //delay(8000); // En fase de pruebas se puede introducir un tiempo de espera para conectar una consola/sniffer cronometro_lectura_temperatura=millis(); cronometro_grabacion_temperatura=millis(); } void loop() { if((unsigned long)(millis()-cronometro_lectura_temperatura)>INTERVALO_LECTURA_TEMPERATURA) { cronometro_lectura_temperatura=millis(); temperatura=analogRead(PIN_TEMPERATURA)/2.46-55.0; muestras++; media_temperaturas=(float)temperatura/(float)muestras+media_temperaturas*(float)(muestras-1)/(float)(muestras); } if(grabando_datos) { if((unsigned long)(millis()-cronometro_esp8266)>(unsigned long)(*operacion_actual).timeout) // Si se ha superado el tiempo de espera máximo { operacion_actual=(*(*operacion_actual).respuesta[FALLO]).operacion; // Pasar a la operación correspondiente al error ENVIAR_OPERACION } else // Si no se ha superado el tiempo de espera máximo { if((*operacion_actual).cantidad_respuestas>1) // Si la siguiente operación depende de la respuesta a la actual desde el ESP8266 hay que leer la información que llegue desde el puerto serie { while(SERIE.available()) { lectura_serie=SERIE.read(); for(unsigned char numero_respuesta=0;numero_respuesta<(*operacion_actual).cantidad_respuestas;numero_respuesta++) // Comparar la letra cargada desde el puerto serie con la correspondiente de los mensajes disponibles { if(lectura_serie==(*(*operacion_actual).respuesta[numero_respuesta]).mensaje[numero_caracter[numero_respuesta]]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { numero_caracter[numero_respuesta]++; // Como el carácter coincide, se puede comparar con el siguiente lo próximo que llegue por el puerto serie if((*(*operacion_actual).respuesta[numero_respuesta]).mensaje[numero_caracter[numero_respuesta]]==0) // Si el carácter que toca es \0 es que se ha terminado de analizar el mensaje { if(esperando_respuesta) // Todavía no se ha encontrado un mensaje que determine la siguiente operación { proxima_operacion=(*(*operacion_actual).respuesta[numero_respuesta]).operacion; // La próxima operación que habrá que procesar será la que indique el mensaje encontrado para la operación actual esperando_respuesta=false; // Ya se ha encontrado un mensaje que determina la siguiente operación } if((*(*operacion_actual).respuesta[numero_respuesta]).termina_operacion) // Si el mensaje encontrado es uno de los que terminan la operación… { if(operacion_actual==&enviar_sufijo) // Se ha completado la última operación de la tarea compleja { grabando_datos=false; // Se ha terminado la tarea compleja (grabar datos en el servidor) } else // No es la última operación de la tarea compleja, hay que seguir realizando otras operaciones { operacion_actual=proxima_operacion; // Ejecutar la siguiente operación ENVIAR_OPERACION } } } } else // Si la letra recibida no es igual que la correspondiente del mensaje { numero_caracter[numero_respuesta]=0; // Empezar a comparar desde la primera letra del mensaje } } } } else // Si no hay que esperar datos desde el puerto serie { operacion_actual=(*(*operacion_actual).respuesta[0]).operacion; ENVIAR_OPERACION } } } else { if((unsigned long)(millis()-cronometro_grabacion_temperatura)>INTERVALO_GRABACION_TEMPERATURA) { cronometro_grabacion_temperatura=millis(); dtostrf(media_temperaturas,4,2,valor_enviado); // snprintf(valor_enviado,9,"%+3.2f",media_temperaturas); // printf y derivadas no funcionan en AVR snprintf(texto_longitud_peticion,4,"%d",longitud_peticion+strlen(valor_enviado)); grabando_datos=true; operacion_actual=&verificar_conexion; enviar_datos.peticion=valor_enviado; enviar_cantidad.peticion=texto_longitud_peticion; ENVIAR_OPERACION } } } |
Aunque en este ejemplo (en ambas implementaciones) no tiene mucho sentido, en la línea de generalizar el funcionamiento para poder aplicarlo a otros casos, hay que resaltar que el envío de datos repite siempre el mismo protocolo: avisar de la cantidad de bytes que se enviarán, esperar el indicador (>) y enviar los datos.
Como en este ejemplo solamente se usa en una ocasión (toda la petición se hace en un paquete), no parece muy útil pero, en general, puede ser necesario para realizar varios envíos en la misma operación, incluyendo los casos en que se deban transmitir cantidades importantes de datos que deberán fragmentarse para no desbordar la memoria del ESP8266.
Para implementar este comportamiento se pueden utilizar los dos últimos elementos de la conexión de forma que a cada envío se rellenen los datos con los valores que correspondan: en el primer caso el número de bytes enviados y en el segundo la (parte de la) petición que se transmita.
Para repetir la asignación y el envío de los diferentes elementos que se deban transmitir se pueden almacenar en un vector. Este nuevo vector será el que determine el final de la operación compleja y no la última operación como hasta ahora.
Carlos CC
Todo lo del ESP8266 lo tengo más o menos claro pero no termino de entender dos cosas que creo que son de programación, las struct y la memoria dinámica.
¿Es mejor usar struct que matrices? ¿Para usar memoria dinámica hay que usar struct? ¿Por qué es malo usar memoria dinámica?
Gracias.
Víctor Ventura
Hola, Carlos.
Las matrices no son mejores que las estructuras ni las estructuras son mejores que las matrices, en todo caso, unas serán mejores que otras en determinado uso. Yo creo que en este caso las estructuras describen mejor los datos pero no mejoran el rendimiento del programa, es decir, principalmente harán el programa más legible y fácil de mantener pero no explotarán mejor los recursos (de hecho, necesita un poco más de memoria). Entiendo que esto tiene una cierta componente subjetiva y sería difícil mantener que, si tú trabajas mejor con matrices (supongamos) fuera para ti más eficiente usar estructuras.
No es necesario usar estructuras para trabajar con memoria dinámica, pero se tratan a la vez en el artículo porque se proponen ambas como una forma de optimizar un poco la primera versión funcional del código. Creo que, buscando la máxima eficiencia del código (quizá no la legibilidad), lo mejor sería utilizar una versión mixta con matrices (dimensiones fijas) para la estructura mayor y asignar dinámicamente los contenidos.
No es malo asignar memoria de forma dinámica pero ① es más fácil cometer errores con esta técnica que con otra, si no aporta una mejora en la explotación de los recursos no es aconsejable usarla, ② los recursos de un microcontrolador (frente a un sistema mayor) son muy reducidos y hay que medir muy bien («manualmente», en muchos casos) cuánta memoria se asignará, o el riesgo de colapsar el sistema es muy grande, mientras que si se asignan valores a variables el compilador puede informar de la memoria necesaria y ③ tradicionalmente, la asignación dinámica ha producido fragmentación de memoria y aunque esto ha mejorado mucho en las versiones más nuevas de los compiladores, ha dejado «mala fama» al uso de esta técnica, especialmente en el entorno de MCU; por otro lado, para no perder memoria y poder reasignarla ciertamente hay un trabajo extra de liberación de la memoria a lo largo de la ejecución del programa.
En los ejemplos del artículo, la cantidad de memoria (la mayor parte) está más o menos bajo control ya que, los textos, lo que más ocupa, están definidos de forma que se conoce lo que ocupan en tiempo de ejecución, así que el riesgo de necesitar más memoria que la disponible es razonablemente bajo.
Asignar la memoria necesaria de forma dinámica para dimensiones variables es mejor que reservar espacios fijos iguales para datos de longitud variable (por ejemplo, en matrices para textos de diferente longitud), así que no se puede decir que la asignación dinámica de memoria sea mala, en todo caso, que hay que minimizar sus riesgos usándola con cuidado.
Gracias por participar en polaridad.es
Carlos CC
Muchas gracias tu rápida respuesta. Tengo otra duda ¿Es mejor unsigned char que byte?
Víctor Ventura
Hola, Carlos.
En Arduino es igual un tipo
byte
que un tipounsigned char
, de hecho es más ortodoxo desde el punto de vista de Arduino usarbyte
porque está pensado para almacenar enteros sin signo de 8 bits (es decir, un byte) mientras quechar
está relacionado con referencias a caracteres. Yo utilizo muchas vecesunsigned char
para poder hacer pruebas compilando una aplicación para la consola de un sistema operativo de escritorio y que funcionen igual en Arduino; seguramente debería cambiarlos luego abyte
para evitar confusiones.Programando para MCU también encontrarás
uint8_t
que es una forma aún más correcta de referirse a un número entero sin signo de 8 bits, eligiendo expresamente lo que ocupa el dato, en lugar de permitir que sea el lenguaje el que «elija», ya que en un contexto de pocos recursos, sistemas basados en MCU, es una cuestión crítica.Gracias por participar en polaridad.es
JotaCe
por favor, podría poner un enlace para descargar la librería
Víctor Ventura
Hola, JotaCe.
No puedo porque no hay librería 🙁
El artículo es más para explicar cómo se hace que para dar una solución cerrada para hacerlo. De todas formas, los ejemplos de ambas implementaciones (con matrices y con estructuras) son funcionales; puedes copiar el código, cambiar los datos (al menos de tu punto de acceso y de tu servidor, pero también la petición HTTP que quieras hacerle) y te servirá.
Sigue leyendo polaridad.es, de unos días tendrás más o menos lo que quieres, estoy trabajando en el uso de un código (más o menos) como este en forma de librería, para un uso concreto también funcional, que explica como implementarlo para otros usos además de las peticiones HTTP.
Saludos.
Oscar Flores
Buenas noches, para empezar me encanta tu página y la manera en la explicas los temas, haciendo una fusión entre hardware y software. Mi comentario o pregunta es la siguiente ¿por que no usar directamente las bondades de este módulo cargando el firmware de NodeMCU? de esta manera ya no tenemos que usarlo como «interfaz tonta» haciendo uso de un segundo sistema microcontrolado, y así poder usar gran parte de su potencial como micro-controlador y sistema conectado SoC. En lo personal he tenido proyectos satisfactorios usando este módulo en su versión de tarjeta de desarrollo (http://nodemcu.com/index_en.html). Sigue con el buen trabajo de esta página, que muchos tomamos de referencia para seguir aprendiendo.
Saludos desde La Paz, B.C.S., México
Víctor Ventura
Hola, Oscar.
Por supuesto, coincido contigo, un módulo wifi ESP8266 que tenga suficientes recursos se puede utilizar sin otro MCU (algo he escrito en este blog hablando sobre los tipos de módulos wifi ESP8266) Incluso, en tu línea de opinión, yo pienso que su uso como MCU principal debería ser la primera opción a considerar en un proyecto wifi (buena parte de los proyectos IoT).
La cuestión que explica por qué existe la necesidad de utilizar el ESP8266 como «periférico» en lugar de utilizar su «inteligencia» es que dispone de pocos GPIO (pocas conexiones, en general) hasta el punto de que algunos módulos incluyen otros MCU para proporcionárselas: justo el caso contrario del debate que abres.
El tipo de módulo wifi ESP8266 más utilizado todavía es el ESP-01 que, por su formato, prácticamente es obligado usarlo como periférico por medio de órdenes AT. Por otra parte, habrás visto que uno de los objetivos del blog es, más incluso que la divulgación, la ayuda en la formación; así que me parecía interesante empezar por hablar de este uso, y todavía me quedan algunos artículos que publicar al respecto para concluir ese objetivo, antes de tratar el uso que propones, del que espero poder hablar también en breve.
Muchas gracias por participar en polaridad.es y muchas gracias también por tu valiosa aportación ¡Por favor, que no sea la última! 🙂
PD
Me ha parecido muy interesante tu proyecto empresarial http://flovid.com.mx y la web con que lo presentas.
Bryam
hola victor
{ if ( W.indexOf(«P32») > 0 ) con esta linea de codigo identifico que si el texto P32 se encuentra en string!!
pero como puedo guardar el numero 32 en una variable entera para poder utilizar el pwm en el arduino.
+IPD,0,349:GET /P32 HTTP/1.1 este es el codigo que entra a la consola desde el esp 8266.
de antemano te lo agradesco mucho si me puedes ayudar.
Víctor Ventura
Hola, Bryam.
Por ejemplo, con
Substring
puedes obtener la parte del texto que necesites.Una vez que «aisles» el texto que contiene el valor puedes usar
toInt
para conseguir un número entero.Saludos.
alberto
estoy usando el modulo wifi con arduino y envio datos por wifi al modulo esp8266 , pero por mas bytes que le envio siempre recibo no mas 64 bytes, tienes idea porque