mbed-os/connectivity/drivers/wifi/esp8266-driver/ESP8266/ESP8266.cpp

1588 lines
45 KiB
C++

/* ESP8266 Example
* Copyright (c) 2015 ARM Limited
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if DEVICE_SERIAL && DEVICE_INTERRUPTIN && defined(MBED_CONF_EVENTS_PRESENT) && defined(MBED_CONF_NSAPI_PRESENT) && defined(MBED_CONF_RTOS_API_PRESENT)
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include "ESP8266.h"
#include "netsocket/nsapi_types.h"
#include "mbed_trace.h"
#include "PinNames.h"
#include "platform/Callback.h"
#include "platform/mbed_error.h"
#include "rtos/Kernel.h"
#define TRACE_GROUP "ESPA" // ESP8266 AT layer
#define ESP8266_ALL_SOCKET_IDS -1
#define ESP8266_DEFAULT_SERIAL_BAUDRATE 115200
using namespace mbed;
using namespace std::chrono;
using std::milli;
ESP8266::ESP8266(PinName tx, PinName rx, bool debug, PinName rts, PinName cts)
: _sdk_v(-1, -1, -1),
_at_v(-1, -1, -1),
_tcp_passive(false),
_callback(),
_serial(tx, rx, MBED_CONF_ESP8266_SERIAL_BAUDRATE),
_serial_rts(rts),
_serial_cts(cts),
_parser(&_serial),
_packets(0),
_packets_end(&_packets),
_sock_active_id(-1),
_heap_usage(0),
_connect_error(0),
_disconnect(false),
_fail(false),
_sock_already(false),
_closed(false),
_error(false),
_busy(false),
_reset_done(false),
_sock_sending_id(-1),
_conn_status(NSAPI_STATUS_DISCONNECTED)
{
_serial.set_baud(ESP8266_DEFAULT_SERIAL_BAUDRATE);
_parser.debug_on(debug);
_parser.set_delimiter("\r\n");
_parser.oob("+IPD", callback(this, &ESP8266::_oob_packet_hdlr));
//Note: espressif at command document says that this should be +CWJAP_CUR:<error code>
//but seems that at least current version is not sending it
//https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf
//Also seems that ERROR is not sent, but FAIL instead
_parser.oob("0,CLOSED", callback(this, &ESP8266::_oob_socket0_closed));
_parser.oob("1,CLOSED", callback(this, &ESP8266::_oob_socket1_closed));
_parser.oob("2,CLOSED", callback(this, &ESP8266::_oob_socket2_closed));
_parser.oob("3,CLOSED", callback(this, &ESP8266::_oob_socket3_closed));
_parser.oob("4,CLOSED", callback(this, &ESP8266::_oob_socket4_closed));
_parser.oob("+CWJAP:", callback(this, &ESP8266::_oob_connect_err));
_parser.oob("WIFI ", callback(this, &ESP8266::_oob_connection_status));
_parser.oob("UNLINK", callback(this, &ESP8266::_oob_socket_close_err));
_parser.oob("ALREADY CONNECTED", callback(this, &ESP8266::_oob_conn_already));
_parser.oob("ERROR", callback(this, &ESP8266::_oob_err));
_parser.oob("ready", callback(this, &ESP8266::_oob_ready));
_parser.oob("+CWLAP:", callback(this, &ESP8266::_oob_scan_results));
// Don't expect to find anything about the watchdog reset in official documentation
//https://techtutorialsx.com/2017/01/21/esp8266-watchdog-functions/
_parser.oob("wdt reset", callback(this, &ESP8266::_oob_watchdog_reset));
// Don't see a reason to make distiction between software(Software WDT reset) and hardware(wdt reset) watchdog treatment
//https://github.com/esp8266/Arduino/blob/4897e0006b5b0123a2fa31f67b14a3fff65ce561/doc/faq/a02-my-esp-crashes.md#watchdog
_parser.oob("Soft WDT reset", callback(this, &ESP8266::_oob_watchdog_reset));
_parser.oob("busy ", callback(this, &ESP8266::_oob_busy));
// NOTE: documentation v3.0 says '+CIPRECVDATA:<data_len>,' but it's not how the FW responds...
_parser.oob("+CIPRECVDATA,", callback(this, &ESP8266::_oob_tcp_data_hdlr));
// Register 'SEND OK'/'SEND FAIL' oobs here. Don't get involved in oob management with send status
// because ESP8266 modem possibly doesn't reply these packets on error case.
_parser.oob("SEND OK", callback(this, &ESP8266::_oob_send_ok_received));
_parser.oob("SEND FAIL", callback(this, &ESP8266::_oob_send_fail_received));
for (int i = 0; i < SOCKET_COUNT; i++) {
_sock_i[i].open = false;
_sock_i[i].proto = NSAPI_UDP;
_sock_i[i].tcp_data = NULL;
_sock_i[i].tcp_data_avbl = 0;
_sock_i[i].tcp_data_rcvd = 0;
_sock_i[i].send_fail = false;
}
_scan_r.res = NULL;
_scan_r.limit = 0;
_scan_r.cnt = 0;
}
bool ESP8266::at_available()
{
bool ready = false;
_smutex.lock();
// Might take a while to respond after HW reset
for (int i = 0; i < 5; i++) {
ready = _parser.send("AT")
&& _parser.recv("OK\n");
if (ready) {
break;
}
tr_debug("at_available(): Waiting AT response.");
}
// Switch baud-rate from default one to assigned one
if (MBED_CONF_ESP8266_SERIAL_BAUDRATE != ESP8266_DEFAULT_SERIAL_BAUDRATE) {
ready &= _parser.send("AT+UART_CUR=%u,8,1,0,0", MBED_CONF_ESP8266_SERIAL_BAUDRATE)
&& _parser.recv("OK\n");
_serial.set_baud(MBED_CONF_ESP8266_SERIAL_BAUDRATE);
ready &= _parser.send("AT")
&& _parser.recv("OK\n");
}
_smutex.unlock();
return ready;
}
bool ESP8266::echo_off()
{
_smutex.lock();
bool ready = _parser.send("ATE0")
&& _parser.recv("OK\n");
_smutex.unlock();
return ready;
}
struct ESP8266::fw_sdk_version ESP8266::sdk_version()
{
int major;
int minor;
int patch;
_smutex.lock();
bool done = _parser.send("AT+GMR")
&& _parser.recv("SDK version:%d.%d.%d", &major, &minor, &patch)
&& _parser.recv("OK\n");
_smutex.unlock();
if (done) {
_sdk_v.major = major;
_sdk_v.minor = minor;
_sdk_v.patch = patch;
}
return _sdk_v;
}
struct ESP8266::fw_at_version ESP8266::at_version()
{
int major;
int minor;
int patch;
int nused;
_smutex.lock();
bool done = _parser.send("AT+GMR")
&& _parser.recv("AT version:%d.%d.%d.%d", &major, &minor, &patch, &nused)
&& _parser.recv("OK\n");
_smutex.unlock();
if (done) {
_at_v.major = major;
_at_v.minor = minor;
_at_v.patch = patch;
}
return _at_v;
}
bool ESP8266::stop_uart_hw_flow_ctrl(bool board_only)
{
bool done = true;
#if DEVICE_SERIAL_FC
if (_serial_rts != NC || _serial_cts != NC) {
// Stop board's flow control
_serial.set_flow_control(SerialBase::Disabled, _serial_rts, _serial_cts);
if (!board_only) {
// Stop ESP8266's flow control
done = _parser.send("AT+UART_CUR=%u,8,1,0,0", MBED_CONF_ESP8266_SERIAL_BAUDRATE)
&& _parser.recv("OK\n");
}
}
#endif
return done;
}
bool ESP8266::start_uart_hw_flow_ctrl(void)
{
bool done = true;
#if DEVICE_SERIAL_FC
_smutex.lock();
if (_serial_rts != NC && _serial_cts != NC) {
// Start ESP8266's flow control
done = _parser.send("AT+UART_CUR=%u,8,1,0,3", MBED_CONF_ESP8266_SERIAL_BAUDRATE)
&& _parser.recv("OK\n");
if (done) {
// Start board's flow control
_serial.set_flow_control(SerialBase::RTSCTS, _serial_rts, _serial_cts);
}
} else if (_serial_rts != NC) {
_serial.set_flow_control(SerialBase::RTS, _serial_rts, NC);
// Enable ESP8266's CTS pin
done = _parser.send("AT+UART_CUR=%u,8,1,0,2", MBED_CONF_ESP8266_SERIAL_BAUDRATE)
&& _parser.recv("OK\n");
} else if (_serial_cts != NC) {
// Enable ESP8266's RTS pin
done = _parser.send("AT+UART_CUR=%u,8,1,0,1", MBED_CONF_ESP8266_SERIAL_BAUDRATE)
&& _parser.recv("OK\n");
if (done) {
_serial.set_flow_control(SerialBase::CTS, NC, _serial_cts);
}
}
_smutex.unlock();
if (!done) {
tr_debug("start_uart_hw_flow_ctrl(): Enable UART HW flow control: FAIL.");
}
#else
if (_serial_rts != NC || _serial_cts != NC) {
done = false;
}
#endif
return done;
}
bool ESP8266::startup(int mode)
{
if (!(mode == WIFIMODE_STATION || mode == WIFIMODE_SOFTAP
|| mode == WIFIMODE_STATION_SOFTAP)) {
return false;
}
_smutex.lock();
set_timeout(ESP8266_CONNECT_TIMEOUT);
bool done = _parser.send("AT+CWMODE_CUR=%d", mode)
&& _parser.recv("OK\n")
&& _parser.send("AT+CIPMUX=1")
&& _parser.recv("OK\n");
set_timeout(); //Restore default
_smutex.unlock();
return done;
}
bool ESP8266::reset(void)
{
static const auto ESP8266_BOOTTIME = 10s;
bool done = false;
_smutex.lock();
auto start_time = rtos::Kernel::Clock::now();
_reset_done = false;
set_timeout(ESP8266_RECV_TIMEOUT);
for (int i = 0; i < 2; i++) {
if (!_parser.send("AT+RST") || !_parser.recv("OK\n")) {
tr_debug("reset(): AT+RST failed or no response.");
continue;
}
while (!_reset_done) {
_process_oob(ESP8266_RECV_TIMEOUT, true); // UART mutex claimed -> need to check for OOBs ourselves
if (_reset_done || rtos::Kernel::Clock::now() - start_time >= ESP8266_BOOTTIME) {
break;
}
rtos::ThisThread::sleep_for(100ms);
}
done = _reset_done;
if (done) {
break;
}
}
tr_debug("reset(): Done: %s.", done ? "OK" : "FAIL");
_clear_socket_packets(ESP8266_ALL_SOCKET_IDS);
_sock_sending_id = -1;
set_timeout();
_smutex.unlock();
return done;
}
bool ESP8266::dhcp(bool enabled, int mode)
{
//only 3 valid modes
if (mode < 0 || mode > 2) {
return false;
}
_smutex.lock();
bool done = _parser.send("AT+CWDHCP_CUR=%d,%d", mode, enabled ? 1 : 0)
&& _parser.recv("OK\n");
_smutex.unlock();
return done;
}
bool ESP8266::cond_enable_tcp_passive_mode()
{
bool done = true;
if (FW_AT_LEAST_VERSION(_at_v.major, _at_v.minor, _at_v.patch, 0, ESP8266_AT_VERSION_TCP_PASSIVE_MODE)) {
_smutex.lock();
done = _parser.send("AT+CIPRECVMODE=1")
&& _parser.recv("OK\n");
_smutex.unlock();
_tcp_passive = done ? true : false;
}
return done;
}
nsapi_error_t ESP8266::connect(const char *ap, const char *passPhrase)
{
nsapi_error_t ret = NSAPI_ERROR_OK;
_smutex.lock();
set_timeout(ESP8266_CONNECT_TIMEOUT);
bool res = _parser.send("AT+CWJAP_CUR=\"%s\",\"%s\"", ap, passPhrase);
if (!res || !_parser.recv("OK\n")) {
if (_fail) {
if (_connect_error == 1) {
ret = NSAPI_ERROR_CONNECTION_TIMEOUT;
} else if (_connect_error == 2) {
ret = NSAPI_ERROR_AUTH_FAILURE;
} else if (_connect_error == 3) {
ret = NSAPI_ERROR_NO_SSID;
} else {
ret = NSAPI_ERROR_NO_CONNECTION;
}
_fail = false;
_connect_error = 0;
}
}
set_timeout();
_smutex.unlock();
return ret;
}
bool ESP8266::disconnect(void)
{
_smutex.lock();
_disconnect = true;
bool done = _parser.send("AT+CWQAP") && _parser.recv("OK\n");
_smutex.unlock();
return done;
}
bool ESP8266::ip_info_print(int enable)
{
_smutex.lock();
_disconnect = true;
bool done = _parser.send("AT+CIPDINFO=%d", enable) && _parser.recv("OK\n");
_smutex.unlock();
return done;
}
const char *ESP8266::ip_addr(void)
{
_smutex.lock();
set_timeout(ESP8266_CONNECT_TIMEOUT);
if (!(_parser.send("AT+CIFSR")
&& _parser.recv("+CIFSR:STAIP,\"%15[^\"]\"", _ip_buffer)
&& _parser.recv("OK\n"))) {
_smutex.unlock();
return 0;
}
set_timeout();
_smutex.unlock();
return _ip_buffer;
}
bool ESP8266::set_ip_addr(const char *ip, const char *gateway, const char *netmask)
{
if (ip == nullptr || ip[0] == '\0') {
return false;
}
bool ok = false;
bool parser_send = false;
_smutex.lock();
if ((gateway == nullptr) || (netmask == nullptr) || gateway[0] == '\0' || netmask[0] == '\0') {
parser_send = _parser.send("AT+CIPSTA_CUR=\"%s\"", ip);
} else {
parser_send = _parser.send("AT+CIPSTA_CUR=\"%s\",\"%s\",\"%s\"", ip, gateway, netmask);
}
if (parser_send && _parser.recv("OK\n")) {
ok = true;
} else {
ok = false;
}
_smutex.unlock();
return ok;
}
const char *ESP8266::mac_addr(void)
{
_smutex.lock();
if (!(_parser.send("AT+CIFSR")
&& _parser.recv("+CIFSR:STAMAC,\"%17[^\"]\"", _mac_buffer)
&& _parser.recv("OK\n"))) {
_smutex.unlock();
return 0;
}
_smutex.unlock();
return _mac_buffer;
}
const char *ESP8266::gateway()
{
_smutex.lock();
if (!(_parser.send("AT+CIPSTA_CUR?")
&& _parser.recv("+CIPSTA_CUR:gateway:\"%15[^\"]\"", _gateway_buffer)
&& _parser.recv("OK\n"))) {
_smutex.unlock();
return 0;
}
_smutex.unlock();
return _gateway_buffer;
}
const char *ESP8266::netmask()
{
_smutex.lock();
if (!(_parser.send("AT+CIPSTA_CUR?")
&& _parser.recv("+CIPSTA_CUR:netmask:\"%15[^\"]\"", _netmask_buffer)
&& _parser.recv("OK\n"))) {
_smutex.unlock();
return 0;
}
_smutex.unlock();
return _netmask_buffer;
}
int8_t ESP8266::rssi()
{
int8_t rssi = 0;
char bssid[18];
_smutex.lock();
set_timeout(ESP8266_CONNECT_TIMEOUT);
if (!(_parser.send("AT+CWJAP_CUR?")
&& _parser.recv("+CWJAP_CUR:\"%*[^\"]\",\"%17[^\"]\"", bssid)
&& _parser.recv("OK\n"))) {
_smutex.unlock();
return 0;
}
set_timeout();
_smutex.unlock();
WiFiAccessPoint ap[1];
_scan_r.res = ap;
_scan_r.limit = 1;
_scan_r.cnt = 0;
_smutex.lock();
set_timeout(ESP8266_CONNECT_TIMEOUT);
if (!(_parser.send("AT+CWLAP=\"\",\"%s\",", bssid)
&& _parser.recv("OK\n"))) {
rssi = 0;
} else if (_scan_r.cnt == 1) {
//All OK so read and return rssi
rssi = ap[0].get_rssi();
}
_scan_r.cnt = 0;
_scan_r.res = NULL;
set_timeout();
_smutex.unlock();
return rssi;
}
int ESP8266::scan(WiFiAccessPoint *res, unsigned limit, scan_mode mode, duration<unsigned, milli> t_max, duration<unsigned, milli> t_min)
{
_smutex.lock();
// Default timeout plus time spend scanning each channel
set_timeout(ESP8266_MISC_TIMEOUT + 13 * (t_max != t_max.zero() ? t_max : duration<unsigned, milli>(ESP8266_SCAN_TIME_MAX_DEFAULT)));
_scan_r.res = res;
_scan_r.limit = limit;
_scan_r.cnt = 0;
bool ret_parse_send = true;
if (FW_AT_LEAST_VERSION(_at_v.major, _at_v.minor, _at_v.patch, 0, ESP8266_AT_VERSION_WIFI_SCAN_CHANGE)) {
ret_parse_send = _parser.send("AT+CWLAP=,,,%u,%u,%u", (mode == SCANMODE_ACTIVE ? 0 : 1), t_min.count(), t_max.count());
} else {
ret_parse_send = _parser.send("AT+CWLAP");
}
if (!(ret_parse_send && _parser.recv("OK\n"))) {
tr_warning("scan(): AP info parsing aborted.");
// Lets be happy about partial success and not return NSAPI_ERROR_DEVICE_ERROR
if (!_scan_r.cnt) {
_scan_r.cnt = NSAPI_ERROR_DEVICE_ERROR;
}
}
int cnt = _scan_r.cnt;
_scan_r.res = NULL;
set_timeout();
_smutex.unlock();
return cnt;
}
nsapi_error_t ESP8266::open_udp(int id, const char *addr, int port, int local_port, int udp_mode)
{
static const char *type = "UDP";
bool done = false;
ip_info_print(1);
_smutex.lock();
// process OOB so that _sock_i reflects the correct state of the socket
_process_oob(ESP8266_SEND_TIMEOUT, true);
// Previous close() can fail with busy in sending. Usually, user will ignore the close()
// error code and cause 'spurious close', in which case user has closed the socket but ESP8266 modem
// hasn't yet. Because we don't know how long ESP8266 modem will trap in busy, enlarge retry count
// or timeout in close() isn't a nice way. Here, we actively re-call close() in open() to let the modem
// close the socket. User can re-try open() on failure. Without this active close(), open() can fail forever
// with previous 'spurious close', unless peer closes the socket and so ESP8266 modem closes it accordingly.
if (id >= SOCKET_COUNT) {
_smutex.unlock();
return NSAPI_ERROR_PARAMETER;
} else if (_sock_i[id].open) {
close(id);
}
for (int i = 0; i < 2; i++) {
if (local_port) {
done = _parser.send("AT+CIPSTART=%d,\"%s\",\"%s\",%d,%d,%d", id, type, addr, port, local_port, udp_mode);
} else {
done = _parser.send("AT+CIPSTART=%d,\"%s\",\"%s\",%d", id, type, addr, port);
}
if (done) {
if (!_parser.recv("OK\n")) {
if (_sock_already) {
_sock_already = false; // To be raised again by OOB msg
done = close(id);
if (!done) {
break;
}
}
if (_error) {
_error = false;
done = false;
}
continue;
}
_sock_i[id].open = true;
_sock_i[id].proto = NSAPI_UDP;
break;
}
}
_clear_socket_packets(id);
_smutex.unlock();
tr_debug("open_udp(): UDP socket %d opened: %s.", id, (_sock_i[id].open ? "true" : "false"));
return done ? NSAPI_ERROR_OK : NSAPI_ERROR_DEVICE_ERROR;
}
nsapi_error_t ESP8266::open_tcp(int id, const char *addr, int port, int keepalive)
{
static const char *type = "TCP";
bool done = false;
ip_info_print(1);
if (!addr) {
return NSAPI_ERROR_PARAMETER;
}
_smutex.lock();
// process OOB so that _sock_i reflects the correct state of the socket
_process_oob(ESP8266_SEND_TIMEOUT, true);
// See the reason above with close()
if (id >= SOCKET_COUNT) {
_smutex.unlock();
return NSAPI_ERROR_PARAMETER;
} else if (_sock_i[id].open) {
close(id);
}
for (int i = 0; i < 2; i++) {
if (keepalive) {
done = _parser.send("AT+CIPSTART=%d,\"%s\",\"%s\",%d,%d", id, type, addr, port, keepalive);
} else {
done = _parser.send("AT+CIPSTART=%d,\"%s\",\"%s\",%d", id, type, addr, port);
}
if (done) {
if (!_parser.recv("OK\n")) {
if (_sock_already) {
_sock_already = false; // To be raised again by OOB msg
done = close(id);
if (!done) {
break;
}
}
if (_error) {
_error = false;
done = false;
}
continue;
}
_sock_i[id].open = true;
_sock_i[id].proto = NSAPI_TCP;
break;
}
}
_clear_socket_packets(id);
_smutex.unlock();
tr_debug("open_tcp: TCP socket %d opened: %s . ", id, (_sock_i[id].open ? "true" : "false"));
return done ? NSAPI_ERROR_OK : NSAPI_ERROR_DEVICE_ERROR;
}
bool ESP8266::dns_lookup(const char *name, char *ip)
{
_smutex.lock();
set_timeout(ESP8266_DNS_TIMEOUT);
bool done = _parser.send("AT+CIPDOMAIN=\"%s\"", name)
&& _parser.recv("+CIPDOMAIN:%15[^\n]\n", ip)
&& _parser.recv("OK\n");
set_timeout();
_smutex.unlock();
return done;
}
nsapi_size_or_error_t ESP8266::send(int id, const void *data, uint32_t amount)
{
if (_sock_i[id].proto == NSAPI_TCP) {
if (_sock_sending_id >= 0 && _sock_sending_id < SOCKET_COUNT) {
if (!_sock_i[id].send_fail) {
tr_debug("send(): Previous packet (socket %d) was not yet ACK-ed with SEND OK.", _sock_sending_id);
return NSAPI_ERROR_WOULD_BLOCK;
} else {
tr_debug("send(): Previous packet (socket %d) failed.", id);
return NSAPI_ERROR_DEVICE_ERROR;
}
}
}
nsapi_error_t ret = NSAPI_ERROR_DEVICE_ERROR;
int bytes_confirmed = 0;
// +CIPSEND supports up to 2048 bytes at a time
// Data stream can be truncated
if (amount > 2048 && _sock_i[id].proto == NSAPI_TCP) {
amount = 2048;
// Datagram must stay intact
} else if (amount > 2048 && _sock_i[id].proto == NSAPI_UDP) {
tr_debug("send(): UDP datagram maximum size is 2048 .");
return NSAPI_ERROR_PARAMETER;
}
_smutex.lock();
// Mark this socket is sending. We allow only one actively sending socket because:
// 1. ESP8266 AT packets 'SEND OK'/'SEND FAIL' are not associated with socket ID. No way to tell them.
// 2. In original implementation, ESP8266::send() is synchronous, which implies only one actively sending socket.
_sock_sending_id = id;
set_timeout(ESP8266_SEND_TIMEOUT);
_busy = false;
_error = false;
if (!_parser.send("AT+CIPSEND=%d,%" PRIu32, id, amount)) {
tr_debug("send(): AT+CIPSEND failed.");
goto END;
}
if (!_parser.recv(">")) {
// This means ESP8266 hasn't even started to receive data
tr_debug("send(): Didn't get \">\"");
if (_sock_i[id].proto == NSAPI_TCP) {
ret = NSAPI_ERROR_WOULD_BLOCK; // Not necessarily critical error.
} else if (_sock_i[id].proto == NSAPI_UDP) {
ret = NSAPI_ERROR_NO_MEMORY;
}
goto END;
}
if (_parser.write((char *)data, (int)amount) < 0) {
tr_debug("send(): Failed to write serial data");
// Serial is not working, serious error, reset needed.
ret = NSAPI_ERROR_DEVICE_ERROR;
goto END;
}
// The "Recv X bytes" is not documented.
if (!_parser.recv("Recv %d bytes", &bytes_confirmed)) {
tr_debug("send(): Bytes not confirmed.");
if (_sock_i[id].proto == NSAPI_TCP) {
ret = NSAPI_ERROR_WOULD_BLOCK;
} else if (_sock_i[id].proto == NSAPI_UDP) {
ret = NSAPI_ERROR_NO_MEMORY;
}
} else if (bytes_confirmed != (int)amount && _sock_i[id].proto == NSAPI_UDP) {
tr_debug("send(): Error: confirmed %d bytes, but expected %d.", bytes_confirmed, amount);
ret = NSAPI_ERROR_DEVICE_ERROR;
} else {
// TCP can accept partial writes (if they ever happen)
ret = bytes_confirmed;
}
END:
_process_oob(ESP8266_RECV_TIMEOUT, true); // Drain USART receive register to avoid data overrun
// error hierarchy, from low to high
// NOTE: We cannot return NSAPI_ERROR_WOULD_BLOCK when "Recv X bytes" has reached, otherwise duplicate data send.
if (_busy && ret < 0) {
ret = NSAPI_ERROR_WOULD_BLOCK;
tr_debug("send(): Modem busy.");
}
if (_error) {
// FIXME: Not sure clear or not of _error. See it as device error and it can recover only via reset?
_sock_sending_id = -1;
ret = NSAPI_ERROR_CONNECTION_LOST;
tr_debug("send(): Connection disrupted.");
}
if (_sock_i[id].send_fail) {
_sock_sending_id = -1;
if (_sock_i[id].proto == NSAPI_TCP) {
ret = NSAPI_ERROR_DEVICE_ERROR;
} else {
ret = NSAPI_ERROR_NO_MEMORY;
}
tr_debug("send(): SEND FAIL received.");
}
if (!_sock_i[id].open && ret < 0) {
_sock_sending_id = -1;
ret = NSAPI_ERROR_CONNECTION_LOST;
tr_debug("send(): Socket %d closed abruptly.", id);
}
set_timeout();
_smutex.unlock();
return ret;
}
void ESP8266::_oob_packet_hdlr()
{
int id;
int port;
int amount;
int pdu_len;
// Get socket id
if (!_parser.scanf(",%d,", &id)) {
return;
}
if (_tcp_passive && _sock_i[id].open == true && _sock_i[id].proto == NSAPI_TCP) {
//For TCP +IPD return only id and amount and it is independent on AT+CIPDINFO settings
//Unfortunately no information about that in ESP manual but it has sense.
if (_parser.recv("%d\n", &amount)) {
_sock_i[id].tcp_data_avbl = amount;
// notify data is available
if (_callback) {
_callback();
}
}
return;
} else {
if (!(_parser.scanf("%d,", &amount)
&& _parser.scanf("%15[^,],", _ip_buffer)
&& _parser.scanf("%d:", &port))) {
return;
}
}
pdu_len = sizeof(struct packet) + amount;
if ((_heap_usage + pdu_len) > MBED_CONF_ESP8266_SOCKET_BUFSIZE) {
tr_debug("\"esp8266.socket-bufsize\"-limit exceeded, packet dropped");
return;
}
struct packet *packet = (struct packet *)malloc(pdu_len);
if (!packet) {
tr_debug("_oob_packet_hdlr(): Out of memory, unable to allocate memory for packet.");
return;
}
_heap_usage += pdu_len;
packet->id = id;
if (_sock_i[id].proto == NSAPI_UDP) {
packet->remote_port = port;
memcpy(packet->remote_ip, _ip_buffer, 16);
}
packet->len = amount;
packet->alloc_len = amount;
packet->next = 0;
if (_parser.read((char *)(packet + 1), amount) < amount) {
free(packet);
_heap_usage -= pdu_len;
return;
}
// append to packet list
*_packets_end = packet;
_packets_end = &packet->next;
}
void ESP8266::_process_oob(duration<uint32_t, milli> timeout, bool all)
{
set_timeout(timeout);
// Poll for inbound packets
while (_parser.process_oob() && all) {
}
set_timeout();
}
void ESP8266::bg_process_oob(duration<uint32_t, milli> timeout, bool all)
{
_smutex.lock();
_process_oob(timeout, all);
_smutex.unlock();
}
int32_t ESP8266::_recv_tcp_passive(int id, void *data, uint32_t amount, duration<uint32_t, milli> timeout)
{
int32_t ret = NSAPI_ERROR_WOULD_BLOCK;
_smutex.lock();
_process_oob(timeout, true);
if (_sock_i[id].tcp_data_avbl != 0) {
_sock_i[id].tcp_data = (char *)data;
_sock_i[id].tcp_data_rcvd = NSAPI_ERROR_WOULD_BLOCK;
_sock_active_id = id;
// +CIPRECVDATA supports up to 2048 bytes at a time
amount = amount > 2048 ? 2048 : amount;
// NOTE: documentation v3.0 says '+CIPRECVDATA:<data_len>,' but it's not how the FW responds...
bool done = _parser.send("AT+CIPRECVDATA=%d,%" PRIu32, id, amount)
&& _parser.recv("OK\n");
_sock_i[id].tcp_data = NULL;
_sock_active_id = -1;
if (!done) {
goto BUSY;
}
// update internal variable tcp_data_avbl to reflect the remaining data
if (_sock_i[id].tcp_data_rcvd > 0) {
if (_sock_i[id].tcp_data_rcvd > (int32_t)amount) {
MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER, MBED_ERROR_CODE_EBADMSG), \
"ESP8266::_recv_tcp_passive() too much data from modem\n");
}
if (_sock_i[id].tcp_data_avbl > _sock_i[id].tcp_data_rcvd) {
_sock_i[id].tcp_data_avbl -= _sock_i[id].tcp_data_rcvd;
} else {
_sock_i[id].tcp_data_avbl = 0;
}
}
ret = _sock_i[id].tcp_data_rcvd;
}
if (!_sock_i[id].open && ret == NSAPI_ERROR_WOULD_BLOCK) {
ret = 0;
}
_smutex.unlock();
return ret;
BUSY:
_process_oob(ESP8266_RECV_TIMEOUT, true);
if (_busy) {
tr_debug("_recv_tcp_passive(): Modem busy.");
ret = NSAPI_ERROR_WOULD_BLOCK;
} else {
tr_error("_recv_tcp_passive(): Unknown state.");
ret = NSAPI_ERROR_DEVICE_ERROR;
}
_smutex.unlock();
return ret;
}
int32_t ESP8266::recv_tcp(int id, void *data, uint32_t amount, duration<uint32_t, milli> timeout)
{
if (_tcp_passive) {
return _recv_tcp_passive(id, data, amount, timeout);
}
_smutex.lock();
// No flow control, drain the USART receive register ASAP to avoid data overrun
if (_serial_rts == NC) {
_process_oob(timeout, true);
}
// check if any packets are ready for us
for (struct packet **p = &_packets; *p; p = &(*p)->next) {
if ((*p)->id == id) {
struct packet *q = *p;
if (q->len <= amount) { // Return and remove full packet
memcpy(data, q + 1, q->len);
if (_packets_end == &(*p)->next) {
_packets_end = p;
}
*p = (*p)->next;
_smutex.unlock();
uint32_t pdu_len = sizeof(struct packet) + q->alloc_len;
uint32_t len = q->len;
free(q);
_heap_usage -= pdu_len;
return len;
} else { // return only partial packet
memcpy(data, q + 1, amount);
q->len -= amount;
memmove(q + 1, (uint8_t *)(q + 1) + amount, q->len);
_smutex.unlock();
return amount;
}
}
}
if (!_sock_i[id].open) {
_smutex.unlock();
return 0;
}
// Flow control, read from USART receive register only when no more data is buffered, and as little as possible
if (_serial_rts != NC) {
_process_oob(timeout, false);
}
_smutex.unlock();
return NSAPI_ERROR_WOULD_BLOCK;
}
int32_t ESP8266::recv_udp(struct esp8266_socket *socket, void *data, uint32_t amount, duration<uint32_t, milli> timeout)
{
_smutex.lock();
set_timeout(timeout);
// Process OOB data since this is
// how UDP packets are received
_process_oob(timeout, true);
set_timeout();
// check if any packets are ready for us
for (struct packet **p = &_packets; *p; p = &(*p)->next) {
if ((*p)->id == socket->id) {
struct packet *q = *p;
socket->addr.set_ip_address((*p)->remote_ip);
socket->addr.set_port((*p)->remote_port);
// Return and remove packet (truncated if necessary)
uint32_t len = q->len < amount ? q->len : amount;
memcpy(data, q + 1, len);
if (_packets_end == &(*p)->next) {
_packets_end = p;
}
*p = (*p)->next;
_smutex.unlock();
uint32_t pdu_len = sizeof(struct packet) + q->alloc_len;
free(q);
_heap_usage -= pdu_len;
return len;
}
}
// Flow control, read from USART receive register only when no more data is buffered, and as little as possible
if (_serial_rts != NC) {
_process_oob(timeout, false);
}
_smutex.unlock();
return NSAPI_ERROR_WOULD_BLOCK;
}
void ESP8266::_clear_socket_packets(int id)
{
struct packet **p = &_packets;
while (*p) {
if ((*p)->id == id || id == ESP8266_ALL_SOCKET_IDS) {
struct packet *q = *p;
int pdu_len = sizeof(struct packet) + q->alloc_len;
if (_packets_end == &(*p)->next) {
_packets_end = p; // Set last packet next field/_packets
}
*p = (*p)->next;
free(q);
_heap_usage -= pdu_len;
} else {
// Point to last packet next field
p = &(*p)->next;
}
}
if (id == ESP8266_ALL_SOCKET_IDS) {
for (int id = 0; id < 5; id++) {
_sock_i[id].tcp_data_avbl = 0;
}
} else {
_sock_i[id].tcp_data_avbl = 0;
}
}
void ESP8266::_clear_socket_sending(int id)
{
if (id == _sock_sending_id) {
_sock_sending_id = -1;
}
_sock_i[id].send_fail = false;
}
bool ESP8266::close(int id)
{
//May take a second try if device is busy
for (unsigned i = 0; i < 2; i++) {
_smutex.lock();
if (_parser.send("AT+CIPCLOSE=%d", id)) {
if (!_parser.recv("OK\n")) {
if (_closed) { // UNLINK ERROR
_closed = false;
_sock_i[id].open = false;
_clear_socket_packets(id);
// Closed, so this socket escapes from SEND FAIL status.
_clear_socket_sending(id);
_smutex.unlock();
// ESP8266 has a habit that it might close a socket on its own.
tr_debug("close(%d): socket close OK with UNLINK ERROR", id);
return true;
}
} else {
// _sock_i[id].open set to false with an OOB
_clear_socket_packets(id);
// Closed, so this socket escapes from SEND FAIL status
_clear_socket_sending(id);
_smutex.unlock();
tr_debug("close(%d): socket close OK with AT+CIPCLOSE OK", id);
return true;
}
}
_smutex.unlock();
}
tr_debug("close(%d): socket close FAIL'ed (spurious close)", id);
return false;
}
void ESP8266::set_timeout(duration<uint32_t, milli> timeout)
{
_parser.set_timeout(timeout.count());
}
bool ESP8266::readable()
{
return _serial.FileHandle::readable();
}
bool ESP8266::writeable()
{
return _serial.FileHandle::writable();
}
void ESP8266::sigio(Callback<void()> func)
{
_serial.sigio(func);
_callback = func;
}
void ESP8266::attach(Callback<void()> status_cb)
{
_conn_stat_cb = status_cb;
}
bool ESP8266::set_sntp_config(bool enable, int timezone, const char *server0,
const char *server1, const char *server2)
{
bool done = false;
_smutex.lock();
if ((server0 == nullptr || server0[0] == '\0')) {
done = _parser.send("AT+CIPSNTPCFG=%d,%d",
enable ? 1 : 0, timezone);
} else if ((server0 != nullptr || server0[0] != '\0')
&& (server1 == nullptr && server1[0] == '\0')) {
done = _parser.send("AT+CIPSNTPCFG=%d,%d,%s",
enable ? 1 : 0, timezone, server0);
} else if ((server0 != nullptr || server0[0] != '\0')
&& (server1 != nullptr && server1[0] != '\0')
&& (server2 == nullptr && server2[0] == '\0')) {
done = _parser.send("AT+CIPSNTPCFG=%d,%d,%s,%s",
enable ? 1 : 0, timezone, server0, server1);
} else {
done = _parser.send("AT+CIPSNTPCFG=%d,%d,%s,%s,%s",
enable ? 1 : 0, timezone, server0, server1, server2);
}
done &= _parser.recv("OK\n");
_smutex.unlock();
return done;
}
bool ESP8266::get_sntp_config(bool *enable, int *timezone, char *server0,
char *server1, char *server2)
{
_smutex.lock();
unsigned int tmp;
bool done = _parser.send("AT+CIPSNTPCFG?")
&& _parser.scanf("+CIPSNTPCFG:%d,%d,\"%32[^\"]\",\"%32[^\"]\",\"%32[^\"]\"",
&tmp, timezone, server0, server1, server2)
&& _parser.recv("OK\n");
_smutex.unlock();
*enable = (done && tmp) ? true : false;
return done;
}
bool ESP8266::get_sntp_time(std::tm *t)
{
_smutex.lock();
char buf[25]; // Thu Aug 04 14:48:05 2016 (always 24 chars + \0)
memset(buf, 0, 25);
bool done = _parser.send("AT+CIPSNTPTIME?")
&& _parser.scanf("+CIPSNTPTIME:%24c", buf)
&& _parser.recv("OK\n");
_smutex.unlock();
if (!done) {
return false;
}
char wday[4] = "\0", mon[4] = "\0";
int mday = 0, hour = 0, min = 0, sec = 0, year = 0;
int ret = sscanf(buf, "%s %s %d %d:%d:%d %d",
wday, mon, &mday, &hour, &min, &sec, &year);
if (ret != 7) {
tr_debug("get_sntp_time(): sscanf returned %d", ret);
return false;
}
t->tm_sec = sec;
t->tm_min = min;
t->tm_hour = hour;
t->tm_mday = mday;
t->tm_wday = 0;
if (strcmp(wday, "Mon") == 0) {
t->tm_wday = 0;
} else if (strcmp(wday, "Tue") == 0) {
t->tm_wday = 1;
} else if (strcmp(wday, "Wed") == 0) {
t->tm_wday = 2;
} else if (strcmp(wday, "Thu") == 0) {
t->tm_wday = 3;
} else if (strcmp(wday, "Fri") == 0) {
t->tm_wday = 4;
} else if (strcmp(wday, "Sat") == 0) {
t->tm_wday = 5;
} else if (strcmp(wday, "Sun") == 0) {
t->tm_wday = 6;
} else {
tr_debug("get_sntp_time(): Invalid weekday: %s", wday);
return false;
}
t->tm_mon = 0;
if (strcmp(mon, "Jan") == 0) {
t->tm_mon = 0;
} else if (strcmp(mon, "Feb") == 0) {
t->tm_mon = 1;
} else if (strcmp(mon, "Mar") == 0) {
t->tm_mon = 2;
} else if (strcmp(mon, "Apr") == 0) {
t->tm_mon = 3;
} else if (strcmp(mon, "May") == 0) {
t->tm_mon = 4;
} else if (strcmp(mon, "Jun") == 0) {
t->tm_mon = 5;
} else if (strcmp(mon, "Jul") == 0) {
t->tm_mon = 6;
} else if (strcmp(mon, "Aug") == 0) {
t->tm_mon = 7;
} else if (strcmp(mon, "Sep") == 0) {
t->tm_mon = 8;
} else if (strcmp(mon, "Oct") == 0) {
t->tm_mon = 9;
} else if (strcmp(mon, "Nov") == 0) {
t->tm_mon = 10;
} else if (strcmp(mon, "Dec") == 0) {
t->tm_mon = 11;
} else {
tr_debug("get_sntp_time(): Invalid month: %s", mon);
return false;
}
t->tm_year = (year - 1900);
return true;
}
bool ESP8266::_recv_ap(nsapi_wifi_ap_t *ap)
{
int sec = NSAPI_SECURITY_UNKNOWN;
int dummy;
int ret;
if (FW_AT_LEAST_VERSION(_at_v.major, _at_v.minor, _at_v.patch, 0, ESP8266_AT_VERSION_WIFI_SCAN_CHANGE)) {
ret = _parser.scanf("(%d,\"%32[^\"]\",%hhd,\"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx\",%hhu,%d,%d,%d,%d,%d,%d)\n",
&sec,
ap->ssid,
&ap->rssi,
&ap->bssid[0], &ap->bssid[1], &ap->bssid[2], &ap->bssid[3], &ap->bssid[4], &ap->bssid[5],
&ap->channel,
&dummy,
&dummy,
&dummy,
&dummy,
&dummy,
&dummy);
} else {
ret = _parser.scanf("(%d,\"%32[^\"]\",%hhd,\"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx\",%hhu,%d,%d)\n",
&sec,
ap->ssid,
&ap->rssi,
&ap->bssid[0], &ap->bssid[1], &ap->bssid[2], &ap->bssid[3], &ap->bssid[4], &ap->bssid[5],
&ap->channel,
&dummy,
&dummy);
}
if (ret < 0) {
_parser.abort();
tr_warning("_recv_ap(): AP info missing.");
}
ap->security = sec < 5 ? (nsapi_security_t)sec : NSAPI_SECURITY_UNKNOWN;
return ret < 0 ? false : true;
}
void ESP8266::_oob_watchdog_reset()
{
MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER, MBED_ERROR_CODE_ETIME), \
"_oob_watchdog_reset() modem watchdog reset triggered\n");
}
void ESP8266::_oob_ready()
{
_reset_done = true;
for (int i = 0; i < SOCKET_COUNT; i++) {
_sock_i[i].open = false;
}
// Makes possible to reinitialize
_conn_status = NSAPI_STATUS_ERROR_UNSUPPORTED;
_conn_stat_cb();
tr_debug("_oob_reset(): Reset detected.");
}
void ESP8266::_oob_busy()
{
char status;
if (_parser.scanf("%c...\n", &status)) {
if (status == 's') {
tr_debug("_oob_busy(): Busy s...");
} else if (status == 'p') {
tr_debug("_oob_busy(): Busy p...");
} else {
tr_error("_oob_busy(): unrecognized busy state '%c...'", status);
MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER, MBED_ERROR_CODE_EBADMSG), \
"ESP8266::_oob_busy() unrecognized busy state\n");
}
} else {
MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER, MBED_ERROR_CODE_ENOMSG), \
"ESP8266::_oob_busy() AT timeout\n");
}
_busy = true;
}
void ESP8266::_oob_tcp_data_hdlr()
{
int32_t len;
MBED_ASSERT(_sock_active_id >= 0 && _sock_active_id < 5);
if (!_parser.scanf("%" SCNd32 ":", &len)) {
return;
}
if (_parser.read(_sock_i[_sock_active_id].tcp_data, len) == -1) {
return;
}
_sock_i[_sock_active_id].tcp_data_rcvd = len;
}
void ESP8266::_oob_scan_results()
{
nsapi_wifi_ap_t ap;
if (_recv_ap(&ap)) {
if (_scan_r.res && _scan_r.cnt < _scan_r.limit) {
_scan_r.res[_scan_r.cnt] = WiFiAccessPoint(ap);
}
_scan_r.cnt++;
}
}
void ESP8266::_oob_connect_err()
{
_fail = false;
_connect_error = 0;
if (_parser.scanf("%d", &_connect_error) && _parser.scanf("FAIL")) {
_fail = true;
_parser.abort();
}
}
void ESP8266::_oob_conn_already()
{
_sock_already = true;
_parser.abort();
}
void ESP8266::_oob_err()
{
_error = true;
_parser.abort();
}
void ESP8266::_oob_socket_close_err()
{
if (_error) {
_error = false;
}
_closed = true; // Not possible to pinpoint to a certain socket
}
void ESP8266::_oob_socket0_closed()
{
static const int id = 0;
_sock_i[id].open = false;
// Closed, so this socket escapes from SEND FAIL status
_clear_socket_sending(id);
tr_debug("_oob_socket0_closed(): Socket %d closed.", id);
}
void ESP8266::_oob_socket1_closed()
{
static const int id = 1;
_sock_i[id].open = false;
// Closed, so this socket escapes from SEND FAIL status
_clear_socket_sending(id);
tr_debug("_oob_socket1_closed(): Socket %d closed.", id);
}
void ESP8266::_oob_socket2_closed()
{
static const int id = 2;
_sock_i[id].open = false;
_clear_socket_sending(id);
tr_debug("_oob_socket2_closed(): Socket %d closed.", id);
}
void ESP8266::_oob_socket3_closed()
{
static const int id = 3;
_sock_i[id].open = false;
_clear_socket_sending(id);
tr_debug("_oob_socket3_closed(): %d closed.", id);
}
void ESP8266::_oob_socket4_closed()
{
static const int id = 4;
_sock_i[id].open = false;
// Closed, so this socket escapes from SEND FAIL status
_clear_socket_sending(id);
tr_debug("_oob_socket0_closed(): Socket %d closed.", id);
}
void ESP8266::_oob_connection_status()
{
char status[13];
if (_parser.recv("%12[^\"]\n", status)) {
if (strcmp(status, "GOT IP\n") == 0) {
_conn_status = NSAPI_STATUS_GLOBAL_UP;
} else if (strcmp(status, "DISCONNECT\n") == 0) {
if (_disconnect) {
_conn_status = NSAPI_STATUS_DISCONNECTED;
_disconnect = false;
} else {
_conn_status = NSAPI_STATUS_CONNECTING;
}
} else if (strcmp(status, "CONNECTED\n") == 0) {
_conn_status = NSAPI_STATUS_CONNECTING;
} else {
tr_error("_oob_connection_status(): Invalid AT cmd \'%s\' .", status);
MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER, MBED_ERROR_CODE_EBADMSG), \
"ESP8266::_oob_connection_status: invalid AT cmd\n");
}
} else {
tr_error("_oob_connection_status(): Network status timeout, disconnecting.");
if (!disconnect()) {
tr_warning("_oob_connection_status(): Driver initiated disconnect failed.");
} else {
tr_debug("_oob_connection_status(): Disconnected.");
}
_conn_status = NSAPI_STATUS_ERROR_UNSUPPORTED;
}
MBED_ASSERT(_conn_stat_cb);
_conn_stat_cb();
}
void ESP8266::_oob_send_ok_received()
{
tr_debug("_oob_send_ok_received called for socket %d", _sock_sending_id);
_sock_sending_id = -1;
}
void ESP8266::_oob_send_fail_received()
{
tr_debug("_oob_send_fail_received called for socket %d", _sock_sending_id);
if (_sock_sending_id >= 0 && _sock_sending_id < SOCKET_COUNT) {
_sock_i[_sock_sending_id].send_fail = true;
}
_sock_sending_id = -1;
}
int8_t ESP8266::default_wifi_mode()
{
int8_t mode;
_smutex.lock();
if (_parser.send("AT+CWMODE_DEF?")
&& _parser.recv("+CWMODE_DEF:%hhd", &mode)
&& _parser.recv("OK\n")) {
_smutex.unlock();
return mode;
}
_smutex.unlock();
return 0;
}
void ESP8266::flush()
{
_smutex.lock();
_parser.flush();
_smutex.unlock();
}
bool ESP8266::set_default_wifi_mode(const int8_t mode)
{
_smutex.lock();
bool done = _parser.send("AT+CWMODE_DEF=%hhd", mode)
&& _parser.recv("OK\n");
_smutex.unlock();
return done;
}
nsapi_connection_status_t ESP8266::connection_status() const
{
return _conn_status;
}
bool ESP8266::set_country_code_policy(bool track_ap, const char *country_code, int channel_start, int channels)
{
if (!(FW_AT_LEAST_VERSION(_at_v.major, _at_v.minor, _at_v.patch, 0, ESP8266_AT_VERSION_WIFI_SCAN_CHANGE))) {
return true;
}
int t_ap = track_ap ? 0 : 1;
_smutex.lock();
bool done = _parser.send("AT+CWCOUNTRY_DEF=%d,\"%s\",%d,%d", t_ap, country_code, channel_start, channels)
&& _parser.recv("OK\n");
if (!done) {
tr_error("\"AT+CWCOUNTRY_DEF=%d,\"%s\",%d,%d\" - FAIL", t_ap, country_code, channel_start, channels);
}
done &= _parser.send("AT+CWCOUNTRY_CUR=%d,\"%s\",%d,%d", t_ap, country_code, channel_start, channels)
&& _parser.recv("OK\n");
if (!done) {
tr_error("\"AT+CWCOUNTRY_CUR=%d,\"%s\",%d,%d\" - FAIL", t_ap, country_code, channel_start, channels);
}
_smutex.unlock();
return done;
}
int ESP8266::uart_enable_input(bool enabled)
{
return _serial.enable_input(enabled);
}
#endif