Grundläggande funktioner på en ESP8266 wifi-modul från Arduino
När Espressiv lanserade de första modulerna på marknaden wiFi med den integrerade ESP8266 och firmware för att hantera det med hjälp av AT-kommandon, det vi användare var intresserade av var att integrera det i assembler med mikrokontroller och problemen reducerades till att känna till det (tidigare) mörkret ESP8266 AT kommandotabell, matningsbehov eller ESP8266 firmware uppdatering.
Sedan kom snabbt alternativ för att programmera ESP8266 och modulimplementeringar wiFi av mycket olika format som väckte andra bekymmer: vilken ESP8266 wifi-modul att välja beroende på räckvidden för de olika antennerna (inklusive externa) eller den fysiska integrationen av dessa nya moduler i våra sammansättningar.
På grund av alla dessa förändringar kanske tonvikten inte har lagts på de mest grundläggande aspekterna, den mest grundläggande hanteringen av ESP8266 wifi-modul. Fastän polaritet.es Du kan hitta information om användningen av ESP8266 och det finns några applikationer som är avsedda att på ett generiskt sätt förklara driften av ESP8266 wifi-modul använda AT-kommandon, särskilt i artikeln om bibliotek för att göra HTTP-frågor från Arduino med ESP8266 wifi-modulen, antyder läsarnas intryck att det skulle vara användbart att lägga till lite mer grundläggande information för att hjälpa användare av ESP8266 att genomföra sina egna implementeringar.
Diskutera de grundläggande funktionerna för att arbeta med ESP8266 och att föreslå generiska lösningar är ett mål av flera mycket olika delar; För att hjälpa till att följa innehållet i artikeln kan följande index fungera som vägledning:
- Styr ESP8266 wifi-modulen från datorn genom serieporten
- Uppdatera firmware med esptool
- Skicka beställningar till modulen
- Ta emot data från ESP8266
- Analysera responsen genom att söka efter texter i innehållet
- Begränsa väntetiden för att få svar
- Utför en komplex operation som definieras av flera AT-kommandon
Styr ESP8266 wifi-modulen från datorn genom serieporten
Från en tallrik Arduino och använder din IDE det är möjligt att övervaka driften av en ESP8266 wifi-modul, skicka ESP8266 AT-kommandon och se svaret, men det är mycket bekvämare att göra det från en dator med ett terminalprogram.
Beroende på vilken tavla Arduino används, kan endast en seriell hårdvaruport vara tillgänglig, vilket ger lite besvär för sändning och mottagning. Att ändra kommunikationshastigheten är mycket bekvämare i en seriell kommunikationsapplikation från en dator och vissa moderkort. Arduino (och under vissa omständigheter) inte stöder de högre hastigheterna för seriell kommunikation bra, särskilt 115200 baud, som är standardhastigheten för de senaste versionerna av firmware.
På Vilket program man ska använda för att övervaka ESP8266 använder seriell port, det finns många att välja mellan efter behov och preferenser; på sistone har jag använt den klassiska mer CuteCom (den i skärmdumpen ovan) eftersom det är väldigt bekvämt för mig att upprepa vissa ESP8266 wifi-modul AT beställningar i projekttestning.
Några rekommendationer har redan givits här om program som fungerar som en seriell konsol; Till exempel när man pratar om PuTTY för att styra seriella UART-enheter från datorn. PuTTYFörutom att vara en utmärkt applikation är den tillgänglig för de flesta stationära operativsystem. Dessutom, som PuTTY kan användas för att fungera som en konsol med både serieporten och Internetprotokollfamilj (TCP/IP), inklusive de som opererar TLS, blir ett vanligt verktyg som mer än betalar tillbaka den (lilla) tiden som går åt till att konfigurera den och vänja sig vid användningen.
Förutom programvara för seriell kommunikation, att ansluta till ESP8266 wifi-modul till hamnen USB En dator kräver också en omvandlare USB till serier TTL. Precis som i fallet med programvara finns det flera versioner, från vilka de endast används för att konvertera porten USB på en seriell port TTL (som kan erhållas från en Euro) till de som kan emulera olika protokoll (som t.ex SPI o I2C).
Precis som ett program som fungerar som en seriell konsol, hårdvaran att kommunicera datorn via USB med en logisk krets (inte bara ESP8266) kommer att vara ett vanligt verktyg i arbetet med en mikrokontrollerad applikationsutvecklare, det är värt att ha det i verktygslådan så snart som möjligt och arbeta med det ESP8266 wifi-modul Det är ett utmärkt tillfälle att få en.
Omvandlaren USB a UART TTL Den kan också användas för att övervaka beteendet hos en krets som använder ESP8266, för att göra detta ansluts utgångarna som du vill övervaka i serie till dataingången (RX) på omvandlaren med en snabb diod (den 1N4148t.ex.) och ett motstånd (t.ex. 2K2) parallellt med varandra. En sådan installation fungerar som en hårdvaruseriesniffer.
Även om sniffern i bilden ovan förvisso är rudimentär (bland annat har den inte buffert) är tillräckligt för att övervaka driften av en sammansättning med Arduino och ESP8266.
Ta bort sniffern från det tidigare schemat, den schematisk som visar hur man ansluter en ESP8266 wifi-modul till en tallrik Arduino. Förutom att mata den vid 3V3, måste återställningsstiftet och aktiveringsstiftet på den integrerade vara anslutna till en hög nivå (aktivera). Naturligtvis måste RX-stiftet på den ena anslutas till den andras TX.
För att förenkla det föregående diagrammet har en platta representerats Arduino drivs med 3V3 och för vilken en spänning på serieporten också antas vara 3V3. Om du använder en mikrokontroller med en annan signalnivå på serieporten (vanligtvis 5 V) kommer att vara nödvändigt för att inte skada ESP8266, använda en nivåomvandlare som i diagrammen nedan. Denna krets återfinns ofta i många kommersiella färdiga modulimplementeringar.
Uppdatera ESP8266 firmware
den ESP8266 AT-kommandon, dess avslutning, standardhastigheten för modulen... beror på versionen av ESP8266 firmware. Det är bäst att se till att du har samma version i alla moduler och om möjligt att det är den senaste versionen.
Tyvärr har de flesta ESP8266 wifi-modulmodeller De har bara 4Mbit, så den senaste versionen kan inte installeras på dem. Den senaste (officiella) versionen av firmware som kan installeras på ESP8266 wifi-moduler med 4 Mbit (mest) är 0.9.4 som inkluderar version 0.2 av ESP8266 AT-kommandon.
Sammanfattningsvis, för att uppdatera firmware behöver du:
-
Ladda ner motsvarande firmwareversion. Den senaste (officiella) versionen för en modul med 4 Mbit minne, som finns i mappen Espressif på github. I Espressif hemsida Du kan ladda ner den senaste versionen av firmware, men det är mycket viktigt att kontrollera att modulen som den är installerad på har tillräckligt med minne.
-
Ladda ner den senaste versionen av installationsverktyget för den fasta programvaran. Min favorit är esptool som är skrivet i Python, så det fungerar på vilken plattform som helst. Förutom att laddas ner kan den även installeras med
pip install esptool
(opip2
opython -m pip
…). Självklart, Espressiv Den erbjuder också ett eget verktyg men är för närvarande endast tillgängligt för Windows. -
Förbered nedladdade filer; packa upp dem i en tillgänglig mapp och, om nödvändigt, gör verktyget körbart esptool, i mitt fall sedan GNU / Linux, Med
chmod +x esptool
-
Anslut modulen till datorn med en omvandlare USB UART TTL som fungerar på 3V3 eller använd en nivåomvandlare om den fungerar på 5 V. Utöver strömmen måste du ansluta TX till RX på omvandlaren USB UART TTL, RX till TX, GPIO0 på låg nivå (GND) och kanske GPIO2 på hög nivå (i mina tester har det fungerat både att koppla in den på låg nivå och koppla bort den). Om modulen har GPIO15 anslutningsfri (som sker i ESP-12) måste den anslutas till en låg nivå. RESET, som normalt skulle vara på en hög nivå under drift, kan lämnas oansluten eller kopplas till en hög nivå med hjälp av ett motstånd (t.ex. 10K), eftersom det innan inspelningen kan vara nödvändigt att återställa enheten genom att ansluta den till en låg nivå.
Genom att slå på modulen kommer den att vara tillgänglig att uppdatera men, Om ett anslutningsfel visas är det nödvändigt att återställa det ansluta RESET på en låg nivå för ett ögonblick och sedan lämna den i luften (utan att ansluta) för uppdateringsprocessen.
Modulen har halv ampere förbrukningen toppar (upp till 600 mA, enligt vissa användare) så det är viktigt att använda en strömförsörjning som kan stödja denna förbrukning, speciellt för att uppdatera firmware. -
Kör verktyget för att uppdatera firmware. I mitt fall har jag sparat verktyget och firmwaredokumenten i steg 3 i samma mapp, så jag kör från konsolen:
cd ~/Datos/firmwareESP8266
(byt till mappen som innehåller verktyget och 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
ställer in hastigheten på ESP8266 (115200 baud i mitt fall) och--port
serieporten den ansluts till (i mitt fall, emulerad, den första USB). De olika dokumenten som utgör den fasta programvaran går bakomwrite_flash
föregås av adressen, med user1.bin-dokumentet som innehåller uppdateringens nyttolast.
Skicka kommandon till ESP8266 wifi-modul
För att kontrollera ESP8266 från en dator vi måste börja med konfigurera appen för vilken det räcker med att ① välja den port som omvandlaren är ansluten till USB UART TTL, något liknande /dev/USB0
i GNU/Linux och liknande eller något liknande COM6
i Windows, ② välj den hastighet med vilken ESP8266, förmodligen 115200 baud, ③ ställ in 8 databitar plus en stoppbit, utan paritet eller handskakning, och ④ ställ in slutet av raden, beroende på firmware, nästan alltid CR+LF.
När applikationen är konfigurerad (eller, i förekommande fall, lagrad och vald), är den det öppna anslutningen ("öppen enhet" respektive "öppen", i skärmdumparna i exemplen ovan med CuteCom y PuTTY) och du kan börja skicka beställningar till ESP8266.
Som kan ses i ESP8266 AT kommandotabell, formatet för att aktivera, avaktivera, ställa in ett värde och hänvisa till det är ganska förutsägbart, men i allmänhet är det inte lätt att komma ihåg dem alla och du kommer förmodligen att behöva ha det till hands för att referera till det.
Vägen till skicka AT-ordrar al ESP8266 wifi-modul från Arduino är mycket enkelt: ① konfigurera kommunikation med Serial.begin(115200);
(eller Serial1, Serial2... på kort med flera seriella hårdvaruportar) och ② skicka kommandona med formatet 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() { } |
Exemplet ovan visar hur du skickar ESP8266 wifi-modul AT beställningar från Arduino. I det här fallet är det illustrerat AT+CWJAP
, som används för att ansluta till en åtkomstpunkt. Detta kommando använder som argument åtkomstpunktsidentifieraren (SSID) och nyckeln, båda inom citattecken, så att de blir ett objekt Srtring
och omges av citattecken med hjälp av escape-koden (\"
). För att slutföra beställningen, använd \r\n
som motsvarar CR
y LF
.
För att komma ihåg att den seriella porten inte alltid identifieras med Serial
(på vissa tallrikar kan det vara Serial1
, Serial2
…) portobjektet som används har definierats genom att tilldela det till makrot PUERTO_SERIE
. Att upptäcka vilken typ av kort som används kan lägga till lite intelligens till valet av serieportar; Vi kommer att gå igenom hur du kan ta reda på vilken typ av Arduino. Resten av definitionerna är de vanliga som låter dig "namna" de konstanta värdena för att undvika att upprepa dem (och göra misstag) och göra det lättare att ändra dem.
Ovanstående exempel är tänkt att ansluta ESP8266 wifi-modul till den angivna åtkomstpunkten men var den redan ansluten tidigare? Har anslutningen fungerat? För att veta detta måste vi "lyssna" på vad ESP8266
Ta emot data från ESP8266 wifi-modul
Genom att koppla datasniffaren som förklaras ovan till datorn kan du se vad Arduino har skickat till ESP8266 och hans svar. Att läsa från Arduino och behandla informationen i den, kommer det att vara nödvändigt att upptäcka med Serial.available()
om någon data har anlänt och i så fall ladda den med Serial.read()
. Följande exempel visar hur du läser svaret från AT+CWJAP?
, som kommer att rapportera om det finns en anslutning till någon åtkomstpunkt.
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(); } } |
Som på en tallrik Arduino Uno (och i andra) genom att öppna den seriella monitorn återställs programmet, det kan användas för att se i seriekonsolen Arduino informationen du skickar till ESP8266 som skärmdumpen av bilden nedan visar.
Analysera svaret som skickas av ESP8266 wifi-modulen
Vi har redan sett hur man läser informationen som når Arduino från ESP8266. Problemet du måste ta itu med är att du inte vet när det börjar komma, hur lång tid det kommer att ta att komma fram, hur lång tid det kommer att vara... och det är inte särskilt effektivt att vänta på svar från ESP8266 tas emot utan att låta mikrokontroller utföra andra uppgifter under tiden.
Ett enkelt sätt att hantera denna omständighet är upprepa de mottagna uppgifterna och leta efter konkreta svar som till exempel aktiverar indikatorer (flaggor eller booleska variabler) som avgör om man ska fortsätta söka i den mottagna texten och vilka åtgärder som ska utföras baserat på informationen som kommer från ESP8266. Medan svaret kommer mikrokontroller kan ägna sig åt andra uppgiftert.ex. ta emot data från sensorer och bearbeta det.
Sök efter en text i informationen från ESP8266
För att söka i texten som kommer från ESP8266 kan jämför varje mottagen brev med den som motsvarar meddelandet du letar efter. Det kommer att vara nödvändigt att använda en räknare (eller en pekare) som pekar på bokstaven som ska jämföras; Om karaktären som kommer från ESP8266 är densamma som den som undersöks i meddelandet, går räknaren framåt, om den är annorlunda initieras den.
För att veta att slutet har nåtts, konsulteras nästa tecken i det sökta meddelandet, vilket kommer att vara noll (\0
) eller längden på meddelandet lagras för att, genom att jämföra det med räknaren, veta om jämförelsen har avslutats och därför ESP8266 wifi-modul har skickat det önskade meddelandet.
Följande exempel använder kommandot AT+CWLAP
som kommer att returnera en lista med åtkomstpunkter och inom dem söks en som heter "wifi polaridad.es". Även om vi har valt att verifiera att det sista tecknet är noll, eftersom buffert Den lagrar bara den sökta texten och dess längd är känd, det skulle också kunna kontrolleras om ett sådant antal korrekta bokstäver har mottagits. Med en LED kopplad till stift 2 rapporteras att den förväntade texten har hittats.
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 } } } } |
I koden i föregående exempel kan du också se ett sätt att välj serieport beroende på typ av kort Arduino Begagnade. Det här exemplet förutsätter att du har tre typer av brädor för projektet: en Arduino Uno, En Arduino Mega 2560 och en arduino leonardo. Om du arbetar med en Arduino Uno den kommer att användas Serial
annars Serial1
.
Om du arbetar med en tallrik arduino leonardo Du kan använda samma metod för att stoppa programmet och vänta på konsolen (den seriella porten som är kopplad till Serial
) är tillgänglig.
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 |
Sök i olika texter i ESP8266-svaret
Koden i föregående exempel används för att söka efter text i informationen som skickas av ESP8266 men svaret kan innehålla olika information beroende på operationen. Antag, för att börja med ett enkelt fall i nästa exempel, att texten som skickas av MCU ESP8266 es OK
när operationen utförs korrekt och ERROR
Annars, som med beställningen AT+CWJAP?
, som tjänar till att verifiera om ESP8266 wifi-modul är redan ansluten till en åtkomstpunkt.
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 } } } } } |
Denna nya implementering av samma metod, som söker efter en matchning med flera möjliga meddelanden, låter dig välja mellan olika åtgärder beroende på svaret som mottagits från ESP8266, slå helt enkelt på LED motsvarande.
Begränsa tiden det tar att få ett svar
Hittills har ingen hänvisning gjorts till en relevant fråga: maximal väntetid (timeout) innan övervägande av en operation misslyckades. Om av någon anledning kopplingen till ESP8266 wifi-modul, modulen med åtkomstpunkten, åtkomstpunkten med Internet eller till exempel en hypotetisk server är inte tillgänglig, programmet kan blockeras vid ett tillfälle och väntar på obestämd tid, så ett svar måste artikuleras till sådana omständigheter. Den maximala väntetiden kan konfigureras för hela applikationen, vanligtvis blir den mer "generös" i så fall, eller individuella väntetider kan programmeras för varje operation.
För att kontrollera att (minst) ett visst tidsintervall har gått "Klockan" för det ögonblick då kontot startas subtraheras vanligtvis från den aktuella "tiden" och det verifieras att skillnaden är större än den önskade gränsen. Denna "tid" behöver inte vara realtid, den motsvarar vanligtvis intervallet som har gått sedan MCU börja räkna tid; Detta påverkar inte programmet eftersom det som är intressant är förfluten tid och inte den absoluta tiden.
Vanligtvis, för att kontrollera om ett visst intervall har förflutit, används ett uttryck av typen:
1 | (unsigned long)(millis()–milisegundos_al_empezar)>intervalo_de_tiempo |
variabel milisegundos_al_empezar
innehåller värdet av millis()
av ett visst ögonblick i utförandet från vilket det är tidsbestämt, så det är inte ovanligt att dess namn syftar på ordet "kronometer". Variabeln intervalo_de_tiempo
innehåller det maximala antalet millisekunder som gör det föregående uttrycket sant, det vill säga det representerar timeout; Det är vanligtvis en konstant (eller ett makro) och som i föregående fall förekommer ordet "TIMEOUT" ofta i dess namn. Om du arbetar med mycket korta intervaller kan du använda micros()
istället för millis()
(mikrosekunder istället för millisekunder) även om det är mycket mindre vanligt och mycket mindre exakt.
1 | (unsigned long)(millis()–cronometro)>TIMEOUT |
Ett långt heltal i Arduino (unsigned long
) upptar 4 byte (32 bitar), så det största värdet den kan representera är 4294967295 (2 i potensen 32 minus ett, eftersom det börjar på noll). på en tallrik Arduino Medan den körs kontinuerligt kommer millisekundräknaren att återställas (återgå till noll) ungefär var 50:e dag. Vid subtrahering med osignerade datatyper reproduceras samma beteende (vända räknaren) så det är lönsamt att kontrollera timeouten på obestämd tid.
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; } } } |
Ovanstående kod visar en mycket grundläggande implementering av timeout-begränsning med de linjer som är markerade med avseende på exemplet som föregår det. Eftersom timeout-verifieringen utförs efter bearbetning av data som kommer från ESP8266 wifi-modul, kan operationen anses lyckad även om mottagningen tar längre tid än den pålagda väntetiden.
Utför en komplex operation som definieras av flera AT-kommandon
För att ha ett exempel på syftet med applikationen som utnyttjar ESP8266 wifi-modul, antar att det är det lagra information i en databas som nås via en webbtjänst för att hålla koll på temperaturen. Följande kod läser av en sensor ansluten till en analog ingång varje visst tidsintervall, beräknar medelvärdet och skickar det efter ett längre tidsintervall till webbservern (stil IoT) genom en petition HTTP (POSTA, HÄMTA...).
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”); } } |
I detta temperaturregistreringsexempel nås en webbserver var femte minut. Även om tillgängligheten inte är särskilt hög kan man förvänta sig att förslaget skulle fungera, men om en högre inspelningsfrekvens skulle behövas skulle andra resurser behöva sättas in, t.ex. databuffert väntar på att skickas, för att skicka flera när servern kan delta och lagra dem när den inte är tillgänglig. Om frekvensen med vilken data behöver registreras var ännu högre, skulle andra typer av protokoll behöva föreslås som ett alternativ till HTTP eller till och med byta ut TCP av UDP för att kunna skicka det mesta av datan med den hastighet som krävs även till priset av att förlora en del.
Operationerna som utgör uppgiften som ska utföras för att skicka temperaturen skulle vara:
- Återställ wifi-modulen
- Koppla från den aktuella åtkomstpunkten (om det finns en standardanslutning)
- Ställ in inställningarna. För exemplet antas det att anslutningsläget (enkelt) och rollen i Wi-Fi-kommunikation (station) måste konfigureras.
- Anslut till åtkomstpunkt
- Verifiera att anslutningen är korrekt (det här är faktiskt startpunkten) Om det inte finns någon anslutning, starta processen från början
- Anslut till servern
- Skicka förfrågan HTTP med de uppgifter som ska lagras
Ordningen på operationerna behöver inte vara exakt så här (även om operationen är det) och varje steg kan kräva flera ESP8266 AT-kommandonTill exempel skulle konfigurationen som anges ovan behöva två: AT+CIPMUX=0
y AT+CWMODE=1
.
En datastruktur för att representera operationer på ESP8266
I de tidigare exemplen, även om det är på ett mycket grundläggande sätt, föreslås redan en generisk lösning på problemet: använda en datastruktur som lagrar möjliga svar och de åtgärder som måste vidtas i varje enskilt fall; skicka en åtgärd, vänta på ett svar och fortsätt enligt vad svaret betyder. Eftersom varje komplex operation kommer att kräva flera ESP8266 AT-kommandon, måste datastrukturen koppla en operation med andra, efterföljande eller tidigare, som måste utföras i varje enskilt fall beroende på svaret från ESP8266.
I de tidigare exemplen söktes ett meddelande i svaret från ESP8266 och det tolkades som framgång eller misstag. Förutom en mottagning (och analys) av all mottagen text, För att ha ett generellt minimum är det tillrådligt att även ta hand om ifyllandet av meddelandet eller, med andra ord, till tillgängligheten av ESP8266 wifi-modul att ta emot nya beställningar. På så sätt kan ändringen till ett tillstånd som vi kan kalla, till exempel "wifi tillgängligt", vara att ta emot namnet på åtkomstpunkten och ta emot texten ERROR
eller texten OK
skulle innebära att ESP8266 du har avslutat svaret och du kan nu skicka nästa AT-kommando till 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; |
Koden ovan använder en vektor (operacion
) för att lagra texten för de på varandra följande operationerna som utgör hela uppgiften. En tvådimensionell array används (mensaje
) med de tre svaren som analyseras. Som förklarats ovan är det nödvändigt att leta efter de meddelanden som representerar slutet av svaret förutom meddelandet som representerar ett korrekt eller felaktigt svar. Inte alla operationer kommer att ha samma antal möjliga svar; När det finns färre svar kan ett tomt meddelande användas som förbrukar minsta möjliga antal cykler i sin analys (även så är det inte det mest optimala sättet). Logiskt sett kommer det att vara nödvändigt att det minsta antalet sökta svar (tre i exemplet) inkluderar alla driftmöjligheter, även om de inte alla är möjliga.
När man talar om möjliga svar kan man redan se att detta exempel inte är särskilt användbart för att ta emot data med ett godtyckligt format från en ESP8266 wifi-modul, men grejen är att, i samband med användning med mikrokontroller det är inte vanligt; Det vanligaste är att skicka data som samlats in av de sensorer de har anslutit och/eller få information om vad man ska göra med de ställdon den styr. Mycket värdefull information, som kan förutsägas mycket väl.
I den tidigare datastrukturen, precis som det görs för att uttrycka de möjliga svaren som analyseras, används också en tvådimensionell matris för att bestämma operationen som måste utföras i varje fall (siguiente_operacion
). Specifikt har vi valt att svara på tre typer av meddelanden: ① en godtycklig text (LITERAL
) för att verifiera om det finns en anslutning till Wi-Fi-åtkomstpunkten och servern, ② en text för att upptäcka fel i processen (FALLO
) och ③ en text som indikerar att operationen slutfördes framgångsrikt (ACIERTO
).
Slutligen finns det ytterligare två vektorer för att ställa in den maximala väntetiden innan du ger upp (timeout
) och specificera (configuracion
) om operationen avslutas utan att vänta på svar (ESPERAR_RESPUESTA
) och meddelanden som indikerar slutet på kommunikationen. Denna sista vektor, för att illustrera ett exempel på hur minne kan sparas, arbetar med bitarna i en konfigurationsbyte för att indikera de olika tillstånden.
Den första ESP8266 AT-kommandon av datastrukturen alltid förvänta sig ett svar, vilket kan vara framgång eller felmeddelande. När ett fel uppstår startas modulen om och den startar igen och om meddelandet indikerar att operationen är korrekt går den vidare till nästa.
När du har anslutit till servern ändras mönstret. I detta fall är det nödvändigt att ① skicka längden på datapaketet som ska överföras och ② sammanställa begäran HTTP med en fast text plus värdet (på temperaturen) som skickas för att lagras på servern. Beredningen av dessa uppgifter utförs i varje försändelse och det är nödvändigt att dela upp dem i två (meddela längden) eller tre (skicka förfrågan HTTP) På ESP8266 AT beställning. Endast den sista av de delar som operationen är uppdelad i väntar på svar.
I det här fallet kommer det att fungera utan problem (kanske varning för att modulen är upptagen) men när längden på data är större kommer det att bli nödvändigt att dela upp datablocken i mindre bitar och det kan till och med bli nödvändigt att implementera en väntan, som görs med temperaturavläsningen, för att ge modulen tid att skicka data utan att fylla dess buffert.
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(); |
Tillsammans med andra makron som redan har förklarats tidigare visar exempelkoden ovan hur de olika tillstånden definieras för att specificera om man ska vänta på svar och, i förekommande fall, vilket meddelande som indikerar att det har avslutats.
Som vid olika punkter i koden kommer en operation att skickas (när det är dags att skicka medeltemperaturen, om väntetiden för en operation överskrids, när den aktuella operationen har slutförts...) men hur man gör det är etablerat globalt, har det definierats som ett makro ENVIAR_OPERACION
som grupperar stegen som ingår i sjöfarten.
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(); |
Följande är koden för huvudprogrammet i exemplet. Den mest externa uppgiften är den som ansvarar för att ta prover på temperaturen för att beräkna medelvärdet och, varje viss tidsperiod, skickas den till servern med hjälp av ESP8266 wifi-modul. När varje operation har skickats analyseras svaret för att avgöra vilken som är nästa eller om uppgiften att skicka information har slutförts.
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 } } } |
Logiskt sett kan flera optimeringsåtgärder utföras på den tidigare koden, men eftersom detta är ett exempel för att förstå hur ESP8266 På ett generiskt sätt är det bara värt att fokusera på vissa aspekter, den första är datastrukturen. Det verkar som att det logiska är använd en datastruktur för programmeringsspråk (struct
) för att representera den information som behandlas: den ESP8266 AT-kommandon och de meddelanden som analyseras.
Använd en struktur (struct
) att lagra data istället för exempelmatriserna (baserat på dem) är trivialt och även om det kan resultera i mer elegant kod, innebär det ingen förbättring av resultatet. Det sanna alternativet som utgörs av användningen av struct
är att implementera, som förklaras nedan, variabla längder i strukturer som innehåller "inre" data som de hänvisar till. På så sätt skulle det till exempel inte vara nödvändigt för en operation att ha ett fast antal svar att analysera.
Detta tillvägagångssätt tyder på att det är det bästa sättet att implementera lösningen men nackdelen är att det skulle vara nödvändigt använd dynamisk minnesallokering, en riskabel praxis att arbeta med en mikrokontroller vilket kräver noggrann mätning av hur mycket minne som kommer att användas vid körning, eftersom kompilatorn knappast kommer att kunna varna oss för detta och det finns en viss möjlighet att tömma minnet (eller stacken) med ödesdigra konsekvenser för programmets exekvering.
I raden för att optimera koden är det intressant att komma ihåg att i ett program av denna typ, som använder en stor mängd text, kan spara minnesutrymme SRAM lagra textsträngar i programminnet (blixt) med makrot F()
. I följande skärmdumpar kan du se olika program och dynamisk minnesfördelning med normal användning av text och med hjälp av makrot F()
.
Med hänsyn till de åtgärder som utförs enligt informationen som kommer från ESP8266 wifi-modul, som ett alternativ till att kontrollera meddelandet från koden och utföra det ena eller det andra enligt vad som tas emot, kan lagras i denna datastruktur pekare till funktioner som utför varje uppgift istället för statusindikatorer (flaggor) som varnar för ett visst tillstånd som applikationen ansvarar för att hantera till exempel inom huvudslingan.
Följande är ett exempel på strukturer för att lagra data för förfrågningar till ESP8266 (datatypen operacion_esp8266
) och deras svar (datatypen 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; |
Som strukturen som representerar operationen (data som skickas till ESP8266 wifi-modul) hänvisar till strukturen med vilken svaren definieras och strukturen för svaren till strukturen för operationerna, det är nödvändigt att deklarera båda först, genom att definiera den nya datatypen och sedan definiera dess innehåll.
Det föregående exemplet anser att programmet som innehåller det har valt att använda en Statusindikator, som måste motsvara en variabel tillgänglig från koden som är ansvarig för att utföra en eller annan operation som indikeras av nämnda värde. Om i svaret av ESP8266 När en viss text analyseras tar tillståndet det värde som indikerar strukturen för motsvarande svar.
Som sagt tidigare skulle ett annat alternativ, antingen att ersätta eller komplettera en statusindikator, vara lagra en funktion i referensstrukturen (en pekare) som skulle kallas när man stöter på viss text i svaret från ESP8266 wifi-modul.
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; |
I det föregående exemplet har det lagts till i datastrukturen som används för att bearbeta svaret från ESP8266 wifi-modul en pekare till en (förmodad) funktion som returnerar data av typen float
(kan vara det viktade värdet av en analog avläsning) och till vilken två bytes tillhandahålls som argument (två unsigned char
vilket kan vara stiftet från vilket den analoga ingången läses och den som aktiverar ENABLE för en hypotetisk integrerad).
Under utveckling för MCU, i motsats till vad som händer i utvecklingsstilen för större system, är det inte så ovanligt att använda globala variabler när man definierar det (globala) beteendet för applikationen som styr en assembly, så det kommer inte att vara särskilt ovanligt att hitta den här typen av definitioner som funktioner utan parametrar och som inte returnerar värden, något liknande void (*accion)();
Om du arbetar med detta sätt att representera data, använder du struct
av data med variabel längd, kommer det att vara nödvändigt att dynamiskt allokera minne med malloc()
(o new()
, om objekt används), som kommer att använda mängden minne som tilldelats som en parameter och returnera en pekare till början av minnesområdet som är reserverat. Med sizeof()
På den typ som lagras, multiplicerat med antalet använda element, kan du få mängden minne som behövs. Ett exempel med och utan användning kan ses i skärmdumparna nedan. malloc()
; Var försiktig med minnet som används av programmet i det första fallet, du måste ladda biblioteket som innehåller denna funktion.
Om verksamheten på ESP8266 wifi-modul kommer att variera under programmets körning, kommer det att vara nödvändigt att frigöra minnet som inte används med free()
(o delete()
, om det är föremål). Även om det är rimligt att förvänta sig att kompilatorn (GCC) kommer att optimera programmet för att undvika minnespartitionering, prestanda kommer säkert inte att vara lika optimal som att arbeta med statiskt allokerat minne.
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 } } } |
Även om det i det här exemplet (i båda implementeringarna) inte är så meningsfullt, för att generalisera operationen för att kunna tillämpa den på andra fall, bör det noteras att att skicka data upprepar alltid samma protokoll: meddela antalet byte som kommer att skickas, vänta på indikatorn (>) och skicka data.
Eftersom det i det här exemplet bara används vid ett tillfälle (hela förfrågan görs i ett paket), verkar det inte särskilt användbart men i allmänhet kan det vara nödvändigt att utföra flera sändningar i samma operation, inklusive fall där de måste överföras avsevärda mängder data som måste fragmenteras för att undvika att minnet strömmar över ESP8266.
För att implementera detta beteende kan de två sista delarna av anslutningen användas så att varje gång data skickas fylls data med motsvarande värden: i det första fallet, antalet skickade byte och i det andra, ( del av den) begäran som ska överföras.
För att upprepa tilldelningen och sändningen av de olika elementen som måste sändas kan lagras i en vektor. Denna nya vektor kommer att vara den som bestämmer slutet av den komplexa operationen och inte den sista operationen som hittills.
1 kommentar