Skip to content

Commit f4842c7

Browse files
authored
Merge pull request #12157 from michalpasztamobica/esp8266_send_retry
ESP8266: Avoid duplicate data sends
2 parents 4922d10 + ea2f36e commit f4842c7

File tree

4 files changed

+131
-41
lines changed

4 files changed

+131
-41
lines changed

TESTS/netsocket/udp/udp_tests.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ static const int TESTS_TIMEOUT = MBED_GREENTEA_TEST_UDPSOCKET_TIMEOUT_S;
4848
#if MBED_CONF_TARGET_NETWORK_DEFAULT_INTERFACE_TYPE == MESH && MBED_CONF_NSAPI_DEFAULT_MESH_TYPE == WISUN
4949
static const int TESTS_TIMEOUT = (25 * 60);
5050
#else
51-
static const int TESTS_TIMEOUT = (10 * 60);
51+
static const int TESTS_TIMEOUT = (20 * 60);
5252
#endif
5353
#endif
5454

components/wifi/esp8266-driver/ESP8266/ESP8266.cpp

Lines changed: 120 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ ESP8266::ESP8266(PinName tx, PinName rx, bool debug, PinName rts, PinName cts)
5858
_error(false),
5959
_busy(false),
6060
_reset_done(false),
61+
_sock_sending_id(-1),
6162
_conn_status(NSAPI_STATUS_DISCONNECTED)
6263
{
6364
_serial.set_baud(MBED_CONF_ESP8266_SERIAL_BAUDRATE);
@@ -89,13 +90,18 @@ ESP8266::ESP8266(PinName tx, PinName rx, bool debug, PinName rts, PinName cts)
8990
_parser.oob("busy ", callback(this, &ESP8266::_oob_busy));
9091
// NOTE: documentation v3.0 says '+CIPRECVDATA:<data_len>,' but it's not how the FW responds...
9192
_parser.oob("+CIPRECVDATA,", callback(this, &ESP8266::_oob_tcp_data_hdlr));
93+
// Register 'SEND OK'/'SEND FAIL' oobs here. Don't get involved in oob management with send status
94+
// because ESP8266 modem possibly doesn't reply these packets on error case.
95+
_parser.oob("SEND OK", callback(this, &ESP8266::_oob_send_ok_received));
96+
_parser.oob("SEND FAIL", callback(this, &ESP8266::_oob_send_fail_received));
9297

9398
for (int i = 0; i < SOCKET_COUNT; i++) {
9499
_sock_i[i].open = false;
95100
_sock_i[i].proto = NSAPI_UDP;
96101
_sock_i[i].tcp_data = NULL;
97102
_sock_i[i].tcp_data_avbl = 0;
98103
_sock_i[i].tcp_data_rcvd = 0;
104+
_sock_i[i].send_fail = false;
99105
}
100106

101107
_scan_r.res = NULL;
@@ -288,6 +294,7 @@ bool ESP8266::reset(void)
288294
tr_debug("reset(): Done: %s.", done ? "OK" : "FAIL");
289295

290296
_clear_socket_packets(ESP8266_ALL_SOCKET_IDS);
297+
_sock_sending_id = -1;
291298
set_timeout();
292299
_smutex.unlock();
293300

@@ -511,9 +518,17 @@ nsapi_error_t ESP8266::open_udp(int id, const char *addr, int port, int local_po
511518
// process OOB so that _sock_i reflects the correct state of the socket
512519
_process_oob(ESP8266_SEND_TIMEOUT, true);
513520

514-
if (id >= SOCKET_COUNT || _sock_i[id].open) {
521+
// Previous close() can fail with busy in sending. Usually, user will ignore the close()
522+
// error code and cause 'spurious close', in which case user has closed the socket but ESP8266 modem
523+
// hasn't yet. Because we don't know how long ESP8266 modem will trap in busy, enlarge retry count
524+
// or timeout in close() isn't a nice way. Here, we actively re-call close() in open() to let the modem
525+
// close the socket. User can re-try open() on failure. Without this active close(), open() can fail forever
526+
// with previous 'spurious close', unless peer closes the socket and so ESP8266 modem closes it accordingly.
527+
if (id >= SOCKET_COUNT) {
515528
_smutex.unlock();
516529
return NSAPI_ERROR_PARAMETER;
530+
} else if (_sock_i[id].open) {
531+
close(id);
517532
}
518533

519534
for (int i = 0; i < 2; i++) {
@@ -565,9 +580,12 @@ nsapi_error_t ESP8266::open_tcp(int id, const char *addr, int port, int keepaliv
565580
// process OOB so that _sock_i reflects the correct state of the socket
566581
_process_oob(ESP8266_SEND_TIMEOUT, true);
567582

568-
if (id >= SOCKET_COUNT || _sock_i[id].open) {
583+
// See the reason above with close()
584+
if (id >= SOCKET_COUNT) {
569585
_smutex.unlock();
570586
return NSAPI_ERROR_PARAMETER;
587+
} else if (_sock_i[id].open) {
588+
close(id);
571589
}
572590

573591
for (int i = 0; i < 2; i++) {
@@ -615,9 +633,24 @@ bool ESP8266::dns_lookup(const char *name, char *ip)
615633
return done;
616634
}
617635

618-
nsapi_error_t ESP8266::send(int id, const void *data, uint32_t amount)
636+
nsapi_size_or_error_t ESP8266::send(int id, const void *data, uint32_t amount)
619637
{
638+
if (_sock_i[id].proto == NSAPI_TCP) {
639+
if (_sock_sending_id >= 0 && _sock_sending_id < SOCKET_COUNT) {
640+
if (!_sock_i[id].send_fail) {
641+
tr_debug("send(): Previous packet (socket %d) was not yet ACK-ed with SEND OK.", _sock_sending_id);
642+
return NSAPI_ERROR_WOULD_BLOCK;
643+
} else {
644+
tr_debug("send(): Previous packet (socket %d) failed.", id);
645+
return NSAPI_ERROR_DEVICE_ERROR;
646+
}
647+
}
648+
}
649+
620650
nsapi_error_t ret = NSAPI_ERROR_DEVICE_ERROR;
651+
int bytes_confirmed = 0;
652+
constexpr unsigned int send_ack_retries = 3;
653+
621654
// +CIPSEND supports up to 2048 bytes at a time
622655
// Data stream can be truncated
623656
if (amount > 2048 && _sock_i[id].proto == NSAPI_TCP) {
@@ -629,7 +662,10 @@ nsapi_error_t ESP8266::send(int id, const void *data, uint32_t amount)
629662
}
630663

631664
_smutex.lock();
632-
RETRY:
665+
// Mark this socket is sending. We allow only one actively sending socket because:
666+
// 1. ESP8266 AT packets 'SEND OK'/'SEND FAIL' are not associated with socket ID. No way to tell them.
667+
// 2. In original implementation, ESP8266::send() is synchronous, which implies only one actively sending socket.
668+
_sock_sending_id = id;
633669
set_timeout(ESP8266_SEND_TIMEOUT);
634670
_busy = false;
635671
_error = false;
@@ -638,52 +674,71 @@ nsapi_error_t ESP8266::send(int id, const void *data, uint32_t amount)
638674
goto END;
639675
}
640676

641-
//We might receive "busy s/p..." and "OK" from modem, so we need to check that also
642-
_ok_received = false;
643-
_parser.oob("OK", callback(this, &ESP8266::_oob_ok_received));
644-
645677
if (!_parser.recv(">")) {
646-
_parser.remove_oob("OK");
647-
if (_busy) {
648-
if (_ok_received) {
649-
goto RETRY;
650-
} else if (_parser.recv("OK")) {
651-
goto RETRY;
652-
}
653-
}
678+
// This means ESP8266 hasn't even started to receive data
654679
tr_debug("send(): Didn't get \">\"");
655-
ret = NSAPI_ERROR_WOULD_BLOCK;
680+
if (_sock_i[id].proto == NSAPI_TCP) {
681+
ret = NSAPI_ERROR_WOULD_BLOCK; // Not necessarily critical error.
682+
} else if (_sock_i[id].proto == NSAPI_UDP) {
683+
ret = NSAPI_ERROR_NO_MEMORY;
684+
}
685+
goto END;
686+
}
687+
688+
if (_parser.write((char *)data, (int)amount) < 0) {
689+
tr_debug("send(): Failed to write serial data");
690+
// Serial is not working, serious error, reset needed.
691+
ret = NSAPI_ERROR_DEVICE_ERROR;
656692
goto END;
657693
}
658-
_ok_received = false;
659-
_parser.remove_oob("OK");
660694

661-
if (_parser.write((char *)data, (int)amount) >= 0 && _parser.recv("SEND OK")) {
662-
ret = NSAPI_ERROR_OK;
695+
// The "Recv X bytes" is not documented.
696+
if (!_parser.recv("Recv %d bytes", &bytes_confirmed)) {
697+
tr_debug("send(): Bytes not confirmed.");
698+
if (_sock_i[id].proto == NSAPI_TCP) {
699+
ret = NSAPI_ERROR_WOULD_BLOCK;
700+
} else if (_sock_i[id].proto == NSAPI_UDP) {
701+
ret = NSAPI_ERROR_NO_MEMORY;
702+
}
703+
} else if (bytes_confirmed != amount && _sock_i[id].proto == NSAPI_UDP) {
704+
tr_debug("send(): Error: confirmed %d bytes, but expected %d.", bytes_confirmed, amount);
705+
ret = NSAPI_ERROR_DEVICE_ERROR;
706+
} else {
707+
// TCP can accept partial writes (if they ever happen)
708+
ret = bytes_confirmed;
663709
}
664710

665711
END:
666712
_process_oob(ESP8266_RECV_TIMEOUT, true); // Drain USART receive register to avoid data overrun
667713

668714
// error hierarchy, from low to high
669-
if (_busy) {
670-
ret = NSAPI_ERROR_WOULD_BLOCK;
671-
tr_debug("send(): Modem busy. ");
672-
}
673-
674-
if (ret == NSAPI_ERROR_DEVICE_ERROR) {
715+
// NOTE: We cannot return NSAPI_ERROR_WOULD_BLOCK when "Recv X bytes" has reached, otherwise duplicate data send.
716+
if (_busy && ret < 0) {
675717
ret = NSAPI_ERROR_WOULD_BLOCK;
676-
tr_debug("send(): Send failed.");
718+
tr_debug("send(): Modem busy.");
677719
}
678720

679721
if (_error) {
722+
// FIXME: Not sure clear or not of _error. See it as device error and it can recover only via reset?
723+
_sock_sending_id = -1;
680724
ret = NSAPI_ERROR_CONNECTION_LOST;
681725
tr_debug("send(): Connection disrupted.");
682726
}
683727

684-
if (!_sock_i[id].open && ret != NSAPI_ERROR_OK) {
728+
if (_sock_i[id].send_fail) {
729+
_sock_sending_id = -1;
730+
if (_sock_i[id].proto == NSAPI_TCP) {
731+
ret = NSAPI_ERROR_DEVICE_ERROR;
732+
} else {
733+
ret = NSAPI_ERROR_NO_MEMORY;
734+
}
735+
tr_debug("send(): SEND FAIL received.");
736+
}
737+
738+
if (!_sock_i[id].open && ret < 0) {
739+
_sock_sending_id = -1;
685740
ret = NSAPI_ERROR_CONNECTION_LOST;
686-
tr_debug("send(): Socket closed abruptly.");
741+
tr_debug("send(): Socket %d closed abruptly.", id);
687742
}
688743

689744
set_timeout();
@@ -956,6 +1011,14 @@ void ESP8266::_clear_socket_packets(int id)
9561011
}
9571012
}
9581013

1014+
void ESP8266::_clear_socket_sending(int id)
1015+
{
1016+
if (id == _sock_sending_id) {
1017+
_sock_sending_id = -1;
1018+
}
1019+
_sock_i[id].send_fail = false;
1020+
}
1021+
9591022
bool ESP8266::close(int id)
9601023
{
9611024
//May take a second try if device is busy
@@ -967,20 +1030,27 @@ bool ESP8266::close(int id)
9671030
_closed = false;
9681031
_sock_i[id].open = false;
9691032
_clear_socket_packets(id);
1033+
// Closed, so this socket escapes from SEND FAIL status.
1034+
_clear_socket_sending(id);
9701035
_smutex.unlock();
9711036
// ESP8266 has a habit that it might close a socket on its own.
1037+
tr_debug("close(%d): socket close OK with UNLINK ERROR", id);
9721038
return true;
9731039
}
9741040
} else {
9751041
// _sock_i[id].open set to false with an OOB
9761042
_clear_socket_packets(id);
1043+
// Closed, so this socket escapes from SEND FAIL status
1044+
_clear_socket_sending(id);
9771045
_smutex.unlock();
1046+
tr_debug("close(%d): socket close OK with AT+CIPCLOSE OK", id);
9781047
return true;
9791048
}
9801049
}
9811050
_smutex.unlock();
9821051
}
9831052

1053+
tr_debug("close(%d): socket close FAIL'ed (spurious close)", id);
9841054
return false;
9851055
}
9861056

@@ -1157,34 +1227,42 @@ void ESP8266::_oob_socket0_closed()
11571227
{
11581228
static const int id = 0;
11591229
_sock_i[id].open = false;
1230+
// Closed, so this socket escapes from SEND FAIL status
1231+
_clear_socket_sending(id);
11601232
tr_debug("_oob_socket0_closed(): Socket %d closed.", id);
11611233
}
11621234

11631235
void ESP8266::_oob_socket1_closed()
11641236
{
11651237
static const int id = 1;
11661238
_sock_i[id].open = false;
1239+
// Closed, so this socket escapes from SEND FAIL status
1240+
_clear_socket_sending(id);
11671241
tr_debug("_oob_socket1_closed(): Socket %d closed.", id);
11681242
}
11691243

11701244
void ESP8266::_oob_socket2_closed()
11711245
{
11721246
static const int id = 2;
11731247
_sock_i[id].open = false;
1248+
_clear_socket_sending(id);
11741249
tr_debug("_oob_socket2_closed(): Socket %d closed.", id);
11751250
}
11761251

11771252
void ESP8266::_oob_socket3_closed()
11781253
{
11791254
static const int id = 3;
11801255
_sock_i[id].open = false;
1256+
_clear_socket_sending(id);
11811257
tr_debug("_oob_socket3_closed(): %d closed.", id);
11821258
}
11831259

11841260
void ESP8266::_oob_socket4_closed()
11851261
{
11861262
static const int id = 4;
11871263
_sock_i[id].open = false;
1264+
// Closed, so this socket escapes from SEND FAIL status
1265+
_clear_socket_sending(id);
11881266
tr_debug("_oob_socket0_closed(): Socket %d closed.", id);
11891267
}
11901268

@@ -1222,10 +1300,19 @@ void ESP8266::_oob_connection_status()
12221300
_conn_stat_cb();
12231301
}
12241302

1225-
void ESP8266::_oob_ok_received()
1303+
void ESP8266::_oob_send_ok_received()
12261304
{
1227-
tr_debug("_oob_ok_received called");
1228-
_ok_received = true;
1305+
tr_debug("_oob_send_ok_received called for socket %d", _sock_sending_id);
1306+
_sock_sending_id = -1;
1307+
}
1308+
1309+
void ESP8266::_oob_send_fail_received()
1310+
{
1311+
tr_debug("_oob_send_fail_received called for socket %d", _sock_sending_id);
1312+
if (_sock_sending_id >= 0 && _sock_sending_id < SOCKET_COUNT) {
1313+
_sock_i[_sock_sending_id].send_fail = true;
1314+
}
1315+
_sock_sending_id = -1;
12291316
}
12301317

12311318
int8_t ESP8266::default_wifi_mode()

components/wifi/esp8266-driver/ESP8266/ESP8266.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,10 @@ class ESP8266 {
255255
*
256256
* @param id id of socket to send to
257257
* @param data data to be sent
258-
* @param amount amount of data to be sent - max 1024
259-
* @return NSAPI_ERROR_OK in success, negative error code in failure
258+
* @param amount amount of data to be sent - max 2048
259+
* @return number of bytes on success, negative error code in failure
260260
*/
261-
nsapi_error_t send(int id, const void *data, uint32_t amount);
261+
nsapi_size_or_error_t send(int id, const void *data, uint32_t amount);
262262

263263
/**
264264
* Receives datagram from an open UDP socket
@@ -444,6 +444,7 @@ class ESP8266 {
444444
// data follows
445445
} *_packets, * *_packets_end;
446446
void _clear_socket_packets(int id);
447+
void _clear_socket_sending(int id);
447448
int _sock_active_id;
448449

449450
// Memory statistics
@@ -469,7 +470,8 @@ class ESP8266 {
469470
void _oob_tcp_data_hdlr();
470471
void _oob_ready();
471472
void _oob_scan_results();
472-
void _oob_ok_received();
473+
void _oob_send_ok_received();
474+
void _oob_send_fail_received();
473475

474476
// OOB state variables
475477
int _connect_error;
@@ -480,7 +482,7 @@ class ESP8266 {
480482
bool _error;
481483
bool _busy;
482484
bool _reset_done;
483-
bool _ok_received;
485+
int _sock_sending_id;
484486

485487
// Modem's address info
486488
char _ip_buffer[16];
@@ -495,6 +497,7 @@ class ESP8266 {
495497
char *tcp_data;
496498
int32_t tcp_data_avbl; // Data waiting on modem
497499
int32_t tcp_data_rcvd;
500+
bool send_fail; // Received 'SEND FAIL'. Expect user will close the socket.
498501
};
499502
struct _sock_info _sock_i[SOCKET_COUNT];
500503

components/wifi/esp8266-driver/ESP8266Interface.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ int ESP8266Interface::socket_accept(void *server, void **socket, SocketAddress *
849849

850850
int ESP8266Interface::socket_send(void *handle, const void *data, unsigned size)
851851
{
852-
nsapi_error_t status;
852+
nsapi_size_or_error_t status;
853853
struct esp8266_socket *socket = (struct esp8266_socket *)handle;
854854
uint8_t expect_false = false;
855855

@@ -881,7 +881,7 @@ int ESP8266Interface::socket_send(void *handle, const void *data, unsigned size)
881881
status = NSAPI_ERROR_DEVICE_ERROR;
882882
}
883883

884-
return status != NSAPI_ERROR_OK ? status : size;
884+
return status;
885885
}
886886

887887
int ESP8266Interface::socket_recv(void *handle, void *data, unsigned size)

0 commit comments

Comments
 (0)