270 lines
9.4 KiB
C++
270 lines
9.4 KiB
C++
/*------------------------------------------------------------------------
|
|
An Arduino library for the ESP8266 WiFi-serial bridge
|
|
|
|
https://www.adafruit.com/product/2282
|
|
|
|
The ESP8266 is a 3.3V device. Safe operation with 5V devices (most
|
|
Arduino boards) requires a logic-level shifter for TX and RX signals.
|
|
|
|
Adafruit invests time and resources providing this open source code,
|
|
please support Adafruit and open-source hardware by purchasing
|
|
products from Adafruit!
|
|
|
|
Written by Limor Fried and Phil Burgess for Adafruit Industries.
|
|
MIT license, all text above must be included in any redistribution.
|
|
------------------------------------------------------------------------*/
|
|
|
|
#include "Adafruit_ESP8266.h"
|
|
|
|
// Constructor
|
|
Adafruit_ESP8266::Adafruit_ESP8266(Stream *s, Stream *d, int8_t r) :
|
|
stream(s), debug(d), reset_pin(r), host(NULL), writing(false) {
|
|
setTimeouts();
|
|
};
|
|
|
|
// Override various timings. Passing 0 for an item keeps current setting.
|
|
void Adafruit_ESP8266::setTimeouts(
|
|
uint32_t rcv, uint32_t rst, uint32_t con, uint32_t ipd) {
|
|
if(rcv) {
|
|
stream->setTimeout(rcv);
|
|
receiveTimeout = rcv;
|
|
}
|
|
if(rst) resetTimeout = rst;
|
|
if(con) connectTimeout = con;
|
|
if(ipd) ipdTimeout = ipd;
|
|
}
|
|
|
|
// Override boot marker string, or pass NULL to restore default.
|
|
void Adafruit_ESP8266::setBootMarker(Fstr *s) {
|
|
bootMarker = s ? s : defaultBootMarker;
|
|
}
|
|
|
|
// Anything printed to the EPS8266 object will be split to both the WiFi
|
|
// and debug streams. Saves having to print everything twice in debug code.
|
|
size_t Adafruit_ESP8266::write(uint8_t c) {
|
|
if(debug) {
|
|
if(!writing) {
|
|
debug->print(F("---> "));
|
|
writing = true;
|
|
}
|
|
debug->write(c);
|
|
}
|
|
return stream->write(c);
|
|
}
|
|
|
|
// Equivalent to Arduino Stream find() function, but with search string in
|
|
// flash/PROGMEM rather than RAM-resident. Returns true if string found
|
|
// (any further pending input remains in stream), false if timeout occurs.
|
|
// Can optionally pass NULL (or no argument) to read/purge the OK+CR/LF
|
|
// returned by most AT commands. The ipd flag indicates this call follows
|
|
// a CIPSEND request and might be broken into multiple sections with +IPD
|
|
// delimiters, which must be parsed and handled (as the search string may
|
|
// cross these delimiters and/or contain \r or \n itself).
|
|
boolean Adafruit_ESP8266::find(Fstr *str, boolean ipd) {
|
|
uint8_t stringLength, matchedLength = 0;
|
|
int c;
|
|
boolean found = false;
|
|
uint32_t t, save;
|
|
uint16_t bytesToGo = 0;
|
|
|
|
if(ipd) { // IPD stream stalls really long occasionally, what gives?
|
|
save = receiveTimeout;
|
|
setTimeouts(ipdTimeout);
|
|
}
|
|
|
|
if(str == NULL) str = F("OK\r\n");
|
|
stringLength = strlen_P((Pchr *)str);
|
|
|
|
if(debug && writing) {
|
|
debug->print(F("<--- '"));
|
|
writing = false;
|
|
}
|
|
|
|
for(t = millis();;) {
|
|
if(ipd && !bytesToGo) { // Expecting next IPD marker?
|
|
if(find(F("+IPD,"))) { // Find marker in stream
|
|
for(;;) {
|
|
if((c = stream->read()) > 0) { // Read subsequent chars...
|
|
if(debug) debug->write(c);
|
|
if(c == ':') break; // ...until delimiter.
|
|
bytesToGo = (bytesToGo * 10) + (c - '0'); // Keep count
|
|
t = millis(); // Timeout resets w/each byte received
|
|
} else if(c < 0) { // No data on stream, check for timeout
|
|
if((millis() - t) > receiveTimeout) goto bail;
|
|
} else goto bail; // EOD on stream
|
|
}
|
|
} else break; // OK (EOD) or ERROR
|
|
}
|
|
|
|
if((c = stream->read()) > 0) { // Character received?
|
|
if(debug) debug->write(c); // Copy to debug stream
|
|
bytesToGo--;
|
|
if(c == pgm_read_byte((Pchr *)str +
|
|
matchedLength)) { // Match next byte?
|
|
if(++matchedLength == stringLength) { // Matched whole string?
|
|
found = true; // Winner!
|
|
break;
|
|
}
|
|
} else { // Character mismatch; reset match pointer+counter
|
|
matchedLength = 0;
|
|
}
|
|
t = millis(); // Timeout resets w/each byte received
|
|
} else if(c < 0) { // No data on stream, check for timeout
|
|
if((millis() - t) > receiveTimeout) break; // You lose, good day sir
|
|
} else break; // End-of-data on stream
|
|
}
|
|
|
|
bail: // Sorry, dreaded goto. Because nested loops.
|
|
if(debug) debug->println('\'');
|
|
if(ipd) setTimeouts(save);
|
|
return found;
|
|
}
|
|
|
|
// Read from ESP8266 stream into RAM, up to a given size. Max number of
|
|
// chars read is 1 less than this, so NUL can be appended on string.
|
|
int Adafruit_ESP8266::readLine(char *buf, int bufSiz) {
|
|
if(debug && writing) {
|
|
debug->print(F("<--- '"));
|
|
writing = false;
|
|
}
|
|
int bytesRead = stream->readBytesUntil('\r', buf, bufSiz-1);
|
|
buf[bytesRead] = 0;
|
|
if(debug) {
|
|
debug->print(buf);
|
|
debug->println('\'');
|
|
}
|
|
while(stream->read() != '\n'); // Discard thru newline
|
|
return bytesRead;
|
|
}
|
|
|
|
// ESP8266 is reset by momentarily connecting RST to GND. Level shifting is
|
|
// not necessary provided you don't accidentally set the pin to HIGH output.
|
|
// It's generally safe-ish as the default Arduino pin state is INPUT (w/no
|
|
// pullup) -- setting to LOW provides an open-drain for reset.
|
|
// Returns true if expected boot message is received (or if RST is unused),
|
|
// false otherwise.
|
|
boolean Adafruit_ESP8266::hardReset(void) {
|
|
if(reset_pin < 0) return true;
|
|
digitalWrite(reset_pin, LOW);
|
|
pinMode(reset_pin, OUTPUT); // Open drain; reset -> GND
|
|
delay(10); // Hold a moment
|
|
pinMode(reset_pin, INPUT); // Back to high-impedance pin state
|
|
return find(bootMarker); // Purge boot message from stream
|
|
}
|
|
|
|
// Soft reset. Returns true if expected boot message received, else false.
|
|
boolean Adafruit_ESP8266::softReset(void) {
|
|
boolean found = false;
|
|
uint32_t save = receiveTimeout; // Temporarily override recveive timeout,
|
|
receiveTimeout = resetTimeout; // reset time is longer than normal I/O.
|
|
println(F("AT+RST")); // Issue soft-reset command
|
|
if(find(bootMarker)) { // Wait for boot message
|
|
println(F("ATE0")); // Turn off echo
|
|
found = find(); // OK?
|
|
}
|
|
receiveTimeout = save; // Restore normal receive timeout
|
|
return found;
|
|
}
|
|
|
|
// For interactive debugging...shuttle data between Serial Console <-> WiFi
|
|
void Adafruit_ESP8266::debugLoop(void) {
|
|
if(!debug) for(;;); // If no debug connection, nothing to do.
|
|
|
|
debug->println(F("\n========================"));
|
|
for(;;) {
|
|
if(debug->available()) stream->write(debug->read());
|
|
if(stream->available()) debug->write(stream->read());
|
|
}
|
|
}
|
|
|
|
// Connect to WiFi access point. SSID and password are flash-resident
|
|
// strings. May take several seconds to execute, this is normal.
|
|
// Returns true on successful connection, false otherwise.
|
|
boolean Adafruit_ESP8266::connectToAP(Fstr *ssid, Fstr *pass) {
|
|
char buf[256];
|
|
|
|
println(F("AT+CWMODE=1")); // WiFi mode = Sta
|
|
readLine(buf, sizeof(buf));
|
|
if(!(strstr_P(buf, (Pchr *)F("OK")) ||
|
|
strstr_P(buf, (Pchr *)F("no change")))) return false;
|
|
|
|
print(F("AT+CWJAP=\"")); // Join access point
|
|
print(ssid);
|
|
print(F("\",\""));
|
|
print(pass);
|
|
println('\"');
|
|
uint32_t save = receiveTimeout; // Temporarily override recv timeout,
|
|
receiveTimeout = connectTimeout; // connection time is much longer!
|
|
boolean found = find(); // Await 'OK' message
|
|
receiveTimeout = save; // Restore normal receive timeout
|
|
if(found) {
|
|
println(F("AT+CIPMUX=0")); // Set single-client mode
|
|
found = find(); // Await 'OK'
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
void Adafruit_ESP8266::closeAP(void) {
|
|
println(F("AT+CWQAP")); // Quit access point
|
|
find(); // Purge 'OK'
|
|
}
|
|
|
|
// Open TCP connection. Hostname is flash-resident string.
|
|
// Returns true on successful connection, else false.
|
|
boolean Adafruit_ESP8266::connectTCP(Fstr *h, int port) {
|
|
|
|
print(F("AT+CIPSTART=\"TCP\",\""));
|
|
print(h);
|
|
print(F("\","));
|
|
println(port);
|
|
|
|
if(find(F("Linked"))) {
|
|
host = h;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Adafruit_ESP8266::closeTCP(void) {
|
|
println(F("AT+CIPCLOSE"));
|
|
find(F("Unlink\r\n"));
|
|
}
|
|
|
|
// Requests page from currently-open TCP connection. URL is
|
|
// flash-resident string. Returns true if request issued successfully,
|
|
// else false. Calling function should then handle data returned, may
|
|
// need to parse IPD delimiters (see notes in find() function.
|
|
// (Can call find(F("Unlink"), true) to dump to debug.)
|
|
boolean Adafruit_ESP8266::requestURL(Fstr *url) {
|
|
print(F("AT+CIPSEND="));
|
|
println(25 + strlen_P((Pchr *)url) + strlen_P((Pchr *)host));
|
|
if(find(F("> "))) { // Wait for prompt
|
|
print(F("GET ")); // 4
|
|
print(url);
|
|
print(F(" HTTP/1.1\r\nHost: ")); // 17
|
|
print(host);
|
|
print(F("\r\n\r\n")); // 4
|
|
return(find()); // Gets 'SEND OK' line
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Requests page from currently-open TCP connection. URL is
|
|
// character string in SRAM. Returns true if request issued successfully,
|
|
// else false. Calling function should then handle data returned, may
|
|
// need to parse IPD delimiters (see notes in find() function.
|
|
// (Can call find(F("Unlink"), true) to dump to debug.)
|
|
boolean Adafruit_ESP8266::requestURL(char* url) {
|
|
print(F("AT+CIPSEND="));
|
|
println(25 + strlen(url) + strlen_P((Pchr *)host));
|
|
if(find(F("> "))) { // Wait for prompt
|
|
print(F("GET ")); // 4
|
|
print(url);
|
|
print(F(" HTTP/1.1\r\nHost: ")); // 17
|
|
print(host);
|
|
print(F("\r\n\r\n")); // 4
|
|
return(find()); // Gets 'SEND OK' line
|
|
}
|
|
return false;
|
|
} |