Основни операции на wifi модул ESP8266 от Arduino
Когато Еспресиф пусна първите модули на пазара WiFi с интегрирания ESP8266 и фърмуер с които да го обработваме с помощта на AT команди, това, от което се интересувахме потребителите, беше да го интегрираме в сборки микроконтролери и проблемите бяха сведени до познаване на (предишната) тъмнина ESP8266 AT командна таблица, хранителни нужди или Актуализация на фърмуера ESP8266.
Тогава бързо се появиха алтернативи за програмиране на ESP8266 и модулни реализации WiFi на много различни формати, които повдигнаха други опасения: кой wifi модул ESP8266 да изберете в зависимост от обхвата на различните антени (включително външни) или физическото интегриране на тези нови модули в нашите модули.
Със сигурност, поради всички тези промени, акцентът може да не е поставен върху най-основните аспекти, най-основното управление на ESP8266 wifi модул. Макар че полярност.es Можете да намерите информация за употребата на ESP8266 и има някои приложения, предназначени да обяснят по общ начин работата на ESP8266 wifi модул използване на AT команди, особено в статията за библиотека за извършване на HTTP заявки от Arduino с wifi модула ESP8266, впечатленията на читателите предполагат, че би било полезно да се добави още малко основна информация, за да помогне на потребителите на ESP8266 да извършват свои собствени реализации.
Обсъдете основните операции за работа с ESP8266 и предлагането на общи решения е цел от няколко много различни части; За да ви помогне да следвате съдържанието на статията, следният индекс може да служи като ръководство:
- Контролирайте wifi модула ESP8266 от компютъра през серийния порт
- Актуализирайте фърмуера с esptool
- Изпращане на поръчки към модула
- Получавайте данни от ESP8266
- Анализирайте отговора, като търсите текстове в съдържанието
- Ограничете времето за изчакване за получаване на отговор
- Изпълнете сложна операция, дефинирана от множество AT команди
Контролирайте wifi модула ESP8266 от компютъра през серийния порт
От чиния Arduino и използвайки вашия IDE възможно е да се наблюдава работата на a ESP8266 wifi модул, изпрати ESP8266 AT команди и вижте отговора, но е много по-удобно да го направите от компютър с приложение от терминален тип.
Зависи на коя дъска Arduino използван, може да е наличен само един хардуерен сериен порт, което добавя малко неудобство към изпращането и получаването. Промяната на скоростта на комуникация е много по-удобна в приложение за серийна комуникация от компютър и някои дънни платки. Arduino (и при някои обстоятелства) не поддържат добре по-високите скорости на серийни комуникации, особено 115200 бода, което е скоростта по подразбиране на най-новите версии на фърмуер.
За Каква програма да използвате за наблюдение на ESP8266 използвайки серийния порт, има много за избор според нуждите и предпочитанията; напоследък използвам повече класическия CuteCom (този на екранната снимка по-горе), защото ми е много удобно да повтарям някои ESP8266 wifi модул AT поръчки в тестването на проекта.
Тук вече са дадени някои препоръки за програми, които функционират като серийна конзола; Например, когато говорим за PuTTY за управление на UART серийни устройства от компютъра. PuTTYОсвен че е отлично приложение, то е достъпно за повечето настолни операционни системи. Освен това, като PuTTY може да се използва да действа като конзола както със серийния порт, така и с семейство интернет протоколи (TCP/IP), включително тези, които работят на TLS, се превръща в обичаен инструмент, който повече от възвръща (малкото) време, прекарано в конфигурирането му и свикването с използването му.
В допълнение към серийния комуникационен софтуер, за свързване на ESP8266 wifi модул до пристанището USB Компютърът също изисква конвертор USB към сериала TTL. Както в случая със софтуера, има няколко версии, от които те се използват само за конвертиране на порта USB на сериен порт TTL (които могат да бъдат получени от едно евро) до такива, които могат да емулират различни протоколи (като напр SPI o I2C).
Точно като програма, която функционира като серийна конзола, хардуерът, чрез който компютърът комуникира USB с логическа схема (не само ESP8266) ще бъде обичаен инструмент в работата на разработчик на микроконтролирани приложения, струва си да го имате в кутията с инструменти възможно най-скоро и да работите с него ESP8266 wifi модул Това е отлична възможност да получите такъв.
Преобразувателят USB a UART TTL Може също да се използва за наблюдение на поведението на верига, която използва ESP8266, за да направите това, изходите, които искате да наблюдавате, се свързват последователно към входа за данни (RX) на преобразувателя с бърз диод ( 1N4148, например) и резистор (2K2, например) успоредно един на друг. Такава настройка работи като хардуерен сериен снифър.
Въпреки че сниферът в изображението по-горе със сигурност е елементарен (наред с други неща, той няма буфер) е достатъчен за наблюдение на работата на модул с Arduino и ESP8266.
Премахване на снифера от предишната схема, the схема, показваща как да свържете a ESP8266 wifi модул към чиния Arduino. В допълнение към захранването му при 3V3, щифтът за нулиране и щифтът за активиране на интегрирания трябва да бъдат свързани към високо ниво (разрешаване). Разбира се, RX изводът на единия трябва да се свърже с TX на другия.
За да се опрости предишната диаграма, е представена плоча Arduino захранван на 3V3 и за който напрежението на серийния порт също се приема, че е 3V3. Ако използвате a микроконтролер с различно ниво на сигнала на серийния порт (обикновено 5 V), ще бъде необходимо, за да не се повреди ESP8266, използвай преобразувател на ниво като тези в диаграмите по-долу. Тази схема често се среща в много търговски готови модулни реализации.
Актуализирайте фърмуера на ESP8266
на ESP8266 AT команди, неговото прекратяване, скоростта по подразбиране на модула... зависят от версията на ESP8266 фърмуер. Най-добре е да се уверите, че имате една и съща версия във всички модули и, ако е възможно, това да е най-новата версия.
За съжаление повечето от Модели с wifi модул ESP8266 Имат само 4Mbit, така че не може да им се инсталира последната версия. Най-новата (официална) версия на фърмуера, на който може да се инсталира ESP8266 wifi модули с 4 Mbit (повечето) е 0.9.4, който включва версия 0.2 на ESP8266 AT команди.
В обобщение, за да актуализирате фърмуера, трябва:
-
Изтеглете съответната версия на фърмуера. В най-новата (официална) версия за модул с 4 Mbit памет, намерена в папката Espressif в github, В Уебсайт на Espressif Можете да изтеглите най-новата версия на фърмуера, но е много важно да проверите дали модулът, на който е инсталиран, има достатъчно памет.
-
Изтеглете най-новата версия на инструмента за инсталиране на фърмуера. Любимото ми е esptool което е написано в Питон, така че работи на всяка платформа. Освен че може да се изтегли, може и да се инсталира с
pip install esptool
(opip2
opython -m pip
…). Разбира се, Еспресиф Той също така предлага свой собствен инструмент, но в момента е достъпен само за Windows. -
Подгответе изтеглените файлове; разархивирайте ги в достъпна папка и, ако е необходимо, направете инструмента изпълним esptool, в моя случай, тъй като GNU / LinuxС
chmod +x esptool
-
Свържете модула към компютъра с помощта на конвертор USB UART TTL който работи на 3V3 или използвайте преобразувател на ниво, ако работи при 5 V. В допълнение към захранването, ще трябва да свържете TX към RX на преобразувателя USB UART TTL, RX към TX, GPIO0 на ниско ниво (GND) и може би GPIO2 на високо ниво (при моите тестове работи както при свързване на ниско ниво, така и при прекъсване). Ако модулът има свободна връзка GPIO15 (както се случва в ESP-12), той трябва да бъде свързан към ниско ниво. RESET, който обикновено би бил на високо ниво по време на работа, може да бъде оставен несвързан или свързан към високо ниво с помощта на резистор (10K, например), тъй като преди започване на запис може да се наложи да нулирате устройството, като го свържете до ниско ниво.
При включване на модула той ще бъде достъпен за актуализиране, но, Ако се покаже грешка при свързване, ще е необходимо да я нулирате свързване на RESET на ниско ниво за момент и след това го оставяте в ефир (без свързване) за процеса на актуализиране.
Модулът има пикове на консумация на половин ампер (до 600 mA, според някои потребители), така че е важно да използвате захранване, способно да поддържа тази консумация, особено за актуализиране на фърмуера. -
Стартирайте инструмента, за да актуализирате фърмуера. В моя случай съм запазил документите за инструмента и фърмуера в стъпка 3 в същата папка, така че стартирам от конзолата:
cd ~/Datos/firmwareESP8266
(промяна в папката, съдържаща инструмента и фърмуера)./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
задава скоростта на ESP8266 (115200 бода в моя случай) и--port
серийния порт, към който се свързва (в моя случай, емулиран, първият USB). Различните документи, които съставляват фърмуера, остават зад негоwrite_flash
предшестван от адреса, като документът user1.bin съдържа полезния товар за актуализиране.
Изпращайте команди до wifi модул ESP8266
За да контролирате ESP8266 от компютър, с който ще трябва да започнем конфигурирайте приложението за което ще бъде достатъчно ① да изберете порта, към който е свързан преобразувателят USB UART TTL, нещо като /dev/USB0
в GNU/Linux и подобни или нещо подобно COM6
в Windows, ② изберете скоростта, с която ESP8266, вероятно 115200 бода, ③ задайте 8 бита данни плюс един стоп бит, без паритет или ръкостискане, и ④ задайте край на реда, в зависимост от фърмуер, почти винаги CR+LF.
След като приложението бъде конфигурирано (или, където е подходящо, съхранено и избрано), то е отворете връзката ("open device" и "open", съответно, в екранните снимки на примерите по-горе с CuteCom y PuTTY) и можете да започнете да изпращате поръчки до ESP8266.
Както може да се види в ESP8266 AT командна таблица, форматът за активиране, деактивиране, задаване на стойност и препратка към нея е доста предсказуем, но като цяло не е лесно да ги запомните всички и вероятно ще трябва да го имате под ръка, за да го направите.
Пътят на Публикувай AT поръчки al ESP8266 wifi модул от Arduino е много проста: ① конфигурирайте комуникациите с Serial.begin(115200);
(или Serial1, Serial2… на платки с няколко хардуерни серийни порта) и ② изпратете командите, като използвате формата 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()
{
}
|
Примерът по-горе показва как да изпратите ESP8266 wifi модул AT поръчки от Arduino. В този случай е илюстрирано AT+CWJAP
, който се използва за свързване към точка за достъп. Тази команда използва като аргументи идентификатора на точката за достъп (SSID) и ключът, и двата в кавички, така че стават обект Srtring
и ги оградете в кавички, като използвате екраниращия код (\"
). За да завършите поръчката, използвайте \r\n
което съответства на CR
y LF
.
За да запомните, че серийният порт не винаги се идентифицира с Serial
(на определени чинии може да бъде Serial1
, Serial2
…) използваният порт обект е дефиниран чрез присвояването му на макроса PUERTO_SERIE
. Откриването на вида на използваната платка може да добави малко интелигентност към избора на сериен порт; По-късно ще разгледаме как можете да разберете вида на Arduino. Останалите дефиниции са обичайните, които ви позволяват да "именувате" константните стойности, за да избегнете тяхното повтаряне (и допускане на грешки) и да улесните промяната им.
Горният пример трябва да свърже ESP8266 wifi модул към посочената точка за достъп, но свързана ли е вече преди? Работи ли връзката? За да знаем това, ние трябва да "слушаме" какво ESP8266
Получавайте данни от wifi модул ESP8266
Като свържете снифера за данни, обяснен по-горе, към компютъра, можете да видите какво Arduino изпрати до ESP8266 и неговия отговор. За четене от Arduino и обработва информацията в него, с която ще е необходимо да се открие Serial.available()
дали са пристигнали някакви данни и ако е така, заредете ги Serial.read()
. Следващият пример показва как да прочетете отговора от AT+CWJAP?
, който ще докладва дали има връзка с някоя точка за достъп.
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();
}
}
|
Като в чиния Arduino Uno (и в други) отварянето на серийния монитор нулира програмата, може да се използва за преглед в серийната конзола Arduino информацията, която изпращате до ESP8266 както показва екранната снимка на изображението по-долу.
Анализирайте отговора, изпратен от wifi модула ESP8266
Вече видяхме как се разчита информацията, която достига Arduino от ESP8266. Проблемът, с който трябва да се справите е, че не знаете кога ще започне да пристига, колко време ще отнеме да пристигне, каква ще бъде дължината... и не е много ефективно да чакате отговора от ESP8266 се получава без да се пуска на микроконтролер изпълняват други задачи междувременно.
Лесен начин за справяне с това обстоятелство е повторете получените данни, търсейки конкретни отговори с които, например, активирайте индикатори (флагове или булеви променливи), които ще определят дали да продължи търсенето в получения текст и какви действия трябва да бъдат извършени въз основа на информацията, която пристига от ESP8266. Докато пристигне отговорът микроконтролер може да се посвети на други задачи, например получаване на данни от сензори и обработката им.
Потърсете текст в информацията, получена от ESP8266
За търсене в текста, който идва от ESP8266 можеш сравнете всяко получено писмо с това, което отговаря на съобщението, което търсите. Ще бъде необходимо да се използва брояч (или показалец), който сочи към буквата за сравнение; Ако знакът, който пристига от ESP8266 е същият като разглеждания в съобщението, броячът напредва, ако е различен се инициализира.
За да се знае, че краят е достигнат, се проверява следващият знак от търсеното съобщение, който ще бъде нула (\0
) или дължината на съобщението се съхранява, за да се знае, като се сравнява с брояча, дали сравнението е приключило и следователно ESP8266 wifi модул е изпратил желаното съобщение.
Следващият пример използва командата AT+CWLAP
което ще върне списък с точки за достъп и в тях се търси една, наречена "wifi polaridad.es". Въпреки че избрахме да проверим дали последният знак е нула, като буфер Съхранява само търсения текст и се знае дължината му, може и да се провери дали са получени толкова правилни букви. С Светодиод свързан към пин 2 се съобщава, че очакваният текст е намерен.
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
}
}
}
}
|
В кода на предишния пример можете също да видите начин за изберете серийния порт в зависимост от типа платка Arduino използвани. Този пример предполага, че имате три типа дъски за проекта: една Arduino Uno, One Arduino Mega 2560 и a Ардуино Леонардо. Ако работите с a Arduino Uno ще се използва Serial
и иначе Serial1
.
Ако работите с плоча Ардуино Леонардо Можете да използвате същия метод, за да спрете програмата и да изчакате конзолата (серийният порт, свързан с Serial
) е на разположение.
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
|
Търсете различни текстове в отговора на ESP8266
Кодът в предишния пример се използва за търсене на текст в информацията, изпратена от ESP8266 но отговорът може да включва различна информация в зависимост от операцията. Да предположим, за да започнем с прост случай в следващия пример, че текстът, изпратен от MCU ESP8266 es OK
когато операцията е извършена правилно и ERROR
Иначе както при поръчката AT+CWJAP?
, който служи за проверка дали ESP8266 wifi модул вече е свързан към точка за достъп.
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
}
}
}
}
}
|
Тази нова реализация на същия метод, който търси съвпадение с няколко възможни съобщения, ви позволява да избирате между различни действия в зависимост от отговора, получен от ESP8266, просто включете Светодиод съответстващ.
Ограничете времето, необходимо за получаване на отговор
Досега не е направено позоваване на съответен въпрос: the максимално време на изчакване (таймаут), преди да се приеме, че операцията е неуспешна. Ако по някаква причина връзката с ESP8266 wifi модул, модулът с точката за достъп, точката за достъп с интернет или, например, хипотетичен сървър не е наличен, програмата може да бъде блокирана в един момент в очакване за неопределено време, така че ще трябва да се даде отговор на такива обстоятелства. Максималното време за изчакване може да бъде конфигурирано за цялото приложение, обикновено то ще бъде по-"щедро" в този случай или могат да бъдат програмирани отделни времена за изчакване за всяка операция.
За да проверите дали (поне) определен интервал от време е изминал „Времето“ на момента, в който акаунтът е стартиран, обикновено се изважда от текущото „време“ и се проверява дали разликата е по-голяма от желания лимит. Това "време" не трябва да е в реално време, то обикновено съответства на интервала, изминал от момента на MCU започнете да отчитате времето; Това не засяга програмата, тъй като интересното е изминалото време, а не абсолютното време.
Обикновено, за да се провери дали определен интервал е изтекъл, се използва израз от типа:
1
|
(unsigned long)(millis()–milisegundos_al_empezar)>intervalo_de_tiempo
|
променлив milisegundos_al_empezar
съдържа стойността на millis()
на определен момент от изпълнението, от който се измерва времето, така че не е необичайно името му да се отнася до думата "хронометър". Променливата intervalo_de_tiempo
съдържа максималния брой милисекунди, който прави предишния израз верен, т.е. той представлява времето за изчакване; Обикновено е константа (или макрос) и, както в предишния случай, думата "TIMEOUT" често се появява в името му. Ако работите с много кратки интервали можете да използвате micros()
вместо millis()
(микросекунди вместо милисекунди), въпреки че е много по-рядко срещано и много по-малко прецизно.
1
|
(unsigned long)(millis()–cronometro)>TIMEOUT
|
Дълго цяло число в Arduino (unsigned long
) заема 4 байта (32 бита), така че най-голямата стойност, която може да представлява, е 4294967295 (2 на степен 32 минус едно, защото започва от нула). върху чиния Arduino Докато работи непрекъснато, броячът на милисекунди ще се нулира (връща на нула) приблизително на всеки 50 дни. При изваждане с неподписани типове данни се възпроизвежда същото поведение (обръщане на брояча), така че е възможно да се контролира времето за изчакване за неопределено време.
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;
}
}
}
|
Горният код показва a много елементарно прилагане на ограничение на изчакване включвайки редовете, маркирани по отношение на примера, който го предхожда. Тъй като проверката на времето за изчакване се извършва след обработка на данните, пристигащи от ESP8266 wifi модул, операцията може да се счита за успешна дори ако приемът отнеме повече от наложеното време за изчакване.
Изпълнете сложна операция, дефинирана от множество AT команди
За да имате примерна справка за целта на приложението, което използва ESP8266 wifi модул, да предположим, че е така съхранява информация в база данни, достъпна чрез уеб услуга за да следите температурата. Следният код чете сензор, свързан към аналогов вход на всеки определен интервал от време, изчислява средната стойност и след по-дълъг интервал от време я изпраща на уеб сървъра (стил ИН) през a петиция HTTP (ПУБЛИКУВАЙТЕ, ВЗЕМЕТЕ...).
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”);
}
}
|
В този пример за запис на температура се осъществява достъп до уеб сървър на всеки пет минути. Въпреки че наличността не е особено висока, може да се очаква, че предложението ще проработи, но ако е необходима по-висока честота на запис, ще трябва да се приложат други ресурси, например буфер за данни чакащи да бъдат изпратени, за да изпрати няколко, когато сървърът може да присъства и да ги съхрани, когато не е наличен. Ако честотата, с която данните трябва да се записват, беше още по-голяма, други видове протоколи ще трябва да бъдат предложени като алтернатива на HTTP или дори замени TCP от UDP за да можете да изпратите повечето от данните с необходимата скорост, дори с цената на загуба на някои.
Операциите, които съставляват задачата, която трябва да се извърши за изпращане на температурата, ще бъдат:
- Нулирайте wifi модула
- Прекъснете връзката с текущата точка за достъп (в случай че съществува връзка по подразбиране)
- Задайте настройките. За примера се предполага, че режимът на свързване (прост) и ролята в Wi-Fi комуникациите (станция) трябва да бъдат конфигурирани.
- Свържете се с точка за достъп
- Проверете дали връзката е правилна (всъщност това е входната точка) Ако няма връзка, започнете процеса отначало
- Свържете се със сървъра
- Изпратете заявката HTTP с данните, които ще се съхраняват
Редът на операциите не трябва да бъде точно такъв (въпреки че операцията е) и всяка стъпка може да изисква няколко ESP8266 AT командиНапример конфигурацията, изброена по-горе, ще се нуждае от две: AT+CIPMUX=0
y AT+CWMODE=1
.
Структура от данни за представяне на операции на ESP8266
В предишните примери, макар и по много елементарен начин, вече е предложено общо решение на проблема: използвайте структура от данни, която съхранява възможните отговори и действията, които трябва да бъдат предприети във всеки случай; изпратете действие, изчакайте отговор и продължете според значението на отговора. Тъй като всяка сложна операция ще изисква няколко ESP8266 AT команди, структурата на данните трябва да свързва операция с други, последващи или предишни, които трябва да бъдат извършени във всеки случай в зависимост от отговора на ESP8266.
В предишните примери бе търсено съобщение в отговора на ESP8266 и се тълкува като успех или грешка. В допълнение към получаване (и анализ) на целия получен текст, За да имате общ минимум, препоръчително е също да обърнете внимание на попълването на съобщението или, с други думи, до наличието на ESP8266 wifi модул за получаване на нови поръчки. По този начин промяната в състояние, което бихме могли да наречем, например „наличен wifi“, може да бъде получаване на името на точката за достъп и получаване на текста ERROR
или текста OK
би означавало, че ESP8266 приключихте с отговора и вече можете да изпратите следващия AT команда към 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;
|
Кодът по-горе използва вектор (operacion
), за да съхраните текста на последователните операции, които формират пълната задача. Използва се двуизмерен масив (mensaje
) с трите отговора, които се анализират. Както е обяснено по-горе, необходимо е да се търсят съобщенията, които представляват края на отговора, в допълнение към съобщението, което представлява правилен или неправилен отговор. Не всички операции ще имат еднакъв брой възможни отговори; Когато има по-малко отговори, може да се използва празно съобщение, което отнема възможно най-малкия брой цикли в своя анализ (дори и така не е най-оптималния начин). Логично ще бъде необходимо минималният брой търсени отговори (три в примера) да включва всички работни възможности, дори и да не са всички възможни.
Когато говорим за възможните отговори, вече може да се види, че този пример не е много полезен за получаване на данни с произволен формат от ESP8266 wifi модул, но работата е там, че в контекста на използване с микроконтролери не е обичайно; Най-често срещаното нещо е да изпраща данни, събрани от сензорите, които са свързали, и/или да получава информация какво да прави с задвижващите механизми, които управлява. Много ценна информация, която може да се предвиди много добре.
В предишната структура на данните, точно както се прави за изразяване на възможните отговори, които се анализират, двуизмерна матрица също се използва за определяне на операцията, която трябва да се извърши във всеки случай (siguiente_operacion
). По-конкретно, избрахме да отговаряме на три типа съобщения: ① произволен текст (LITERAL
), за да проверите дали има връзка с Wi-Fi точката за достъп и сървъра, ② текст за откриване на грешки в процеса (FALLO
) и ③ текст, показващ, че операцията е завършена успешно (ACIERTO
).
И накрая, има още два вектора за задаване на максималното време за изчакване, преди да се откажете (timeout
) и посочете (configuracion
), ако операцията приключи без изчакване на отговор (ESPERAR_RESPUESTA
) и съобщения, указващи края на комуникацията. Този последен вектор, за да илюстрира пример за това как може да се спести памет, работи с битовете на конфигурационния байт, за да посочи различните състояния.
Първият ESP8266 AT команди от структурата на данните винаги очаквайте отговор, който може да бъде съобщението за успех или грешка. Когато възникне грешка, модулът се рестартира и стартира отново и ако съобщението показва, че операцията е правилна, се преминава към следващата.
Когато се свържете със сървъра, моделът се променя. В този случай е необходимо ① да изпратите дължината на пакета данни за предаване и ② да съставите заявката HTTP с фиксиран текст плюс стойността (на температурата), която се изпраща за съхранение на сървъра. Подготовката на тези данни се извършва във всяка пратка и е необходимо тя да бъде разделена на две (уведомете дължината) или три (изпратете заявката HTTP) На ESP8266 AT поръчка. Само последната от частите, на които е разделена операцията, ще чака отговор.
В този случай ще работи безпроблемно (може би предупреждение, че модулът е зает), но когато дължината на данните е по-голяма, ще се наложи блоковете с данни да се разделят на по-малки части и дори може да се наложи да се приложи изчакване, т.к. се извършва с отчитането на температурата, за да се даде време на модула да изпрати данните, без да ги запълва буфер.
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();
|
Заедно с други макроси, които вече бяха обяснени преди, примерният код по-горе показва как се дефинират различните състояния, с които да се укаже дали да се изчака отговор и, ако е приложимо, какво съобщение показва, че е приключил.
Тъй като в различни точки на кода ще бъде изпратена операция (кога е време да се изпрати средната температура, ако времето за изчакване на операция е превишено, когато текущата операция е завършена успешно...), но как да се направи това е установено глобално, то е дефинирано като макрос ENVIAR_OPERACION
който групира стъпките, включени в доставката.
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();
|
Следва кодът на основната програма на примера. Най-външната задача е тази, която отговаря за вземането на проби от температурата за изчисляване на средната стойност и на всеки определен период от време тя се изпраща на сървъра с помощта на ESP8266 wifi модул. След като всяка операция бъде изпратена, отговорът се анализира, за да се определи коя е следващата или дали задачата за изпращане на информация е изпълнена.
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
}
}
}
|
Логично, няколко оптимизационни действия могат да бъдат извършени върху предишния код, но тъй като това е пример за разбиране как ESP8266 По общ начин си струва да се съсредоточите само върху някои аспекти, като първият е структурата на данните. Изглежда, че е логично използвайте структура от данни на език за програмиране (struct
), за да представи информацията, която се обработва: the ESP8266 AT команди и съобщенията, които се анализират.
Използвайте структура (struct
) за съхраняване на данните вместо примерните масиви (базирани на тях) е тривиално и, въпреки че може да доведе до по-елегантен код, това не предполага никакво подобрение в резултата. Истинската алтернатива, породена от използването на struct
е да се приложи, както е обяснено по-долу, променливи дължини в структури, които съдържат „вътрешни“ данни които са посочени от тях. По този начин, например, не би било необходимо една операция да има фиксиран брой отговори за анализ.
Този подход предполага, че това е най-добрият начин за прилагане на решението, но недостатъкът е, че ще бъде необходимо използвайте динамично разпределение на паметта, рискована практика при работа с a микроконтролер което изисква внимателно измерване на това колко памет ще се използва по време на изпълнение, тъй като компилаторът едва ли ще може да ни предупреди за това и има известна възможност за изчерпване на паметта (или стека) с фатални последици за изпълнението на програмата.
По отношение на оптимизирането на кода е интересно да се помни, че в програма от този тип, която използва голямо количество текст, може да спести място в паметта SRAM съхраняване на текстови низове в програмната памет (флаш) с макроса F()
. В следващите екранни снимки можете да видите различната програма и динамично разпределение на паметта с нормално използване на текст и използване на макроса F()
.
По отношение на действията, които се извършват по информацията, която постъпва от ESP8266 wifi модул, като алтернатива на проверка на съобщението от кода и извършване на едно или друго според полученото, може да се съхранява в тази структура от данни указатели към функции, които изпълняват всяка задача, вместо индикатори за състояние (флагове), които предупреждават за определено състояние, за което приложението отговаря, например, в рамките на главния цикъл.
Следното е пример за структури за съхраняване на данните от заявките към ESP8266 (типа данни operacion_esp8266
) и техните отговори (типа данни 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;
|
Като структура, която представлява операцията (данните, които се изпращат до ESP8266 wifi модул) се отнася до структурата, с която са дефинирани отговорите, и структурата на отговорите до структурата на операциите, необходимо е първо да декларирате и двете, като дефинирате новия тип данни и след това дефинирате съдържанието му.
Предишният пример счита, че програмата, която го включва, е избрала да използва a индикатор за състояние, който трябва да съответства на променлива, достъпна от кода, който отговаря за извършването на една или други операции, както е посочено от споменатата стойност. Ако в отговора на ESP8266 Когато се анализира определен текст, състоянието приема стойността, която показва структурата на съответния отговор.
Както беше казано по-рано, би била друга алтернатива, или да се замени, или да се допълни индикатор за състояние съхранява функция в референтната структура (указател), който ще бъде извикан при среща с определен текст в отговора от ESP8266 wifi модул.
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;
|
В предишния пример той е добавен към структурата от данни, която се използва за обработка на отговора от ESP8266 wifi модул указател към (предполагаема) функция, която връща данни от тип float
(може да бъде претеглената стойност на аналогово четене) и на което два байта са предоставени като аргументи (два unsigned char
който може да бъде щифтът, от който се чете аналоговият вход и този, който активира ENABLE на хипотетичен интегриран).
В процес на разработка за MCU, противно на това, което се случва в стила на разработка за по-големи системи, не е толкова необичайно да се използват глобални променливи, когато се дефинира (глобалното) поведение на приложението, което контролира сборка, така че няма да е особено рядко да се намерят този тип дефиниции като функции без параметри и които не връщат стойности, нещо като void (*accion)();
Ако работите с този начин на представяне на данните, използвайте struct
на данни с променлива дължина, ще е необходимо динамично разпределяне на памет с malloc()
(o new()
, ако се използват обекти), който ще използва количеството памет, разпределено като параметър и ще върне указател към началото на областта на паметта, която е запазена. с sizeof()
От вида, който се съхранява, умножен по броя на използваните елементи, можете да получите необходимото количество памет. Пример със и без използването му може да видите на екранните снимки по-долу. malloc()
; Внимавайте с паметта, използвана от програмата в първия случай, трябва да заредите библиотеката, която съдържа тази функция.
Ако операциите на ESP8266 wifi модул ще варира по време на изпълнението на програмата, ще е необходимо да се освободи паметта, която не се използва с free()
(o delete()
, в случай че са обекти). Въпреки че е разумно да се очаква, че компилаторът (GCC) ще оптимизира програмата, за да избегне разделянето на паметта, със сигурност производителността няма да бъде толкова оптимална, колкото работата със статично разпределена памет.
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
}
}
}
|
Въпреки че в този пример (и в двете реализации) няма много смисъл, за да се обобщи операцията, за да може да се приложи към други случаи, трябва да се отбележи, че Изпращането на данни винаги повтаря един и същ протокол: уведомете броя на байтовете, които ще бъдат изпратени, изчакайте индикатора (>) и изпратете данните.
Тъй като в този пример се използва само веднъж (цялата заявка се прави в един пакет), не изглежда много полезно, но като цяло може да е необходимо да се извършат няколко изпращания в една и съща операция, включително случаи, в които трябва се предават значителни количества данни, които трябва да бъдат фрагментирани, за да се избегне препълване на паметта на ESP8266.
За да се приложи това поведение, могат да се използват последните два елемента на връзката, така че всеки път, когато данните се изпращат, данните се попълват със съответните стойности: в първия случай броят на изпратените байтове, а във втория ( част от) искането. да бъде предадено.
За да повторите присвояването и изпращането на различните елементи, които трябва да бъдат предадени, могат да бъдат съхранени във вектор. Този нов вектор ще бъде този, който определя края на сложната операция, а не последната операция, както досега.
1 коментар