Operações básicas em um módulo wifi ESP8266 do Arduino
Quando Expressivo lançou os primeiros módulos do mercado wi-fi com o integrado ESP8266 e pelo firmware para lidar com isso usando comandos AT, o que nos interessava era integrá-lo em assemblies com microcontroladores e os problemas foram reduzidos a conhecer o (anteriormente) escuro Tabela de comando ESP8266 AT, necessidades alimentares ou Atualização de firmware ESP8266.
Então rapidamente surgiram alternativas para programar o ESP8266 e implementações de módulos wi-fi de formatos muito diferentes que suscitaram outras preocupações: qual módulo wifi ESP8266 escolher dependendo do alcance das diferentes antenas (inclusive externas) ou da integração física destes novos módulos em nossos conjuntos.
Certamente, devido a todas estas mudanças, a ênfase pode não ter sido colocada nos aspectos mais básicos, na gestão mais básica do Módulo wi-fi ESP8266. Apesar polaridade.es Você pode encontrar informações sobre o uso do ESP8266 e existem algumas aplicações que pretendem explicar de forma genérica o funcionamento do Módulo wi-fi ESP8266 usando comandos AT, especialmente no artigo sobre biblioteca para fazer consultas HTTP do Arduino com o módulo wifi ESP8266, as impressões dos leitores sugerem que seria útil adicionar mais algumas informações básicas para ajudar os usuários do ESP8266 para realizar suas próprias implementações.
Discuta as operações básicas para trabalhar com o ESP8266 e propor soluções genéricas é um objetivo de diversas partes; Para ajudar a acompanhar o conteúdo do artigo, o seguinte índice pode servir de guia:
- Controle o módulo wifi ESP8266 do computador através da porta serial
- Atualizar firmware com esptool
- Envie pedidos para o módulo
- Receba dados do ESP8266
- Analise a resposta pesquisando textos no conteúdo
- Limite o tempo de espera para receber a resposta
- Execute uma operação complexa definida por vários comandos AT
Controle o módulo wifi ESP8266 do computador através da porta serial
De um prato Arduino e usando seu IDE é possível monitorar o funcionamento de um Módulo wi-fi ESP8266, envie o Comandos ESP8266 AT e veja a resposta, mas é muito mais conveniente fazê-lo em um computador com um aplicativo do tipo terminal.
Dependendo de qual placa Arduino usado, apenas uma porta serial de hardware pode estar disponível, o que adiciona um pouco de inconveniência ao envio e recebimento. Alterar a velocidade das comunicações é muito mais confortável em uma aplicação de comunicação serial a partir de um computador e de algumas placas-mãe. Arduino (e em algumas circunstâncias) não suportam bem as velocidades mais altas de comunicações seriais, especialmente 115200 baud, que é a velocidade padrão das versões mais recentes do firmware.
em Qual programa usar para monitorar o ESP8266 usando porta serial, há muitos para escolher de acordo com necessidades e preferências; ultimamente estou usando mais o clássico CuteCom (aquele na imagem acima) porque é muito confortável para mim repetir certos Módulo wifi ESP8266 em pedidos nos testes do projeto.
Algumas recomendações já foram dadas aqui sobre programas que funcionam como console serial; Por exemplo, ao falar sobre PuTTY para controlar dispositivos seriais UART a partir do computador. PuTTYAlém de ser um excelente aplicativo, está disponível para a maioria dos sistemas operacionais de desktop. Além disso, como PuTTY pode ser usado para atuar como um console com a porta serial e o Família de protocolos da Internet (TCP/IP), incluindo aqueles que operam em TLS, torna-se uma ferramenta comum que mais do que compensa o (pouco) tempo gasto para configurá-la e se acostumar com seu uso.
Além do software de comunicação serial, para conectar o Módulo wi-fi ESP8266 para o porto USB Um computador também requer um conversor USB para a série TTL. Tal como acontece com o software, existem várias versões, das quais servem apenas para converter a porta USB em uma porta serial TTL (que podem ser obtidos a partir de um euro) até aqueles que podem emular diferentes protocolos (como SPI o I2C).
Assim como um programa que funciona como um console serial, o hardware para comunicar o computador via USB com um circuito lógico (não apenas o ESP8266) será uma ferramenta comum no trabalho de um desenvolvedor de aplicações microcontroladas, vale a pena tê-la na caixa de ferramentas o mais rápido possível e trabalhar com ela Módulo wi-fi ESP8266 É uma excelente oportunidade para conseguir um.
O conversor USB a UART TTL Também pode ser usado para monitorar o comportamento de um circuito que utiliza o ESP8266, para isso, as saídas que se deseja monitorar são conectadas em série à entrada de dados (RX) do conversor com um diodo rápido (o 1N4148, por exemplo) e um resistor (2K2, por exemplo) em paralelo entre si. Essa configuração funciona como um sniffer serial de hardware.
Embora o farejador da imagem acima seja certamente rudimentar (entre outras coisas, não possui amortecer) é suficiente para monitorar a operação de uma montagem com Arduino e pelo ESP8266.
Removendo o sniffer do esquema anterior, o esquema mostrando como conectar um Módulo wi-fi ESP8266 para um prato Arduino. Além de alimentá-lo em 3V3, o pino de reset e o pino de ativação do integrado devem estar conectados em nível alto (habilitar). Claro, o pino RX de um deve se conectar ao TX do outro.
Para simplificar o diagrama anterior, foi representada uma placa Arduino alimentado em 3V3 e para o qual a tensão na porta serial também é assumida como 3V3. Se você usar um microcontrolador será necessário um nível de sinal diferente na porta serial (normalmente 5 V), para não danificar o ESP8266, use um conversor de nível como aqueles nos diagramas abaixo. Este circuito é freqüentemente encontrado em muitas implementações comerciais de módulos prontos para uso.
Atualizar firmware ESP8266
Os Comandos ESP8266 AT, seu término, a velocidade padrão do módulo... dependem da versão do Firmware ESP8266. É melhor garantir que você tenha a mesma versão em todos os módulos e, se possível, que seja a versão mais recente.
Infelizmente, a maior parte Modelos de módulo wi-fi ESP8266 Eles têm apenas 4 Mbit, portanto a versão mais recente não pode ser instalada neles. A versão mais recente (oficial) do firmware que pode ser instalada em Módulos wi-fi ESP8266 com 4 Mbit (a maioria) é 0.9.4 que inclui a versão 0.2 do Comandos ESP8266 AT.
Em resumo, para atualizar o firmware você precisa:
-
Baixe a versão de firmware correspondente. o versão mais recente (oficial) para um módulo com 4Mbit de memória, encontrada na pasta Espressif no github. No Site Expressif Você pode baixar a versão mais recente do firmware, mas é muito importante verificar se o módulo no qual está instalado possui memória suficiente.
-
Baixe a versão mais recente da ferramenta de instalação de firmware. Meu favorito é roubar que está escrito em Python, então funciona em qualquer plataforma. Além de ser baixado, também pode ser instalado com
pip install esptool
(opip2
opython -m pip
…). Claro, Expressivo Ele também oferece sua própria ferramenta, mas atualmente está disponível apenas para Windows. -
Preparar arquivos baixados; descompacte-os em uma pasta acessível e, se necessário, torne a ferramenta executável roubar, no meu caso, já que GNU / Linux, com
chmod +x esptool
-
Conecte o módulo ao computador usando um conversor USB UART TTL que funciona em 3V3 ou use um conversor de nível se funcionar em 5 V. Além da alimentação, você terá que conectar TX a RX do conversor USB UART TTL, RX para TX, GPIO0 em baixo nível (GND) e talvez GPIO2 em alto nível (em meus testes funcionou tanto conectando em baixo nível quanto desconectando). Caso o módulo esteja com a conexão GPIO15 livre (como ocorre no ESP-12) ele deve ser conectado em nível baixo. O RESET, que normalmente estaria em nível alto durante a operação, pode ser deixado desconectado ou conectado em nível alto por meio de um resistor (10K, por exemplo), pois antes de iniciar a gravação pode ser necessário reiniciar o dispositivo conectando-o para um nível baixo.
Ao ligar o módulo, ele estará disponível para atualização, mas, Se for exibido um erro de conexão, será necessário redefini-lo conectando RESET em nível baixo por um instante e depois deixando no ar (sem conectar) para o processo de atualização.
O módulo tem picos de consumo de meio ampere (até 600 mA, segundo alguns usuários) por isso é importante utilizar uma fonte de alimentação capaz de suportar esse consumo, principalmente para atualização de firmware. -
Execute a ferramenta para atualizar o firmware. No meu caso, salvei os documentos da ferramenta e do firmware na etapa 3 na mesma pasta, então executo a partir do console:
cd ~/Datos/firmwareESP8266
(mude para a pasta que contém a ferramenta e o 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
define a velocidade do ESP8266 (115200 baud no meu caso) e--port
a porta serial à qual ele se conecta (no meu caso, emulado, o primeiro USB). Os diferentes documentos que compõem o firmware ficam atráswrite_flash
precedido pelo endereço, com o documento user1.bin contendo a carga de atualização.
Envie comandos para o módulo wifi ESP8266
Para controlar o ESP8266 de um computador teremos que começar com configurar o aplicativo para isso bastará ① escolher a porta à qual o conversor está conectado USB UART TTL, algo parecido /dev/USB0
em GNU/Linux e similares ou algo parecido COM6
no Windows, ② escolha a velocidade com que o ESP8266, provavelmente 115200 baud, ③ definir 8 bits de dados mais um bit de parada, sem paridade ou handshake, e ④ definir fim de linha, dependendo do firmware, quase sempre CR+LF.
Depois que o aplicativo for configurado (ou, quando apropriado, armazenado e selecionado), ele será abra a conexão ("open device" e "open", respectivamente, nas capturas de tela dos exemplos acima com CuteCom y PuTTY) e você pode começar a enviar pedidos para ESP8266.
Como pode ser visto no Tabela de comando ESP8266 AT, o formato para ativar, desativar, definir um valor e consultá-lo é bastante previsível, mas em geral não é fácil lembrar de todos e provavelmente você precisará tê-lo em mãos para consultá-lo.
O caminho de enviar Em pedidos al Módulo wi-fi ESP8266 de Arduino é muito simples: ① configurar comunicações com Serial.begin(115200);
(ou Serial1, Serial2… em placas com diversas portas seriais de hardware) e ② enviar os comandos usando o 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()
{
}
|
O exemplo acima mostra como enviar o Módulo wifi ESP8266 em pedidos de Arduino. Neste caso é ilustrado AT+CWJAP
, que é usado para conectar-se a um ponto de acesso. Este comando usa como argumentos o identificador do ponto de acesso (SSID) e a chave, ambas entre aspas, para que se tornem um objeto Srtring
e coloque-os entre aspas usando o código de escape (\"
). Para concluir o pedido, use \r\n
que corresponde a CR
y LF
.
Lembrando que a porta serial nem sempre é identificada com Serial
(em certas placas pode ser Serial1
, Serial2
…) o objeto de porta utilizado foi definido atribuindo-o à macro PUERTO_SERIE
. Detectar o tipo de placa usada pode adicionar um pouco de inteligência à seleção da porta serial; Mais tarde veremos como você pode descobrir o tipo de Arduino. As restantes definições são as habituais que permitem "nomear" os valores constantes para evitar repeti-los (e cometer erros) e facilitar a sua alteração.
O exemplo acima deve conectar o Módulo wi-fi ESP8266 ao ponto de acesso indicado, mas já estava conectado antes? A conexão funcionou? Para saber, precisamos “ouvir” o que o ESP8266
Receba dados do módulo wifi ESP8266
Ao conectar o sniffer de dados explicado acima ao computador, você pode ver o que Arduino enviou o ESP8266 e sua resposta. Para ler de Arduino e processar as informações nele contidas, será necessário detectar com Serial.available()
se algum dado chegou e, em caso afirmativo, carregue-o com Serial.read()
. O exemplo a seguir mostra como ler a resposta de AT+CWJAP?
, que informará se há conexão com algum ponto de acesso.
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 em um prato Arduino Uno (e em outros) abrir o monitor serial redefine o programa, pode ser usado para ver no console serial Arduino as informações que você envia para ESP8266 como mostra a captura de tela da imagem abaixo.
Analise a resposta enviada pelo módulo wifi ESP8266
Já vimos como ler as informações que chegam Arduino do ESP8266. O problema que você tem que enfrentar é que você não sabe quando vai começar a chegar, quanto tempo vai demorar para chegar, qual será a duração... e não é muito eficiente esperar a resposta do ESP8266 é recebido sem deixar o microcontrolador enquanto isso, execute outras tarefas.
Uma maneira simples de gerenciar essa circunstância é iterar sobre os dados recebidos em busca de respostas concretas com o qual, por exemplo, ativar indicadores (sinalizadores ou variáveis booleanas) que determinarão se deve continuar a pesquisa no texto recebido e quais ações devem ser realizadas com base nas informações que chegam do ESP8266. Enquanto a resposta chega microcontrolador pode se dedicar a outras tarefas, por exemplo, recebendo dados de sensores e processando-os.
Procure um texto nas informações recebidas do ESP8266
Para pesquisar o texto que vem do ESP8266 pode ser compare cada carta recebida com aquela que corresponde à mensagem que você procura. Será necessário utilizar um contador (ou ponteiro) que aponte para a letra a ser comparada; Se o personagem que chega do ESP8266 for igual ao que está sendo examinado na mensagem, o contador avança, se for diferente é inicializado.
Para saber que o fim foi alcançado, consulta-se o próximo caractere da mensagem pesquisada, que será zero (\0
) ou o comprimento da mensagem é armazenado para, comparando-a com o contador, saber se a comparação terminou e, portanto, o Módulo wi-fi ESP8266 enviou a mensagem desejada.
O exemplo a seguir usa o comando AT+CWLAP
que retornará uma lista de pontos de acesso e dentro deles será pesquisado um chamado "wifi polaridad.es". Embora tenhamos optado por verificar se o último caractere é zero, como o amortecer Ele armazena apenas o texto pesquisado e seu comprimento é conhecido, também pode ser verificado se esse número de letras corretas foi recebido. Com um CONDUZIU conectado ao pino 2 é relatado que o texto esperado foi encontrado.
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
}
}
}
}
|
No código do exemplo anterior você também pode ver uma maneira de escolha a porta serial dependendo do tipo de placa Arduino usado. Este exemplo pressupõe que você tenha três tipos de placas para o projeto: uma Arduino Uno, uma arduino mega 2560 e um Arduino Leonardo. Se você trabalha com um Arduino Uno ele será usado Serial
e caso contrário Serial1
.
Se você trabalha com um prato Arduino Leonardo Você pode usar o mesmo método para parar o programa e aguardar o console (a porta serial associada ao Serial
) está disponível.
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
|
Pesquise vários textos na resposta do ESP8266
O código do exemplo anterior é utilizado para buscar texto nas informações enviadas pelo ESP8266 mas a resposta pode incluir informações diferentes dependendo da operação. Suponha, para começar com um caso simples no próximo exemplo, que o texto enviado pelo MCU ESP8266 es OK
quando a operação é realizada corretamente e ERROR
Caso contrário, como acontece com o pedido AT+CWJAP?
, que serve para verificar se o Módulo wi-fi ESP8266 já está conectado a um ponto de acesso.
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 nova implementação do mesmo método, que procura uma correspondência com várias mensagens possíveis, permite escolher entre diferentes ações dependendo da resposta recebida do ESP8266, basta ligar o CONDUZIU isso corresponde.
Limite o tempo que leva para receber uma resposta
Até agora não foi feita qualquer referência a uma questão relevante: a tempo máximo de espera (timeout) antes de considerar que uma operação falhou. Se por algum motivo a conexão com o Módulo wi-fi ESP8266, o módulo com o ponto de acesso, o ponto de acesso com Internet ou, por exemplo, um hipotético servidor não estiver disponível, o programa poderá ficar bloqueado num ponto aguardando indefinidamente, pelo que terá de ser articulada uma resposta a tais circunstâncias. O tempo máximo de espera pode ser configurado para toda a aplicação, normalmente será mais “generoso” nesse caso, ou podem ser programados tempos de espera individuais para cada operação.
Para verificar se (pelo menos) um determinado intervalo de tempo passou O “tempo” do momento em que a conta é iniciada costuma ser subtraído do “tempo” atual e verifica-se que a diferença é maior que o limite desejado. Este “tempo” não precisa ser em tempo real, normalmente corresponde ao intervalo que passou desde o MCU comece a contar o tempo; Isto não afeta o programa, pois o interessante é o tempo decorrido e não o tempo absoluto.
Normalmente, para verificar se decorreu determinado intervalo, utiliza-se uma expressão do tipo:
1
|
(unsigned long)(millis()–milisegundos_al_empezar)>intervalo_de_tiempo
|
A variável milisegundos_al_empezar
contém o valor de millis()
de um determinado momento da execução a partir do qual é cronometrado, por isso não é incomum que seu nome se refira à palavra “cronômetro”. A variável intervalo_de_tiempo
contém o número máximo de milissegundos que torna verdadeira a expressão anterior, ou seja, representa o tempo limite; Geralmente é uma constante (ou uma macro) e, como no caso anterior, a palavra “TIMEOUT” aparece frequentemente em seu nome. Se você trabalha com intervalos muito curtos, você pode usar micros()
em vez de millis()
(microssegundos em vez de milissegundos), embora seja muito menos comum e muito menos preciso.
1
|
(unsigned long)(millis()–cronometro)>TIMEOUT
|
Um inteiro longo em Arduino (unsigned long
) ocupa 4 bytes (32 bits), então o maior valor que ele pode representar é 4294967295 (2 elevado a 32 menos um, porque começa em zero). num prato Arduino Durante a execução contínua, o contador de milissegundos será redefinido (retornará a zero) aproximadamente a cada 50 dias. Ao subtrair com tipos de dados não assinados, o mesmo comportamento é reproduzido (invertendo o contador), portanto é viável controlar o tempo limite indefinidamente.
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;
}
}
}
|
O código acima mostra um implementação muito básica de limitação de tempo limite incorporando as linhas marcadas em relação ao exemplo que o precede. Como a verificação do timeout é realizada após o processamento dos dados que chegam do Módulo wi-fi ESP8266, a operação pode ser considerada bem-sucedida mesmo que a recepção demore mais que o tempo de espera imposto.
Execute uma operação complexa definida por vários comandos AT
Para ter um exemplo de referência da finalidade do aplicativo que explora o Módulo wi-fi ESP8266, suponha que seja armazenar informações em um banco de dados acessado através de um serviço web para acompanhar a temperatura. O código a seguir lê um sensor conectado a uma entrada analógica a cada determinado intervalo de tempo, calcula o valor médio e, após um intervalo de tempo maior, envia para o servidor web (estilo Internet das coisas) através de um solicitar HTTP (POSTAR, OBTER…).
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”);
}
}
|
Neste exemplo de registro de temperatura, um servidor web é acessado a cada cinco minutos. Embora a disponibilidade não seja particularmente elevada, era de esperar que a proposta funcionasse, mas se fosse necessária uma frequência de gravação mais elevada, outros recursos teriam que ser implementados, por exemplo, um buffer de dados esperando para ser enviado, para enviar vários quando o servidor puder atender e armazená-los para quando não estiver disponível. Se a frequência com que os dados precisam ser registrados fosse ainda maior, outros tipos de protocolos teriam que ser propostos como alternativa ao HTTP ou até mesmo substituir TCP por UDP ser capaz de enviar a maioria dos dados na velocidade necessária, mesmo ao custo de perder alguns.
As operações que compõem a tarefa a ser realizada para enviar a temperatura seriam:
- Reinicialize o módulo wi-fi
- Desconecte-se do ponto de acesso atual (caso exista uma conexão padrão)
- Defina as configurações. Para o exemplo, assume-se que o modo de conexão (simples) e o papel nas comunicações Wi-Fi (estação) devem ser configurados.
- Conecte-se ao ponto de acesso
- Verifique se a conexão está correta (na verdade, este é o ponto de entrada) Se não houver conexão, inicie o processo desde o início
- Conecte-se ao servidor
- Envie a solicitação HTTP com os dados a serem armazenados
A ordem das operações não precisa ser exatamente assim (embora a operação seja) e cada etapa pode exigir vários Comandos ESP8266 ATPor exemplo, a configuração listada acima precisaria de dois: AT+CIPMUX=0
y AT+CWMODE=1
.
Uma estrutura de dados para representar operações no ESP8266
Nos exemplos anteriores, embora de forma muito básica, já é sugerida uma solução genérica para o problema: utilizar uma estrutura de dados que armazene as possíveis respostas e as ações que devem ser tomadas em cada caso; envie uma ação, aguarde uma resposta e proceda de acordo com o que a resposta significa. Como cada operação complexa exigirá vários Comandos ESP8266 AT, a estrutura de dados deve vincular uma operação a outras, subsequentes ou anteriores, que devem ser realizadas em cada caso dependendo da resposta do ESP8266.
Nos exemplos anteriores, uma mensagem foi pesquisada dentro da resposta do ESP8266 e foi interpretado como sucesso ou erro. Além de uma recepção (e análise) de todo o texto recebido, Para ter um mínimo genérico, é aconselhável atender também ao preenchimento da mensagem ou, em outras palavras, à disponibilidade do Módulo wi-fi ESP8266 para receber novos pedidos. Desta forma, a mudança para um estado que poderíamos chamar, por exemplo, de “wifi disponível”, poderia ser o recebimento do nome do ponto de acesso e o recebimento do texto ERROR
ou o texto OK
significaria que o ESP8266 você terminou a resposta e agora pode enviar a próxima Comando AT para 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;
|
O código acima usa um vetor (operacion
) para armazenar o texto das operações sucessivas que formam a tarefa completa. Uma matriz bidimensional é usada (mensaje
) com as três respostas que são analisadas. Conforme explicado acima, é necessário buscar as mensagens que representam o final da resposta além da mensagem que representa uma resposta correta ou incorreta. Nem todas as operações terão o mesmo número de respostas possíveis; Quando há menos respostas, pode-se utilizar uma mensagem vazia que consuma o menor número possível de ciclos em sua análise (mesmo assim, não é a forma mais ideal). Logicamente será necessário que o número mínimo de respostas buscadas (três no exemplo) inclua todas as possibilidades de atuação, mesmo que nem todas sejam possíveis.
Ao falar sobre as possíveis respostas, já se percebe que este exemplo não é muito útil para receber dados com formato arbitrário de um Módulo wi-fi ESP8266, mas o fato é que, no contexto de uso com microcontroladores não é usual; O mais comum é enviar dados coletados pelos sensores conectados e/ou receber informações sobre o que fazer com os atuadores que controla. Informações muito valiosas, que podem ser muito bem previstas.
Na estrutura de dados anterior, assim como é feito para expressar as possíveis respostas que são analisadas, também é utilizada uma matriz bidimensional para determinar a operação que deve ser realizada em cada caso (siguiente_operacion
). Especificamente, optamos por responder a três tipos de mensagens: ① um texto arbitrário (LITERAL
) para verificar se há conexão com o ponto de acesso Wi-Fi e o servidor, ② um texto para detectar erros no processo (FALLO
) e ③ um texto indicando que a operação foi concluída com sucesso (ACIERTO
).
Finalmente, existem mais dois vetores para definir o tempo máximo de espera antes de desistir (timeout
) e especifique (configuracion
) se a operação terminar sem esperar por uma resposta (ESPERAR_RESPUESTA
) e mensagens indicando o fim da comunicação. Este último vetor, para ilustrar um exemplo de como a memória pode ser salva, funciona com os bits de um byte de configuração para indicar os diferentes estados.
O primeiro Comandos ESP8266 AT da estrutura de dados sempre esperam uma resposta, que pode ser uma mensagem de sucesso ou de erro. Quando ocorre um erro, o módulo é reiniciado e inicia novamente e se a mensagem indicar que a operação está correta, passa para a próxima.
Quando você se conecta ao servidor, o padrão muda. Neste caso é necessário ① enviar o comprimento do pacote de dados a ser transmitido e ② redigir a solicitação HTTP com um texto fixo mais o valor (da temperatura) que é enviado para ser armazenado no servidor. A preparação destes dados é realizada em cada envio e é necessário dividi-los em dois (avisar o comprimento) ou três (enviar a solicitação HTTP) Para ESP8266 EM pedido. Apenas a última das partes em que a operação está dividida aguardará resposta.
Neste caso funcionará sem problemas (talvez avisando que o módulo está ocupado) mas quando o comprimento dos dados for maior será necessário dividir os blocos de dados em pedaços menores e pode até ser necessário implementar uma espera, como é feito com a leitura da temperatura, para dar tempo ao módulo de enviar os dados sem preencher seu amortecer.
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();
|
Juntamente com outras macros que já foram explicadas anteriormente, o código de exemplo acima mostra como são definidos os diferentes estados para especificar se deve-se esperar por uma resposta e, se for o caso, qual mensagem indica que ela foi concluída.
Como em diferentes pontos do código será enviada uma operação (quando chegar a hora de enviar a temperatura média, se o tempo de espera de uma operação for ultrapassado, quando a operação atual for concluída com sucesso...) mas como fazer isso é estabelecido globalmente, foi definido um macro ENVIAR_OPERACION
que agrupa as etapas envolvidas no envio.
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();
|
A seguir está o código do programa principal do exemplo. A tarefa mais externa é a responsável por amostrar a temperatura para calcular a média e, a cada determinado período de tempo, ela é enviada ao servidor através do Módulo wi-fi ESP8266. Uma vez enviada cada operação, a resposta é analisada para determinar qual é a próxima ou se a tarefa de envio de informações foi concluída.
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
}
}
}
|
Logicamente, diversas ações de otimização podem ser realizadas no código anterior mas, como este é um exemplo para entender como o ESP8266 De forma genérica, vale apenas focar em alguns aspectos, sendo o primeiro a estrutura de dados. Parece que o lógico é usar uma estrutura de dados de linguagem de programação (struct
) para representar a informação que está sendo processada: o Comandos ESP8266 AT e as mensagens que são analisadas.
Use uma estrutura (struct
) armazenar os dados em vez dos arrays de exemplo (baseados neles) é trivial e, embora possa resultar em um código mais elegante, não implica nenhuma melhoria no resultado. A verdadeira alternativa apresentada pelo uso de struct
é implementar, conforme explicado abaixo, comprimentos variáveis em estruturas que contêm dados “internos” que são referidos por eles. Dessa forma, por exemplo, não seria necessário que uma operação tivesse um número fixo de respostas para analisar.
Esta abordagem sugere que é a melhor forma de implementar a solução, mas a desvantagem é que seria necessário usar alocação dinâmica de memória, uma prática arriscada trabalhando com um microcontrolador o que requer uma medição cuidadosa de quanta memória será usada em tempo de execução, pois dificilmente o compilador conseguirá nos avisar sobre isso e existe certa possibilidade de esgotar a memória (ou a pilha) com consequências fatais para a execução do programa.
Na linha de otimização do código, é interessante lembrar que, em um programa desse tipo, que utiliza uma grande quantidade de texto, pode economizar espaço de memória SRAM armazenando strings de texto na memória do programa (chamada de conferência) com a macro F()
. Nas capturas de tela a seguir você pode ver os diferentes programas e distribuição dinâmica de memória com uso normal de texto e usando a macro F()
.
Com relação às ações que são executadas de acordo com as informações que chegam do Módulo wi-fi ESP8266, como alternativa à verificação da mensagem do código e realizar uma ou outra de acordo com o que é recebido, podem ser armazenados nesta estrutura de dados ponteiros para funções que executam cada tarefa em vez de indicadores de status (flags) que alertam sobre um determinado estado que a aplicação é responsável por gerenciar, por exemplo, dentro do loop principal.
A seguir está um exemplo de estruturas para armazenar os dados das solicitações ao ESP8266 (o tipo de dados operacion_esp8266
) e suas respostas (o tipo de dados 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 a estrutura que representa a operação (os dados que são enviados para o Módulo wi-fi ESP8266) refere-se à estrutura com a qual as respostas são definidas, e a estrutura das respostas à estrutura das operações, é necessário declarar ambos primeiro, definindo o novo tipo de dados e, em seguida, definindo seu conteúdo.
O exemplo anterior considera que o programa que o inclui optou por utilizar um Indicador de status, que deve corresponder a uma variável acessível a partir do código responsável por realizar uma ou outras operações indicadas pelo referido valor. Se na resposta de ESP8266 Quando um determinado texto é analisado, o estado assume o valor que indica a estrutura da resposta correspondente.
Como dito anteriormente, outra alternativa, seja para substituir ou complementar um indicador de status, seria armazenar uma função na estrutura de referência (um ponteiro) que seria chamado ao encontrar determinado texto na resposta do Módulo wi-fi 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;
|
No exemplo anterior, ele foi adicionado à estrutura de dados usada para processar a resposta do Módulo wi-fi ESP8266 um ponteiro para uma (suposta) função que retorna um dado do tipo float
(pode ser o valor ponderado de uma leitura analógica) e para o qual dois bytes são fornecidos como argumentos (dois unsigned char
que poderia ser o pino a partir do qual se lê a entrada analógica e aquele que ativa o ENABLE de um integrado hipotético).
Em desenvolvimento para MCU, ao contrário do que ocorre no estilo de desenvolvimento para sistemas maiores, não é tão incomum a utilização de variáveis globais na definição do comportamento (global) da aplicação que controla um assembly, portanto não será especialmente raro encontrar este tipo de definições como funções sem parâmetros e que não retornam valores, algo como void (*accion)();
Se você trabalha com essa forma de representar os dados, usando struct
de dados de comprimento variável, será necessário alocar memória dinamicamente com malloc()
(o new()
, se forem usados objetos), que usará a quantidade de memória alocada como parâmetro e retornará um ponteiro para o início da área de memória que está reservada. Com sizeof()
Sobre o tipo que está armazenado, multiplicado pelo número de elementos utilizados, você pode obter a quantidade de memória necessária. Um exemplo com e sem uso pode ser visto nas imagens abaixo. malloc()
; Cuidado com a memória utilizada pelo programa, no primeiro caso, é necessário carregar a biblioteca que contém esta função.
Se as operações no Módulo wi-fi ESP8266 irá variar ao longo da execução do programa, será necessário liberar a memória que não é utilizada com free()
(o delete()
, no caso de serem objetos). Embora seja razoável esperar que o compilador (GCC) otimizará o programa para evitar particionamento de memória, certamente o desempenho não será tão ideal quanto trabalhar com memória alocada estaticamente.
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
}
}
}
|
Embora neste exemplo (em ambas as implementações) não faça muito sentido, para generalizar a operação para poder aplicá-la a outros casos, deve-se notar que O envio dos dados repete sempre o mesmo protocolo: informe a quantidade de bytes que serão enviados, aguarde o indicador (>) e envie os dados.
Como neste exemplo é utilizado apenas uma vez (toda a requisição é feita em um pacote), não parece muito útil mas, em geral, pode ser necessário realizar vários envios na mesma operação, inclusive casos em que devem ser transmitidas quantidades significativas de dados que devem ser fragmentados para evitar transbordamento da memória do ESP8266.
Para implementar este comportamento, os dois últimos elementos da conexão podem ser utilizados para que cada vez que os dados sejam enviados, os dados sejam preenchidos com os valores correspondentes: no primeiro caso, o número de bytes enviados e no segundo, o ( parte da solicitação a ser transmitida.
Para repetir a atribuição e envio dos diferentes elementos que devem ser transmitidos podem ser armazenados em um vetor. Este novo vetor será o que determinará o fim da operação complexa e não a última operação como até agora.
comentário 1