ESP8266: add a retry mechanism to avoid duplicate data sends

We are now checking if ESP8266 has confirmed receiving data over serial
port with an undocumented (but existing) "Recv x bytes" message. Next we
are explicitly waiting for an official "SEND OK".
pull/12157/head
Michal Paszta 2019-12-20 12:18:05 +02:00
parent 5f495db06b
commit de2896c659
2 changed files with 82 additions and 30 deletions

View File

@ -58,6 +58,8 @@ ESP8266::ESP8266(PinName tx, PinName rx, bool debug, PinName rts, PinName cts)
_error(false),
_busy(false),
_reset_done(false),
_prev_send_ok_pending(false),
_send_fail_received(false),
_conn_status(NSAPI_STATUS_DISCONNECTED)
{
_serial.set_baud(MBED_CONF_ESP8266_SERIAL_BAUDRATE);
@ -614,7 +616,16 @@ bool ESP8266::dns_lookup(const char *name, char *ip)
nsapi_error_t ESP8266::send(int id, const void *data, uint32_t amount)
{
if (_prev_send_ok_pending && _sock_i[id].proto == NSAPI_TCP) {
tr_debug("send(): Previous packet was not ACK-ed with SEND OK.");
return NSAPI_ERROR_WOULD_BLOCK;
}
nsapi_error_t ret = NSAPI_ERROR_DEVICE_ERROR;
_send_fail_received = false;
int bytes_confirmed = 0;
constexpr unsigned int send_ack_retries = 3;
// +CIPSEND supports up to 2048 bytes at a time
// Data stream can be truncated
if (amount > 2048 && _sock_i[id].proto == NSAPI_TCP) {
@ -626,7 +637,6 @@ nsapi_error_t ESP8266::send(int id, const void *data, uint32_t amount)
}
_smutex.lock();
RETRY:
set_timeout(ESP8266_SEND_TIMEOUT);
_busy = false;
_error = false;
@ -635,42 +645,67 @@ RETRY:
goto END;
}
//We might receive "busy s/p..." and "OK" from modem, so we need to check that also
_ok_received = false;
_parser.oob("OK", callback(this, &ESP8266::_oob_ok_received));
if (!_parser.recv(">")) {
_parser.remove_oob("OK");
if (_busy) {
if (_ok_received) {
goto RETRY;
} else if (_parser.recv("OK")) {
goto RETRY;
}
}
// This means ESP8266 hasn't even started to receive data
tr_debug("send(): Didn't get \">\"");
ret = NSAPI_ERROR_WOULD_BLOCK;
if (_sock_i[id].proto == NSAPI_TCP) {
ret = NSAPI_ERROR_WOULD_BLOCK; // Not neccesarily critical error.
} else if (_sock_i[id].proto == NSAPI_UDP) {
ret = NSAPI_ERROR_NO_MEMORY;
}
goto END;
}
_ok_received = false;
_parser.remove_oob("OK");
if (_parser.write((char *)data, (int)amount) >= 0 && _parser.recv("SEND OK")) {
ret = NSAPI_ERROR_OK;
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.");
ret = NSAPI_ERROR_DEVICE_ERROR;
goto END;
} else if (bytes_confirmed != amount) {
tr_debug("send(): Error: confirmed %d bytes, but expected %d.", bytes_confirmed, amount);
ret = NSAPI_ERROR_DEVICE_ERROR;
goto END;
}
//We might receive "busy s/p...", "SEND OK" or "SEND FAIL" from modem, so we need to check that also
_parser.oob("SEND FAIL", callback(this, &ESP8266::_oob_send_fail_received));
for (unsigned int i = send_ack_retries; i > 0; i--) {
if (!_parser.recv("SEND OK")) {
if (_error || _send_fail_received) {
_parser.remove_oob("SEND FAIL");
goto END;
}
if (_busy) {
_busy = false;
tr_debug("send(): Busy, %d retries left...", i - 1);
} else {
tr_debug("send(): Not busy, but no SEND OK. %d retries left...", i - 1);
}
} else {
ret = amount; // Got "SEND OK" - return number of bytes.
goto END;
}
}
// ESP8266 ACKed data over serial, but did not ACK over TCP or report any error.
_prev_send_ok_pending = true;
_parser.oob("SEND OK", callback(this, &ESP8266::_oob_send_ok_received));
ret = amount;
END:
_process_oob(ESP8266_RECV_TIMEOUT, true); // Drain USART receive register to avoid data overrun
// error hierarchy, from low to high
if (_busy) {
ret = NSAPI_ERROR_WOULD_BLOCK;
tr_debug("send(): Modem busy. ");
}
if (ret == NSAPI_ERROR_DEVICE_ERROR) {
ret = NSAPI_ERROR_WOULD_BLOCK;
tr_debug("send(): Send failed.");
tr_debug("send(): Modem busy.");
}
if (_error) {
@ -678,7 +713,16 @@ END:
tr_debug("send(): Connection disrupted.");
}
if (!_sock_i[id].open && ret != NSAPI_ERROR_OK) {
if (_send_fail_received) {
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) {
ret = NSAPI_ERROR_CONNECTION_LOST;
tr_debug("send(): Socket closed abruptly.");
}
@ -1219,10 +1263,16 @@ void ESP8266::_oob_connection_status()
_conn_stat_cb();
}
void ESP8266::_oob_ok_received()
void ESP8266::_oob_send_ok_received()
{
tr_debug("_oob_ok_received called");
_ok_received = true;
tr_debug("_oob_send_ok_received called");
_prev_send_ok_pending = false;
}
void ESP8266::_oob_send_fail_received()
{
tr_debug("_oob_send_fail_received called");
_send_fail_received = true;
}
int8_t ESP8266::default_wifi_mode()

View File

@ -469,7 +469,8 @@ private:
void _oob_tcp_data_hdlr();
void _oob_ready();
void _oob_scan_results();
void _oob_ok_received();
void _oob_send_ok_received();
void _oob_send_fail_received();
// OOB state variables
int _connect_error;
@ -480,7 +481,8 @@ private:
bool _error;
bool _busy;
bool _reset_done;
bool _ok_received;
bool _prev_send_ok_pending;
bool _send_fail_received;
// Modem's address info
char _ip_buffer[16];