/* This file is part of the WiFiEspAT library for Arduino https://github.com/jandrassy/WiFiEspAT Copyright 2019 Juraj Andrassy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "EspAtDrv.h" #include "EspAtDrvLogging.h" #if defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F4) #include #define strcmp_P(a, b) strcmp((a), (b)) #define strncmp_P(a, b, n) strncmp((a), (b), (n)) #endif //#define ESPATDRV_ASSUME_FLOW_CONTROL const uint8_t TIMEOUT_COUNT = 3; const uint8_t WIFI_MODE_STA = 0b01; const uint8_t WIFI_MODE_SAP = 0b10; const uint16_t MAX_SEND_LENGTH = 2048; const char OK[] PROGMEM = "OK"; const char STATUS[] PROGMEM = "STATUS"; const char AT_CIPSTATUS[] PROGMEM = "AT+CIPSTATUS"; const char CIPSTATUS[] PROGMEM = "+CIPSTATUS"; const char CIPSTA[] PROGMEM = "+CIPSTA"; const char CIPAP[] PROGMEM = "+CIPAP"; const char CIPETH[] PROGMEM = "+CIPETH"; const char QOUT_COMMA_QOUT[] PROGMEM = "\",\""; const char PROCESSED[] PROGMEM = " ...processed"; const char IGNORED[] PROGMEM = " ...ignored"; #if WIFIESPAT_LOG_LEVEL >= LOG_LEVEL_DEBUG class DebugPrint : public Print { public: Print* stream; bool nl = true; virtual size_t write(uint8_t b) { if (nl) { LOG_DEBUG_PRINT_PREFIX(); nl = false; } if (b == '\n') { nl = true; } LOG_OUTPUT.write(b); return stream->write(b); } } debugPrint; #endif bool EspAtDrvClass::init(Stream* _serial, int8_t resetPin) { serial = _serial; #if WIFIESPAT_LOG_LEVEL < LOG_LEVEL_DEBUG cmd = _serial; #else debugPrint.stream = serial; cmd = &debugPrint; #endif lastErrorCode = EspAtDrvError::NO_ERROR; return reset(resetPin); } bool EspAtDrvClass::reset(int8_t resetPin) { maintain(); LOG_INFO_PRINT_PREFIX(); if (resetPin >= 0) { LOG_INFO_PRINT(F("reset over pin ")); LOG_INFO_PRINTLN(resetPin); } else { LOG_INFO_PRINTLN(F("soft reset")); } if (resetPin >= 0) { pinMode(resetPin, OUTPUT); delay(1); pinMode(resetPin, INPUT); readRX(PSTR("ready")); // can be missed } else { cmd->print(F("AT+RST")); sendCommand(PSTR("ready")); // can be missed } if (!simpleCommand(PSTR("ATE0")) || // turn off echo. must work !simpleCommand(PSTR("AT+CIPMUX=1")) || // Enable multiple connections. !simpleCommand(PSTR("AT+CIPRECVMODE=1"))) // Set TCP Receive Mode - passive return false; #ifndef WIFIESPAT1 //AT2 if (!simpleCommand(PSTR("AT+SYSSTORE=0"))) {// our default is persistent false persistent = true; LOG_WARN_PRINT_PREFIX(); LOG_WARN_PRINTLN(F("Error setting store mode. Is the firmware AT2?")); } #endif // read default wifi mode cmd->print(F("AT+CWMODE?")); if (!sendCommand(PSTR("+CWMODE"))) return false; wifiMode = buffer[strlen("+CWMODE:")] - 48; if (!readOK()) return false; wifiModeDef = wifiMode; return true; } void EspAtDrvClass::maintain() { lastErrorCode = EspAtDrvError::NO_ERROR; readRX(nullptr, false); } bool EspAtDrvClass::firmwareVersion(char* buff) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("fw version")); cmd->print(F("AT+GMR")); if (!sendCommand(PSTR("AT version:"))) return false; char* start = buffer + strlen("AT version:"); size_t l = strchr(start, '(') - start; strncpy(buff, start, l); buff[l] = 0; return readOK(); } bool EspAtDrvClass::sysPersistent(bool _persistent) { #ifdef WIFIESPAT1 persistent = _persistent; return true; #else LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("sys store ")); LOG_INFO_PRINTLN(_persistent ? F("on") : F("off")); if (sysStoreInternal(_persistent)) { persistent = _persistent; return true; } return false; #endif } //AT2 bool EspAtDrvClass::sysStoreInternal(bool store) { cmd->print(F("AT+SYSSTORE=")); cmd->print(store ? 1 : 0); return sendCommand(); } int EspAtDrvClass::staStatus() { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("wifi status")); if (wifiModeDef == -1) { // reset() was not executed successful LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("AT firmware was not initialized")); lastErrorCode = EspAtDrvError::NOT_INITIALIZED; return -1; } cmd->print((FSH_P) AT_CIPSTATUS); if (!sendCommand(STATUS)) return -1; uint8_t status = buffer[strlen("STATUS:")] - 48; return readOK() ? status : -1; } int EspAtDrvClass::ethStatus() { maintain(); return ethConnected; } uint8_t EspAtDrvClass::listAP(WiFiApData apData[], uint8_t size) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("list AP")); uint8_t mode = wifiMode | WIFI_MODE_STA; // turn on STA, leave SoftAP as it is if (!setWifiMode(mode, false)) return 0; if (!simpleCommand(PSTR("AT+CWLAPOPT=1,31"))) // sort and first 5 values (0b0000011111) return false; cmd->print(F("AT+CWLAP")); uint8_t count = 0; bool found = sendCommand(PSTR("+CWLAP"), true, true); while (found) { WiFiApData& r = apData[count]; const char* delims = ",:\")"; char* tok = strtok(buffer + strlen("+CWLAP:("), delims); // r.enc = atoi(tok); tok = strtok(NULL, delims); // strcpy(r.ssid, tok); tok = strtok(NULL, delims); // r.rssi = atoi(tok); for (int i = 5; i >= 0; i--) { tok = strtok(NULL, delims); // [i] r.bssid[i] = strtoul(tok, NULL, 16); } tok = strtok(NULL, delims); // r.channel = atoi(tok); count++; if (count == size) break; found = readRX(PSTR("+CWLAP"), true, true); } if (count == size) { readOK(); // skip the rest of results } return count; } bool EspAtDrvClass::staStaticIp(const IPAddress& ip, const IPAddress& gw, const IPAddress& nm) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("set static IP ")); LOG_INFO_PRINT(ip); LOG_INFO_PRINTLN(persistent ? F(" persistent") : F(" current") ); uint8_t mode = wifiMode | WIFI_MODE_STA; // turn on STA, leave SoftAP as it is if (!setWifiMode(mode, false)) return false; // can't set ip without sta mode #ifdef WIFIESPAT1 if (persistent) { #endif cmd->print(F("AT+CIPSTA=\"")); #ifdef WIFIESPAT1 } else { cmd->print(F("AT+CIPSTA_CUR=\"")); } #endif ip.printTo(*cmd); if (gw[0]) { cmd->print((FSH_P) QOUT_COMMA_QOUT); gw.printTo(*cmd); if (nm[0]) { cmd->print((FSH_P) QOUT_COMMA_QOUT); nm.printTo(*cmd); } } cmd->print('"'); return sendCommand(); } bool EspAtDrvClass::setDNS(const IPAddress& dns1, const IPAddress& dns2) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("set static DNS ")); LOG_INFO_PRINTLN(persistent ? F("persistent") : F("current") ); #ifdef WIFIESPAT1 // AT+CIPDNS doesn't work in AT 1 if (persistent) { cmd->print(F("AT+CIPDNS_DEF=")); } else { cmd->print(F("AT+CIPDNS_CUR=")); } #else cmd->print(F("AT+CIPDNS=")); #endif if (!dns1[0]) { cmd->print(0); } else { cmd->print(F("1,\"")); dns1.printTo(*cmd); if (dns2[0]) { cmd->print((FSH_P) QOUT_COMMA_QOUT); dns2.printTo(*cmd); } cmd->print('"'); } return sendCommand(); } bool EspAtDrvClass::staMacQuery(uint8_t* mac) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("STA MAC query ")); cmd->print(F("AT+CIPSTAMAC?")); if (!sendCommand(PSTR("+CIPSTAMAC"))) return false; const char* delims = ":\""; char* tok = strtok(buffer, delims); // +CIPSTAMAC:" for (int i = 5; i >= 0; i--) { tok = strtok(NULL, delims); // [i] mac[i] = strtol(tok, NULL, 16); } return readOK(); } bool EspAtDrvClass::staIpQuery(IPAddress& ip, IPAddress& gwip, IPAddress& mask) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("STA IP query")); cmd->print(F("AT+CIPSTA?")); if (!sendCommand(CIPSTA)) return false; buffer[strlen(buffer) - 1] = 0; // delete last char '\"' ip.fromString(buffer + strlen("+CIPSTA:ip:\"")); if (!readRX(CIPSTA)) return false; buffer[strlen(buffer) - 1] = 0; gwip.fromString(buffer + strlen("+CIPSTA:gateway:\"")); if (!readRX(CIPSTA)) return false; buffer[strlen(buffer) - 1] = 0; mask.fromString(buffer + strlen("+CIPSTA:netmask:\"")); return readOK(); } bool EspAtDrvClass::dnsQuery(IPAddress& dns1, IPAddress& dns2) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("DNS query")); #ifdef WIFIESPAT1 // AT+CIPDNS? has different output in AT 2 cmd->print(F("AT+CIPDNS_CUR?")); if (!sendCommand(PSTR("+CIPDNS_CUR"), true, true)) return false; dns1.fromString(buffer + strlen("+CIPDNS_CUR:")); if (!readRX(PSTR("+CIPDNS_CUR"), true, true)) return true; // second dns is not set dns2.fromString(buffer + strlen("+CIPDNS_CUR:")); #else cmd->print(F("AT+CIPDNS?")); if (!sendCommand(PSTR("+CIPDNS"), true)) return false; const char* delims = ",\""; char* tok = strtok(buffer, delims); tok = strtok(NULL, delims); // skip +CIPDNS:, if (tok != NULL) { dns1.fromString(tok); tok = strtok(NULL, delims); if (tok != NULL) { dns2.fromString(tok); } } #endif return readOK(); } bool EspAtDrvClass::joinAP(const char* ssid, const char* password, const uint8_t* bssid) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("join AP ")); LOG_INFO_PRINT(ssid); LOG_INFO_PRINTLN(persistent ? F(" persistent") : F(" current") ); if (!setWifiMode(wifiMode | WIFI_MODE_STA, persistent)) return false; // can't join ap without sta mode #ifdef WIFIESPAT1 if (persistent) { #endif cmd->print(F("AT+CWJAP")); #ifdef WIFIESPAT1 } else { cmd->print(F("AT+CWJAP_CUR")); } #endif if (ssid) { cmd->print(F("=\"")); cmd->print(ssid); cmd->print((FSH_P) QOUT_COMMA_QOUT); if (password) { cmd->print(password); if (bssid) { cmd->print((FSH_P) QOUT_COMMA_QOUT); for (int i = 5; i >= 0; i--) { if (bssid[i] < 16) { cmd->print('0'); } cmd->print(bssid[i], HEX); if (i > 0) { cmd->print(':'); } } } } cmd->print('"'); } if (!sendCommand()) return false; if (persistent) { simpleCommand(PSTR("AT+CWAUTOCONN=1")); } return true; } bool EspAtDrvClass::joinEAP(const char* ssid, uint8_t method, const char* identity, const char* username, const char* password, uint8_t security) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("join Enterprise AP ")); LOG_INFO_PRINT(ssid); LOG_INFO_PRINTLN(persistent ? F(" persistent") : F(" current") ); if (!setWifiMode(wifiMode | WIFI_MODE_STA, persistent)) return false; // can't join ap without sta mode cmd->print(F("AT+CWJEAP=\"")); cmd->print(ssid); cmd->print(','); cmd->print(method); cmd->print(','); if (identity) { cmd->print('"'); cmd->print(identity); cmd->print('"'); } cmd->print(','); if (username) { cmd->print('"'); cmd->print(username); cmd->print('"'); } cmd->print(','); if (password) { cmd->print('"'); cmd->print(password); cmd->print('"'); } cmd->print(','); cmd->print(security); if (!sendCommand()) return false; if (persistent) { simpleCommand(PSTR("AT+CWAUTOCONN=1")); } return true; } bool EspAtDrvClass::quitAP(bool save) { LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("quit AP ")); LOG_INFO_PRINTLN((persistent || save) ? F(" persistent") : F(" current") ); if (!(wifiMode & WIFI_MODE_STA)) { // STA is off LOG_WARN_PRINT_PREFIX(); LOG_WARN_PRINTLN(F("STA is off")); return false; } #ifdef WIFIESPAT1 // AT 1 AT+CWDHCP= parameters are strange. first parameter is 0, 1, 2. second is 0/1 if (persistent || save) { simpleCommand(PSTR("AT+CWAUTOCONN=0")); // don't reconnect on reset simpleCommand(PSTR("AT+CIPDNS_DEF=0")); // clear static DNS servers simpleCommand(PSTR("AT+CWDHCP=1,1")); // enable DHCP back in case static IP disabled it } else { simpleCommand(PSTR("AT+CIPDNS_CUR=0")); // clear static DNS servers simpleCommand(PSTR("AT+CWDHCP_CUR=1,1")); // enable DHCP back in case static IP disabled it } #else // AT 2 AT+CWDHCP= first parameter is 0/1 and second parameter are bits for net. interfaces if (persistent != save && !sysStoreInternal(save)) return false; if (persistent || save) { simpleCommand(PSTR("AT+CWAUTOCONN=0")); // don't reconnect on reset. always stored } simpleCommand(PSTR("AT+CIPDNS=0")); // clear static DNS servers simpleCommand(PSTR("AT+CWDHCP=1,1")); // enable STA DHCP back in case static IP disabled it if (persistent != save && !sysStoreInternal(persistent)) { persistent = save; } #endif return simpleCommand(PSTR("AT+CWQAP")); // it doesn't clear the persistent settings } bool EspAtDrvClass::staAutoConnect(bool autoConnect) { LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("STA auto connect ")); LOG_INFO_PRINTLN(autoConnect ? F("on") : F("off")); cmd->print(F("AT+CWAUTOCONN=")); cmd->print(autoConnect ? 1 : 0); return sendCommand(); } bool EspAtDrvClass::apQuery(char* ssid, uint8_t* bssid, uint8_t& channel, int32_t& rssi) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("AP query")); if (ssid) { ssid[0] = 0; } if (!(wifiMode & WIFI_MODE_STA)) { // STA is off LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("STA is off")); return false; } cmd->print(F("AT+CWJAP?")); if (!sendCommand(PSTR("+CWJAP"))) return false; const char* delims = ",:\""; char* tok = strtok(buffer + strlen("+CWJAP:\""), delims); // if (ssid != nullptr) { strcpy(ssid, tok); } for (int i = 5; i >= 0; i--) { tok = strtok(NULL, delims); // [i] bssid[i] = strtol(tok, NULL, 16); } tok = strtok(NULL, delims); // channel = atoi(tok); tok = strtok(NULL, delims); // rssi = atol(tok); return readOK(); } bool EspAtDrvClass::softApIp(const IPAddress& ip, const IPAddress& gw, const IPAddress& nm) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("set SoftAP IP ")); LOG_INFO_PRINT(ip); LOG_INFO_PRINTLN(persistent ? F(" persistent") : F(" current") ); uint8_t origMode = wifiMode; uint8_t mode = wifiMode | WIFI_MODE_SAP; // turn on SoftAP temporally, leave STA as it is if (!setWifiMode(mode)) return false; // can't set ip without ap mode #ifdef WIFIESPAT1 if (persistent) { #endif cmd->print(F("AT+CIPAP=\"")); #ifdef WIFIESPAT1 } else { cmd->print(F("AT+CIPAP_CUR=\"")); } #endif ip.printTo(*cmd); if (gw[0]) { cmd->print((FSH_P) QOUT_COMMA_QOUT); gw.printTo(*cmd); if (nm[0]) { cmd->print((FSH_P) QOUT_COMMA_QOUT); nm.printTo(*cmd); } } cmd->print('"'); bool ok = sendCommand(); setWifiMode(origMode); return ok; } bool EspAtDrvClass::softApMacQuery(uint8_t* mac) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("SoftAP MAC query ")); cmd->print(F("AT+CIPAPMAC?")); if (!sendCommand(PSTR("+CIPAPMAC"))) return false; const char* delims = ":\""; char* tok = strtok(buffer, delims); // +CIPAPMAC:" for (int i = 5; i >= 0; i--) { tok = strtok(NULL, delims); // [i] mac[i] = strtol(tok, NULL, 16); } return readOK(); } bool EspAtDrvClass::softApIpQuery(IPAddress& ip, IPAddress& gwip, IPAddress& mask) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("SoftAP IP query")); cmd->print(F("AT+CIPAP?")); if (!sendCommand(CIPAP)) return false; buffer[strlen(buffer) - 1] = 0; // delete last char '\"' ip.fromString(buffer + strlen("+CIPAP:ip:\"")); if (!readRX(CIPAP)) return false; buffer[strlen(buffer) - 1] = 0; gwip.fromString(buffer + strlen("+CIPAP:gateway:\"")); if (!readRX(CIPAP)) return false; buffer[strlen(buffer) - 1] = 0; mask.fromString(buffer + strlen("+CIPAP:netmask:\"")); return readOK(); } bool EspAtDrvClass::beginSoftAP(const char *ssid, const char* passphrase, uint8_t channel, uint8_t encoding, uint8_t maxConnetions, bool hidden) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("begin SoftAP ")); LOG_INFO_PRINTLN(persistent ? F("persistent") : F("current") ); uint8_t mode = wifiMode | WIFI_MODE_SAP; // turn on SoftAP, leave STA as it is if (!setWifiMode(mode, false)) // not persistent on purpose return false; if (!ssid) // only start the SoftAp as it is configured return true; #ifdef WIFIESPAT1 if (persistent) { #endif cmd->print(F("AT+CWSAP=\"")); #ifdef WIFIESPAT1 } else { cmd->print(F("AT+CWSAP_CUR=\"")); } #endif cmd->print(ssid); cmd->print((FSH_P) QOUT_COMMA_QOUT); if (passphrase) { cmd->print(passphrase); } cmd->print("\","); cmd->print(channel); cmd->print(','); cmd->print(passphrase ? encoding : 0); if (maxConnetions) { cmd->print(','); cmd->print(maxConnetions); cmd->print(','); cmd->print(hidden ? 1 : 0); } return sendCommand(); } bool EspAtDrvClass::endSoftAP(bool save) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("end SoftAP ")); LOG_INFO_PRINTLN((persistent || save) ? F("persistent") : F("current") ); return setWifiMode(0, persistent || save); // 0 sets STA mode } bool EspAtDrvClass::softApQuery(char* ssid, char* passphrase, uint8_t& channel, uint8_t& encoding, uint8_t& maxConnections, bool& hidden) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("SoftAP query")); if (!(wifiMode & WIFI_MODE_SAP)) { // SoftAP is off LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("SoftAP is off")); return false; } cmd->print(F("AT+CWSAP?")); if (!sendCommand(PSTR("+CWSAP"))) return false; const char* delims = ","; char* tok = strtok(buffer + strlen("+CWSAP:\""), "\""); // if (ssid != nullptr) { strcpy(ssid, tok); } tok = strtok(NULL, delims); // tok[strlen(tok) - 1] = 0; // strip " if (passphrase != nullptr) { strcpy(passphrase, tok + 1); } tok = strtok(NULL, delims); // channel = atoi(tok); tok = strtok(NULL, delims); // encoding = atoi(tok); tok = strtok(NULL, delims); // maxConnections = atol(tok); tok = strtok(NULL, delims); // hidden = atol(tok); return readOK(); } bool EspAtDrvClass::ethSetMac(uint8_t* mac) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("set ETH MAC ")); // LOG_INFO_PRINT(ip); LOG_INFO_PRINTLN(persistent ? F(" persistent") : F(" current") ); #ifdef WIFIESPAT1 if (persistent) { #endif cmd->print(F("AT+CIPETHMAC=\"")); #ifdef WIFIESPAT1 } else { cmd->print(F("AT+CIPETHMAC_CUR=\"")); } #endif printMAC(cmd, mac); cmd->print('"'); return sendCommand(); } bool EspAtDrvClass::ethStaticIp(const IPAddress& ip, const IPAddress& gw, const IPAddress& nm) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("set ETH static IP ")); LOG_INFO_PRINT(ip); LOG_INFO_PRINTLN(persistent ? F(" persistent") : F(" current") ); #ifdef WIFIESPAT1 if (persistent) { #endif cmd->print(F("AT+CIPETH=\"")); #ifdef WIFIESPAT1 } else { cmd->print(F("AT+CIPETH_CUR=\"")); } #endif ip.printTo(*cmd); if (gw[0]) { cmd->print((FSH_P) QOUT_COMMA_QOUT); gw.printTo(*cmd); if (nm[0]) { cmd->print((FSH_P) QOUT_COMMA_QOUT); nm.printTo(*cmd); } } cmd->print('"'); return sendCommand(); } bool EspAtDrvClass::ethEnableDHCP() { LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("enable Eth DHCP ")); LOG_INFO_PRINTLN(persistent ? F("persistent") : F("current") ); #ifdef WIFIESPAT1 if (persistent) { return simpleCommand(PSTR("AT+CWDHCP=3,1")); } return simpleCommand(PSTR("AT+CWDHCP_CUR=3,1")); #else return simpleCommand(PSTR("AT+CWDHCP=1,4")); #endif } bool EspAtDrvClass::ethMacQuery(uint8_t* mac) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("ETH MAC query ")); cmd->print(F("AT+CIPETHMAC?")); if (!sendCommand(PSTR("+CIPETHMAC"))) return false; const char* delims = ":\""; char* tok = strtok(buffer, delims); // +CIPETHMAC:" for (int i = 0; i < 6; i++) { tok = strtok(NULL, delims); // [i] mac[i] = strtol(tok, NULL, 16); } return readOK(); } bool EspAtDrvClass::ethIpQuery(IPAddress& ip, IPAddress& gwip, IPAddress& mask) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("ETH IP query")); cmd->print(F("AT+CIPETH?")); if (!sendCommand(CIPETH)) return false; buffer[strlen(buffer) - 1] = 0; // delete last char '\"' ip.fromString(buffer + strlen("+CIPETH:ip:\"")); if (!readRX(CIPETH)) return false; buffer[strlen(buffer) - 1] = 0; gwip.fromString(buffer + strlen("+CIPETH:gateway:\"")); if (!readRX(CIPETH)) return false; buffer[strlen(buffer) - 1] = 0; mask.fromString(buffer + strlen("+CIPETH:netmask:\"")); return readOK(); } bool EspAtDrvClass::setEthHostname(const char* hostname) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("set eth hostname ")); LOG_INFO_PRINTLN(hostname); cmd->print(F("AT+CEHOSTNAME=\"")); cmd->print(hostname); cmd->print('"'); return sendCommand(); } bool EspAtDrvClass::ethHostnameQuery(char* hostname) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("eth hostname query")); cmd->print(F("AT+CEHOSTNAME?")); if (!sendCommand(PSTR("+CEHOSTNAME"))) return false; strcpy(hostname, buffer + strlen("+CEHOSTNAME:")); return readOK(); } bool EspAtDrvClass::serverBegin(uint16_t port, uint8_t maxConnCount, uint16_t serverTimeout, bool ssl, bool ca) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("begin server at port ")); LOG_INFO_PRINTLN(port); cmd->print(F("AT+CIPSERVERMAXCONN=")); cmd->print(maxConnCount); if (!sendCommand()) return false; cmd->print(F("AT+CIPSERVER=1,")); cmd->print(port); if (ssl) { cmd->print(F(",\"SSL\",")); cmd->print(ca); } if (!sendCommand()) return false; cmd->print(F("AT+CIPSTO=")); cmd->print(serverTimeout); return sendCommand(); } bool EspAtDrvClass::serverEnd(uint16_t port) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("stop server")); #ifdef WIFIESPAT_MULTISERVER cmd->print(F("AT+CIPSERVER=0,")); cmd->print(port); return sendCommand(); #else return simpleCommand(PSTR("AT+CIPSERVER=0")); #endif } uint8_t EspAtDrvClass::clientLinkId(uint16_t serverPort, bool accept) { maintain(); for (int linkId = 0; linkId < LINKS_COUNT; linkId++) { LinkInfo& link = linkInfo[linkId]; if (link.isConnected() && link.isIncoming() && !link.isClosing() && !link.isAccepted() && (link.available || accept)) { #ifdef WIFIESPAT_MULTISERVER if (!link.localPort) { checkLinks(); } if (serverPort != link.localPort) continue; #endif LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("incoming linkId ")); LOG_INFO_PRINTLN(linkId); if (accept) { link.flags |= LINK_IS_ACCEPTED; } return linkId; } } return NO_LINK; } uint8_t EspAtDrvClass::clientLinkIds(uint16_t serverPort, uint8_t linkIds[]) { maintain(); uint8_t l = 0; for (int linkId = 0; linkId < LINKS_COUNT; linkId++) { LinkInfo& link = linkInfo[linkId]; if (link.isConnected() && link.isIncoming() && !link.isClosing() && !link.isAccepted()) { #ifdef WIFIESPAT_MULTISERVER if (!link.localPort) { checkLinks(); } if (serverPort != link.localPort) continue; #endif linkIds[l] = linkId; l++; } } LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(l); LOG_INFO_PRINTLN(F(" link ids for server")); return l; } uint8_t EspAtDrvClass::connect(const char* type, const char* host, uint16_t port, #ifdef WIFIESPAT1 EspAtDrvUdpDataCallback* udpDataCallback, #endif uint16_t udpLocalPort) { maintain(); uint8_t linkId = freeLinkId(); if (linkId == NO_LINK) return NO_LINK; LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("start ")); LOG_INFO_PRINT(type); LOG_INFO_PRINT(F(" to ")); LOG_INFO_PRINT(host); LOG_INFO_PRINT(':'); LOG_INFO_PRINT(port); LOG_INFO_PRINT(F(" on link ")); LOG_INFO_PRINTLN(linkId); LinkInfo& link = linkInfo[linkId]; if (link.isConnected()) { LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("linkId ")); LOG_ERROR_PRINT(linkId); LOG_ERROR_PRINTLN(F("is already connected.")); lastErrorCode = EspAtDrvError::LINK_ALREADY_CONNECTED; return NO_LINK; } cmd->print(F("AT+CIPSTART=")); cmd->print(linkId); cmd->print(F(",\"")); cmd->print(type); cmd->print((FSH_P) QOUT_COMMA_QOUT); cmd->print(host); cmd->print(F("\",")); cmd->print(port); if (udpLocalPort != 0) { cmd->print(','); cmd->print(udpLocalPort); cmd->print(",2"); #ifdef WIFIESPAT_MULTISERVER link.localPort = udpLocalPort; } else { link.localPort = 0; #endif } link.flags = LINK_CONNECTED; if (!sendCommand()) { link.flags = 0; return NO_LINK; } if (udpLocalPort != 0) { link.flags |= LINK_IS_UDP_LISTNER; #ifdef WIFIESPAT1 link.udpDataCallback = udpDataCallback; #endif } return linkId; } bool EspAtDrvClass::close(uint8_t linkId, bool abort) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("close link ")); LOG_INFO_PRINTLN(linkId); LinkInfo& link = linkInfo[linkId]; link.available = 0; if (!link.isConnected()) { LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("link is already closed")); return true; } link.flags |= LINK_CLOSING; if (abort) { cmd->print(F("AT+CIPCLOSEMODE=")); cmd->print(linkId); cmd->print(",1"); sendCommand(); } cmd->print(F("AT+CIPCLOSE=")); cmd->print(linkId); return sendCommand(); } uint16_t EspAtDrvClass::localPortQuery(uint8_t linkId) { #ifdef WIFIESPAT_MULTISERVER if (linkInfo[linkId].localPort != 0) return linkInfo[linkId].localPort; #endif IPAddress remoteIP; uint16_t remotePort; uint16_t localPort = 0; remoteParamsQuery(linkId, remoteIP, remotePort, localPort); return localPort; } bool EspAtDrvClass::remoteParamsQuery(uint8_t linkId, IPAddress& remoteIP, uint16_t& remotePort, uint16_t& localPort) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("status of link ")); LOG_INFO_PRINTLN(linkId); LinkInfo& link = linkInfo[linkId]; if (link.isConnected()) { cmd->print((FSH_P) AT_CIPSTATUS); if (!sendCommand(STATUS)) return false; while (readRX(CIPSTATUS, true, true)) { uint8_t id = buffer[strlen("+CIPSTATUS:")] - 48; if (id == linkId) { const char* delim = ",\""; char* tok = strtok(buffer, delim); // +CIPSTATUS: tok = strtok(NULL, delim); // tok = strtok(NULL, delim); // remoteIP.fromString(tok); tok = strtok(NULL, delim); // remotePort = atoi(tok); tok = strtok(NULL, delim); // localPort = atoi(tok); #ifdef WIFIESPAT_MULTISERVER linkInfo[linkId].localPort = localPort; #endif readOK(); return true; } } } LOG_WARN_PRINT_PREFIX(); LOG_WARN_PRINTLN(F("link is not active")); link.flags = 0; lastErrorCode = EspAtDrvError::LINK_NOT_ACTIVE; return false; } bool EspAtDrvClass::connected(uint8_t linkId) { maintain(); LinkInfo& link = linkInfo[linkId]; return link.isConnected() && !link.isClosing(); } size_t EspAtDrvClass::availData(uint8_t linkId) { maintain(); LinkInfo& link = linkInfo[linkId]; #ifndef ESPATDRV_ASSUME_FLOW_CONTROL if (link.available == 0 && link.isConnected() && !link.isClosing()) { syncLinkInfo(); } #endif return link.available; } size_t EspAtDrvClass::recvData(uint8_t linkId, uint8_t data[], size_t buffSize) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("get data on link ")); LOG_INFO_PRINTLN(linkId); LinkInfo& link = linkInfo[linkId]; if (link.available == 0) { LOG_WARN_PRINT_PREFIX(); if (!link.isConnected()) { LOG_WARN_PRINTLN(F("link is not active")); lastErrorCode = EspAtDrvError::LINK_NOT_ACTIVE; } else { LOG_WARN_PRINTLN(F("no data for link")); } return 0; } cmd->print(F("AT+CIPRECVDATA=")); cmd->print(linkId); cmd->print(','); cmd->print(buffSize); if (!sendCommand(PSTR("+CIPRECVDATA"), false)) { #ifndef WIFIESPAT1 //AT2 if (link.available == 0) // AT2 SSL reports more data available and closes the connection to indicate end of data return 0; #endif LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("error receiving on link ")); LOG_ERROR_PRINTLN(linkId); link.available = 0; lastErrorCode = EspAtDrvError::RECEIVE; return 0; } #ifdef WIFIESPAT1 size_t len = atol(buffer + strlen("+CIPRECVDATA,")); // AT 1.7.x has : after (not matching the doc) #else size_t lt = serial->readBytesUntil(',', buffer, 6); buffer[lt] = 0; size_t len = atol(buffer); #endif size_t l = serial->readBytes(data, len); if (l != len) { //timeout LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("error receiving on link ")); LOG_ERROR_PRINTLN(linkId); link.available = 0; lastErrorCode = EspAtDrvError::RECEIVE; return 0; } if (len > link.available) { link.available = 0; } else { link.available -= len; } readOK(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("\tgot ")); LOG_INFO_PRINT(len); LOG_INFO_PRINT(F(" bytes on link ")); LOG_INFO_PRINTLN(linkId); return len; } //for AT2 size_t EspAtDrvClass::recvDataWithInfo(uint8_t linkId, uint8_t data[], size_t buffSize, IPAddress& remoteIp, uint16_t& remotePort) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("get data and info on link ")); LOG_INFO_PRINTLN(linkId); LinkInfo& link = linkInfo[linkId]; if (link.available == 0) { LOG_WARN_PRINT_PREFIX(); if (!link.isConnected()) { LOG_WARN_PRINTLN(F("link is not active")); lastErrorCode = EspAtDrvError::LINK_NOT_ACTIVE; } else { LOG_WARN_PRINTLN(F("no data for link")); } return 0; } size_t len = buffSize; if (link.isUdpListener()) { if (buffSize > link.available) { len = link.available; // to not read data of next message } else if (buffSize < link.available) { // in AT2 UDP passive mode the message must be read at once LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("UDP message on link ")); LOG_ERROR_PRINT(linkId); LOG_ERROR_PRINT(F(" size ")); LOG_ERROR_PRINT(link.available); LOG_ERROR_PRINT(F(" is larger then ")); LOG_ERROR_PRINTLN(buffSize); lastErrorCode = EspAtDrvError::UDP_LARGE; link.available = len; // the rest of message will not be available } } if (!simpleCommand(PSTR("AT+CIPDINFO=1"))) return 0; cmd->print(F("AT+CIPRECVDATA=")); cmd->print(linkId); cmd->print(','); cmd->print(len); if (!sendCommand(PSTR("+CIPRECVDATA"), false)) { LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("error receiving on link ")); LOG_ERROR_PRINTLN(linkId); link.available = 0; lastErrorCode = EspAtDrvError::RECEIVE; } else { size_t l = serial->readBytesUntil(',', buffer, 6); buffer[l] = 0; len = atol(buffer); l = serial->readBytesUntil(',', buffer, 18); // IP in quotes if (l > 0) { buffer[l - 1] = 0; remoteIp.fromString(buffer + 1); } l = serial->readBytesUntil(',', buffer, 6); buffer[l] = 0; remotePort = atol(buffer); l = serial->readBytes(data, len); if (l != len) { //timeout LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("error receiving on link ")); LOG_ERROR_PRINTLN(linkId); link.available = 0; lastErrorCode = EspAtDrvError::RECEIVE; len = 0; } else { if (len > link.available) { link.available = 0; } else { link.available -= len; } readOK(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("\tgot ")); LOG_INFO_PRINT(len); LOG_INFO_PRINT(F(" bytes on link ")); LOG_INFO_PRINTLN(linkId); } } simpleCommand(PSTR("AT+CIPDINFO=0")); return len; } size_t EspAtDrvClass::sendData(uint8_t linkId, const uint8_t data[], size_t len, const char* udpHost, uint16_t udpPort) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("send data on link ")); LOG_INFO_PRINTLN(linkId); if (!linkInfo[linkId].isConnected()) { LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("link is not connected.")); lastErrorCode = EspAtDrvError::LINK_NOT_ACTIVE; return 0; } cmd->print(F("AT+CIPSEND=")); cmd->print(linkId); cmd->print(','); cmd->print(len); if (udpHost != nullptr) { cmd->print(F(",\"")); cmd->print(udpHost); cmd->print(F("\",")); cmd->print(udpPort); } if (!sendCommand(PSTR(">"))) return 0; serial->write(data, len); if (!readRX(PSTR("Recv "))) return 0; size_t l = atol(buffer + strlen("Recv ")); if (!readRX(PSTR("SEND "))) // SEND OK or SEND FAIL return 0; if (strcmp_P(buffer + strlen("SEND "), OK) != 0) {// FAIL LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("failed to send data")); lastErrorCode = EspAtDrvError::SEND; return 0; } LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("\tsent ")); LOG_INFO_PRINT(l); LOG_INFO_PRINT(F(" bytes on link ")); LOG_INFO_PRINTLN(linkId); return l; } size_t EspAtDrvClass::sendData(uint8_t linkId, Stream& file, const char* udpHost, uint16_t udpPort) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("send stream on link ")); LOG_INFO_PRINTLN(linkId); if (!linkInfo[linkId].isConnected()) { LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("link is not connected.")); lastErrorCode = EspAtDrvError::LINK_NOT_ACTIVE; return 0; } uint32_t len = 0; while (file.available()) { size_t l = file.available(); if (l > MAX_SEND_LENGTH) { l = MAX_SEND_LENGTH; } cmd->print(F("AT+CIPSEND=")); cmd->print(linkId); cmd->print(','); cmd->print(l); if (udpHost != nullptr) { cmd->print(F(",\"")); cmd->print(udpHost); cmd->print(F("\",")); cmd->print(udpPort); } if (!sendCommand(PSTR(">"))) { LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("CIPSEND failed at ")); LOG_ERROR_PRINTLN(len); lastErrorCode = EspAtDrvError::SEND; return 0; } for (size_t i = 0; i < l; i++) { serial->write(file.read()); } if (!readRX(PSTR("Recv "))) return 0; size_t sl = atol(buffer + strlen("Recv ")); len += sl; if (!readRX(PSTR("SEND ")) || strcmp_P(buffer + strlen("SEND "), OK) != 0) {// FAIL LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("failed to send data at ")); LOG_ERROR_PRINTLN(len); lastErrorCode = EspAtDrvError::SEND; return 0; } if (l == MAX_SEND_LENGTH && sl < MAX_SEND_LENGTH) { LOG_WARN_PRINT_PREFIX(); LOG_WARN_PRINT(F("Retardment of sending data at ")); LOG_WARN_PRINTLN(len); } } LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("\tsent ")); LOG_INFO_PRINT(len); LOG_INFO_PRINT(F(" bytes on link ")); LOG_INFO_PRINTLN(linkId); return len; } size_t EspAtDrvClass::sendData(uint8_t linkId, SendCallbackFnc callback, const char* udpHost, uint16_t udpPort) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("send with callback on link ")); LOG_INFO_PRINTLN(linkId); if (!linkInfo[linkId].isConnected()) { LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("link is not connected.")); lastErrorCode = EspAtDrvError::LINK_NOT_ACTIVE; return 0; } cmd->print(F("AT+CIPSENDEX=")); cmd->print(linkId); cmd->print(','); cmd->print(MAX_SEND_LENGTH); if (udpHost != nullptr) { cmd->print(F(",\"")); cmd->print(udpHost); cmd->print(F("\",")); cmd->print(udpPort); } if (!sendCommand(PSTR(">"))) return 0; callback(*serial); delay(20); // mandatory delay before \\0 serial->print("\\0"); //end data if (!readRX(PSTR("Recv "))) return 0; size_t l = atol(buffer + strlen("Recv ")); if (!readRX(PSTR("SEND "))) // SEND OK or SEND FAIL return 0; if (strcmp_P(buffer + strlen("SEND "), OK) != 0) {// FAIL LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("failed to send data")); lastErrorCode = EspAtDrvError::SEND; return 0; } LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("\tsent ")); LOG_INFO_PRINT(l); LOG_INFO_PRINT(F(" bytes on link ")); LOG_INFO_PRINTLN(linkId); return l; } bool EspAtDrvClass::setHostname(const char* hostname) { maintain(); uint8_t mode = wifiMode | WIFI_MODE_STA; // turn on STA, leave SoftAP as it is if (!setWifiMode(mode, false)) return false; // can't set hostname without sta mode LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("set hostname ")); LOG_INFO_PRINTLN(hostname); cmd->print(F("AT+CWHOSTNAME=\"")); cmd->print(hostname); cmd->print('"'); return sendCommand(); } bool EspAtDrvClass::hostnameQuery(char* hostname) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("hostname query")); cmd->print(F("AT+CWHOSTNAME?")); if (!sendCommand(PSTR("+CWHOSTNAME"))) return false; strcpy(hostname, buffer + strlen("+CWHOSTNAME:")); return readOK(); } bool EspAtDrvClass::dhcpStateQuery(bool& staDHCP, bool& softApDHCP, bool& ethDHCP) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("DHCP state query")); cmd->print(F("AT+CWDHCP?")); if (!sendCommand(PSTR("+CWDHCP"))) return false; uint8_t state = buffer[strlen("+CWDHCP:")] - 48; #ifdef WIFIESPAT1 // AT1 v. AT2 state bits are swapped softApDHCP = state & 0b01; staDHCP = state & 0b10; #else softApDHCP = state & 0b10; staDHCP = state & 0b01; #endif ethDHCP = state & 0b100; return readOK(); } bool EspAtDrvClass::mDNS(const char* hostname, const char* serverName, uint16_t serverPort) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("start MDNS")); cmd->print(F("AT+MDNS=1,\"")); cmd->print(hostname); cmd->print((FSH_P) QOUT_COMMA_QOUT); cmd->print(serverName); cmd->print("\","); cmd->print(serverPort); return sendCommand(); } bool EspAtDrvClass::resolve(const char* hostname, IPAddress& result) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("resolve ip")); cmd->print(F("AT+CIPDOMAIN=\"")); cmd->print(hostname); cmd->print('"'); if (!sendCommand(PSTR("+CIPDOMAIN"))) return false; #ifdef WIFIESPAT1 result.fromString(buffer + strlen("+CIPDOMAIN:")); #else buffer[strlen(buffer) - 1] = 0; // delete last char '\"' result.fromString(buffer + strlen("+CIPDOMAIN:\"")); #endif return readOK(); } bool EspAtDrvClass::sntpCfg(const char* server1, const char* server2) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("SNTP config")); cmd->print(F("AT+CIPSNTPCFG=1,0,\"")); cmd->print(server1); if (server2) { cmd->print((FSH_P) QOUT_COMMA_QOUT); cmd->print(server2); } cmd->print('"'); return sendCommand(); } unsigned long EspAtDrvClass::sntpTime() { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("SNTP time")); #ifdef WIFIESPAT1 cmd->print(F("AT+SNTPTIME?")); // AT LoBo firmware command if (!sendCommand(PSTR("+SNTPTIME"))) return 0; char* tok = strtok(buffer + strlen("+SNTPTIME:"), ","); unsigned long res = strtoul(tok, NULL, 10); #else cmd->print(F("AT+SYSTIMESTAMP?")); if (!sendCommand(PSTR("+SYSTIMESTAMP"))) return 0; unsigned long res = strtoul(buffer + strlen("+SYSTIMESTAMP:"), NULL, 10); #endif readOK(); return res; } bool EspAtDrvClass::ping(const char* hostname) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("ping")); cmd->print(F("AT+PING=\"")); cmd->print(hostname); cmd->print("\""); return sendCommand(); } bool EspAtDrvClass::sleepMode(EspAtSleepMode mode) { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("set sleep mode")); cmd->print(F("AT+SLEEP=")); cmd->print(mode); return sendCommand(); } bool EspAtDrvClass::wifiOff(bool save) { maintain(); return setWifiMode(0, save); } bool EspAtDrvClass::deepSleep() { maintain(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("deep sleep")); return simpleCommand(PSTR("AT+GSLP=0")); } void EspAtDrvClass::ip2str(const IPAddress& ip, char* s) { itoa(ip[0], s, 10); size_t l = strlen(s); s[l] = '.'; itoa(ip[1], s + l + 1, 10); l = strlen(s); s[l] = '.'; itoa(ip[2], s + l + 1, 10); l = strlen(s); s[l] = '.'; itoa(ip[3], s + l + 1, 10); } uint8_t EspAtDrvClass::freeLinkId() { maintain(); for (int linkId = LINKS_COUNT - 1; linkId >= 0; linkId--) { LinkInfo& link = linkInfo[linkId]; if (!link.isConnected() && !link.isClosing() && !link.available) { LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("free linkId is ")); LOG_INFO_PRINTLN(linkId); return linkId; } } return NO_LINK; } bool EspAtDrvClass::readRX(PGM_P expected, bool bufferData, bool listItem) { const size_t SL_IPD = strlen("+IPD,"); uint8_t timeout = 0; bool unlinkBug = false; uint8_t ignoredCount = 0; while (true) { size_t available = serial->available(); if (!expected && available == 0) return true; buffer[0] = 0; int l = serial->readBytes(buffer, 1); // read first byte with stream's timeout if (!l) {// timeout or unconnected if (timeout == TIMEOUT_COUNT) { LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("AT firmware not responding")); lastErrorCode = EspAtDrvError::AT_NOT_RESPONDIG; return false; } // next we send an invalid command to AT. cmd->println("?"); // response is: // nothing if the firmware doesn't respond at all. readBytes will timeout again // "busy p..." if still processing a command. will be printed to debug output and ignored // ERROR if we missed some unexpected response of a current command. will be evaluated as ERROR timeout++; continue; } if (buffer[0] == '>') { // AT+CIPSEND prompt #ifdef WIFIESPAT1 // AT versions 1.x send a space after >. we must clear it serial->readBytes(buffer + 1, 1); // read byte with stream's timeout #endif buffer[1] = 0; l = 1; timeout = 0; // AT firmware responded } else { l += serial->readBytes(buffer + l, 1); // read second byte with stream's timeout if (l < 2) continue; // We haven't read requested 2 bytes, something went wrong timeout = 0; // AT firmware responded if (buffer[0] == '\r' && buffer[1] == '\n') // empty line. skip it continue; char terminator = '\n'; if (buffer[0] == '+') { // +IPD, +CIP if (buffer[1] == 'C' && !bufferData) { // +CIP terminator = ':'; #ifdef WIFIESPAT1 } else if (buffer[1] == 'I') { // +IPD l += serial->readBytes(buffer + l, 4); // (+I)PD,i int8_t linkId = buffer[SL_IPD] - 48; if (linkInfo[linkId].isUdpListener()) { terminator = ':'; } #endif } } l += serial->readBytesUntil(terminator, buffer + l, sizeof(buffer) - l - 1); // - 1 for terminating 0 while (buffer[l - 1] == '\r') { // 'while' because some (ignored) messages have \r\r\n l--; // trim \r } buffer[l] = 0; // terminate the string } LOG_DEBUG_PRINT_PREFIX(); LOG_DEBUG_PRINT(buffer); if (expected && strncmp_P(buffer, expected, strlen_P(expected)) == 0) { // startsWith LOG_DEBUG_PRINTLN(F(" ...matched")); return true; } if (strncmp_P(buffer, PSTR("+IPD,"), SL_IPD) == 0) { // startsWith int8_t linkId = buffer[SL_IPD] - 48; size_t len = atol(buffer + SL_IPD + 2); if (linkId >= 0 && linkId < LINKS_COUNT && len > 0) { LinkInfo& link = linkInfo[linkId]; #ifdef WIFIESPAT1 if (!link.isUdpListener()) { #endif link.available = len; LOG_DEBUG_PRINTLN((FSH_P) PROCESSED); #ifdef WIFIESPAT1 } else { // UDP listener LOG_DEBUG_PRINT(F(":")); uint8_t res = link.udpDataCallback->readRxData(serial, len); if (res == EspAtDrvUdpDataCallback::OK) { LOG_DEBUG_PRINTLN((FSH_P) PROCESSED); } else { LOG_DEBUG_PRINTLN(F(" ...error")); LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("UDP message on link ")); LOG_ERROR_PRINT(linkId); LOG_ERROR_PRINT(F(" size ")); LOG_ERROR_PRINT(len); LOG_ERROR_PRINT(F(" error ")); LOG_ERROR_PRINTLN(res); lastErrorCode = (EspAtDrvError)((uint8_t) EspAtDrvError::UDP_BUSY + (res - 1)); } } #endif #ifndef ESPATDRV_ASSUME_FLOW_CONTROL } else { // +IPD truncated in serial buffer overflow LOG_DEBUG_PRINTLN((FSH_P) IGNORED); #endif } } else if (strcmp_P(buffer + 1, PSTR(",CONNECT")) == 0) { uint8_t linkId = buffer[0] - 48; LinkInfo& link = linkInfo[linkId]; if (link.available == 0 && (!link.isConnected() || link.isClosing())) { // incoming connection (and we could miss CLOSED) link.flags = LINK_CONNECTED | LINK_IS_INCOMING; #ifdef WIFIESPAT_MULTISERVER link.localPort = 0; #endif LOG_DEBUG_PRINTLN((FSH_P) PROCESSED); } else { LOG_DEBUG_PRINTLN((FSH_P) IGNORED); } } else if ((strcmp_P(buffer + 1, PSTR(",CLOSED")) == 0 || strcmp_P(buffer + 1, PSTR(",CONNECT FAIL")) == 0)) { uint8_t linkId = buffer[0] - 48; linkInfo[linkId].flags = 0; #ifndef WIFIESPAT1 //AT2 linkInfo[linkId].available = 0; // AT2 sends CLOSED only after all data are read #endif LOG_DEBUG_PRINTLN((FSH_P) PROCESSED); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINT(F("closed linkId ")); LOG_INFO_PRINTLN(linkId); } else if (!strcmp_P(buffer, PSTR("ERROR")) || !strcmp_P(buffer, PSTR("FAIL"))) { if (unlinkBug) { LOG_DEBUG_PRINTLN(F(" ...UNLINK is OK")); return true; } if (expected == nullptr || !strcmp_P("ready", expected)) { LOG_DEBUG_PRINTLN((FSH_P) IGNORED); // it is only a late response to timeout query '?' } else { LOG_DEBUG_PRINTLN(F(" ...error")); LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("expected ")); LOG_ERROR_PRINT((FSH_P) expected); LOG_ERROR_PRINT(F(" got ")); LOG_ERROR_PRINTLN(buffer); lastErrorCode = EspAtDrvError::AT_ERROR; return false; } } else if (!strcmp_P(buffer, PSTR("No AP"))) { LOG_DEBUG_PRINTLN((FSH_P) PROCESSED); LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINT(F("expected ")); LOG_ERROR_PRINT((FSH_P) expected); LOG_ERROR_PRINT(F(" got ")); LOG_ERROR_PRINTLN(buffer); lastErrorCode = EspAtDrvError::NO_AP; return false; } else if (!strcmp_P(buffer, PSTR("UNLINK"))) { unlinkBug = true; LOG_DEBUG_PRINTLN((FSH_P) PROCESSED); } else if (listItem && !strcmp_P(buffer, OK)) { // OK ends the listing of unknown items count LOG_DEBUG_PRINTLN(F(" ...end of list")); return false; } else if (!strncmp_P(buffer, PSTR("+ETH"), strlen("+ETH"))) { ethConnected = (buffer[strlen("+ETH_")] != 'D'); // +ETH_DISCONNECTED LOG_DEBUG_PRINTLN((FSH_P) PROCESSED); } else { ignoredCount++; if (ignoredCount > 70) { // reset() has many ignored lines LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("To much garbage on RX")); lastErrorCode = EspAtDrvError::AT_NOT_RESPONDIG; return false; } LOG_DEBUG_PRINTLN((FSH_P) IGNORED); } } } bool EspAtDrvClass::readOK() { return readRX(OK); } bool EspAtDrvClass::sendCommand(PGM_P expected, bool bufferData, bool listItem) { // AT command is already printed, but not 'entered' with "\r\n" LOG_DEBUG_PRINT(F(" ...sent")); cmd->println(); // finish AT command sending return expected ? readRX(expected, bufferData, listItem) : readOK(); } bool EspAtDrvClass::simpleCommand(PGM_P command) { maintain(); cmd->print((FSH_P) command); LOG_DEBUG_PRINT(F(" ...sent")); cmd->println(); return readOK(); } bool EspAtDrvClass::setWifiMode(uint8_t mode, bool save) { if (wifiModeDef == -1) { // reset() was not executed successful LOG_ERROR_PRINT_PREFIX(); LOG_ERROR_PRINTLN(F("AT firmware was not initialized")); lastErrorCode = EspAtDrvError::NOT_INITIALIZED; return false; } if (mode == wifiMode && (!save || mode == wifiModeDef)) // no change return true; #ifdef WIFIESPAT1 cmd->print(save ? F("AT+CWMODE=") : F("AT+CWMODE_CUR=")); cmd->print(mode); if (!sendCommand()) return false; #else if (persistent != save && !sysStoreInternal(save)) return false; cmd->print(F("AT+CWMODE=")); cmd->print(mode); bool ok = sendCommand(); if (persistent != save && !sysStoreInternal(persistent)) { persistent = save; } if (!ok) return false; #endif wifiMode = mode; if (save) { wifiModeDef = mode; } return true; } #ifndef ESPATDRV_ASSUME_FLOW_CONTROL /** * information sent by AT firmware without request (+IPD, CONNECT, CLOSE) * can get lost in serial buffer overflow or on SoftwareSerial RX * (SoftwareSerial can't receive while transmitting). * * AT1: Since the link is closed by peer only after the data is received by AT * firmware, we check the receive length after link status check, to not end * with closed link without data size read to LinkInfo.available. */ bool EspAtDrvClass::syncLinkInfo() { if (millis() - lastSyncMillis < 500) return false; lastSyncMillis = millis(); LOG_INFO_PRINT_PREFIX(); LOG_INFO_PRINTLN(F("sync")); #ifdef WIFIESPAT1 return checkLinks() && recvLenQuery(); #else return recvLenQuery(); #endif } bool EspAtDrvClass::recvLenQuery() { maintain(); cmd->print(F("AT+CIPRECVLEN?")); if (!sendCommand(PSTR("+CIPRECVLEN"))) return false; const char* delim = ","; char* tok = strtok(buffer + strlen("+CIPRECVLEN:"), delim); for (int linkId = 0; linkId < LINKS_COUNT; linkId++) { if (tok == NULL) break; if (strlen(tok) > 0) { #ifdef WIFIESPAT1 linkInfo[linkId].available = atol(tok); #else LinkInfo& link = linkInfo[linkId]; if (tok[0] == '-') { // AT V2 sends -1 for inactive links link.flags = 0; link.available = 0; } else { if (!link.isConnected() || link.isClosing()) { // missed incoming connection link.flags = LINK_CONNECTED | LINK_IS_INCOMING; } link.available = atol(tok); } #endif } tok = strtok(NULL, delim); } return readOK(); } #endif #if !defined(ESPATDRV_ASSUME_FLOW_CONTROL) || defined(WIFIESPAT_MULTISERVER) bool EspAtDrvClass::checkLinks() { maintain(); cmd->print((FSH_P) AT_CIPSTATUS); if (!sendCommand(STATUS)) return false; bool ok[LINKS_COUNT] = {false}; while (readRX(CIPSTATUS, true, true)) { uint8_t linkId = buffer[strlen("+CIPSTATUS:")] - 48; ok[linkId] = true; #ifdef WIFIESPAT_MULTISERVER const char* delim = ",\""; char* tok = strtok(buffer, delim); // +CIPSTATUS: tok = strtok(NULL, delim); // tok = strtok(NULL, delim); // tok = strtok(NULL, delim); // tok = strtok(NULL, delim); // linkInfo[linkId].localPort = atoi(tok); #endif } for (int linkId = 0; linkId < LINKS_COUNT; linkId++) { LinkInfo& link = linkInfo[linkId]; if (ok[linkId]) { if (!link.isConnected() || link.isClosing()) { // missed incoming connection link.flags = LINK_CONNECTED | LINK_IS_INCOMING; } } else { // not connected link.flags = 0; } } return true; } #endif void EspAtDrvClass::printMAC(Print* out, uint8_t* mac) { for (int i = 0; i < 6; i++) { if (i > 0) { out->print(":"); } if (mac[i] < 16) { out->print("0"); } out->print(mac[i], HEX); } } EspAtDrvClass EspAtDrv;