Skip to content

Commit a85f2bb

Browse files
authored
Merge pull request #13164 from OpenNuvoton/esp8266_send_retry_5.15
ESP8266: Avoid duplicate data sends (5.15)
2 parents 455041e + 935134e commit a85f2bb

File tree

4 files changed

+155
-23
lines changed

4 files changed

+155
-23
lines changed

TESTS/netsocket/udp/udp_tests.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ namespace udp_global {
4343
#ifdef MBED_GREENTEA_TEST_UDPSOCKET_TIMEOUT_S
4444
static const int TESTS_TIMEOUT = MBED_GREENTEA_TEST_UDPSOCKET_TIMEOUT_S;
4545
#else
46-
static const int TESTS_TIMEOUT = 480;
46+
static const int TESTS_TIMEOUT = (20 * 60);
4747
#endif
4848

4949
static const int MAX_SEND_SIZE_IPV4 = 536;

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

Lines changed: 123 additions & 15 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++) {
@@ -562,9 +577,12 @@ nsapi_error_t ESP8266::open_tcp(int id, const char *addr, int port, int keepaliv
562577
// process OOB so that _sock_i reflects the correct state of the socket
563578
_process_oob(ESP8266_SEND_TIMEOUT, true);
564579

565-
if (id >= SOCKET_COUNT || _sock_i[id].open) {
580+
// See the reason above with close()
581+
if (id >= SOCKET_COUNT) {
566582
_smutex.unlock();
567583
return NSAPI_ERROR_PARAMETER;
584+
} else if (_sock_i[id].open) {
585+
close(id);
568586
}
569587

570588
for (int i = 0; i < 2; i++) {
@@ -612,9 +630,24 @@ bool ESP8266::dns_lookup(const char *name, char *ip)
612630
return done;
613631
}
614632

615-
nsapi_error_t ESP8266::send(int id, const void *data, uint32_t amount)
633+
nsapi_size_or_error_t ESP8266::send(int id, const void *data, uint32_t amount)
616634
{
635+
if (_sock_i[id].proto == NSAPI_TCP) {
636+
if (_sock_sending_id >= 0 && _sock_sending_id < SOCKET_COUNT) {
637+
if (!_sock_i[id].send_fail) {
638+
tr_debug("send(): Previous packet (socket %d) was not yet ACK-ed with SEND OK.", _sock_sending_id);
639+
return NSAPI_ERROR_WOULD_BLOCK;
640+
} else {
641+
tr_debug("send(): Previous packet (socket %d) failed.", id);
642+
return NSAPI_ERROR_DEVICE_ERROR;
643+
}
644+
}
645+
}
646+
617647
nsapi_error_t ret = NSAPI_ERROR_DEVICE_ERROR;
648+
int bytes_confirmed = 0;
649+
constexpr unsigned int send_ack_retries = 3;
650+
618651
// +CIPSEND supports up to 2048 bytes at a time
619652
// Data stream can be truncated
620653
if (amount > 2048 && _sock_i[id].proto == NSAPI_TCP) {
@@ -626,6 +659,10 @@ nsapi_error_t ESP8266::send(int id, const void *data, uint32_t amount)
626659
}
627660

628661
_smutex.lock();
662+
// Mark this socket is sending. We allow only one actively sending socket because:
663+
// 1. ESP8266 AT packets 'SEND OK'/'SEND FAIL' are not associated with socket ID. No way to tell them.
664+
// 2. In original implementation, ESP8266::send() is synchronous, which implies only one actively sending socket.
665+
_sock_sending_id = id;
629666
set_timeout(ESP8266_SEND_TIMEOUT);
630667
_busy = false;
631668
_error = false;
@@ -635,37 +672,70 @@ nsapi_error_t ESP8266::send(int id, const void *data, uint32_t amount)
635672
}
636673

637674
if (!_parser.recv(">")) {
675+
// This means ESP8266 hasn't even started to receive data
638676
tr_debug("send(): Didn't get \">\"");
639-
ret = NSAPI_ERROR_WOULD_BLOCK;
677+
if (_sock_i[id].proto == NSAPI_TCP) {
678+
ret = NSAPI_ERROR_WOULD_BLOCK; // Not necessarily critical error.
679+
} else if (_sock_i[id].proto == NSAPI_UDP) {
680+
ret = NSAPI_ERROR_NO_MEMORY;
681+
}
640682
goto END;
641683
}
642684

643-
if (_parser.write((char *)data, (int)amount) >= 0 && _parser.recv("SEND OK")) {
644-
ret = NSAPI_ERROR_OK;
685+
if (_parser.write((char *)data, (int)amount) < 0) {
686+
tr_debug("send(): Failed to write serial data");
687+
// Serial is not working, serious error, reset needed.
688+
ret = NSAPI_ERROR_DEVICE_ERROR;
689+
goto END;
690+
}
691+
692+
// The "Recv X bytes" is not documented.
693+
if (!_parser.recv("Recv %d bytes", &bytes_confirmed)) {
694+
tr_debug("send(): Bytes not confirmed.");
695+
if (_sock_i[id].proto == NSAPI_TCP) {
696+
ret = NSAPI_ERROR_WOULD_BLOCK;
697+
} else if (_sock_i[id].proto == NSAPI_UDP) {
698+
ret = NSAPI_ERROR_NO_MEMORY;
699+
}
700+
} else if (bytes_confirmed != amount && _sock_i[id].proto == NSAPI_UDP) {
701+
tr_debug("send(): Error: confirmed %d bytes, but expected %d.", bytes_confirmed, amount);
702+
ret = NSAPI_ERROR_DEVICE_ERROR;
703+
} else {
704+
// TCP can accept partial writes (if they ever happen)
705+
ret = bytes_confirmed;
645706
}
646707

647708
END:
648709
_process_oob(ESP8266_RECV_TIMEOUT, true); // Drain USART receive register to avoid data overrun
649710

650711
// error hierarchy, from low to high
651-
if (_busy) {
652-
ret = NSAPI_ERROR_WOULD_BLOCK;
653-
tr_debug("send(): Modem busy. ");
654-
}
655-
656-
if (ret == NSAPI_ERROR_DEVICE_ERROR) {
712+
// NOTE: We cannot return NSAPI_ERROR_WOULD_BLOCK when "Recv X bytes" has reached, otherwise duplicate data send.
713+
if (_busy && ret < 0) {
657714
ret = NSAPI_ERROR_WOULD_BLOCK;
658-
tr_debug("send(): Send failed.");
715+
tr_debug("send(): Modem busy.");
659716
}
660717

661718
if (_error) {
719+
// FIXME: Not sure clear or not of _error. See it as device error and it can recover only via reset?
720+
_sock_sending_id = -1;
662721
ret = NSAPI_ERROR_CONNECTION_LOST;
663722
tr_debug("send(): Connection disrupted.");
664723
}
665724

666-
if (!_sock_i[id].open && ret != NSAPI_ERROR_OK) {
725+
if (_sock_i[id].send_fail) {
726+
_sock_sending_id = -1;
727+
if (_sock_i[id].proto == NSAPI_TCP) {
728+
ret = NSAPI_ERROR_DEVICE_ERROR;
729+
} else {
730+
ret = NSAPI_ERROR_NO_MEMORY;
731+
}
732+
tr_debug("send(): SEND FAIL received.");
733+
}
734+
735+
if (!_sock_i[id].open && ret < 0) {
736+
_sock_sending_id = -1;
667737
ret = NSAPI_ERROR_CONNECTION_LOST;
668-
tr_debug("send(): Socket closed abruptly.");
738+
tr_debug("send(): Socket %d closed abruptly.", id);
669739
}
670740

671741
set_timeout();
@@ -938,6 +1008,14 @@ void ESP8266::_clear_socket_packets(int id)
9381008
}
9391009
}
9401010

1011+
void ESP8266::_clear_socket_sending(int id)
1012+
{
1013+
if (id == _sock_sending_id) {
1014+
_sock_sending_id = -1;
1015+
}
1016+
_sock_i[id].send_fail = false;
1017+
}
1018+
9411019
bool ESP8266::close(int id)
9421020
{
9431021
//May take a second try if device is busy
@@ -949,20 +1027,27 @@ bool ESP8266::close(int id)
9491027
_closed = false;
9501028
_sock_i[id].open = false;
9511029
_clear_socket_packets(id);
1030+
// Closed, so this socket escapes from SEND FAIL status.
1031+
_clear_socket_sending(id);
9521032
_smutex.unlock();
9531033
// ESP8266 has a habit that it might close a socket on its own.
1034+
tr_debug("close(%d): socket close OK with UNLINK ERROR", id);
9541035
return true;
9551036
}
9561037
} else {
9571038
// _sock_i[id].open set to false with an OOB
9581039
_clear_socket_packets(id);
1040+
// Closed, so this socket escapes from SEND FAIL status
1041+
_clear_socket_sending(id);
9591042
_smutex.unlock();
1043+
tr_debug("close(%d): socket close OK with AT+CIPCLOSE OK", id);
9601044
return true;
9611045
}
9621046
}
9631047
_smutex.unlock();
9641048
}
9651049

1050+
tr_debug("close(%d): socket close FAIL'ed (spurious close)", id);
9661051
return false;
9671052
}
9681053

@@ -1140,34 +1225,42 @@ void ESP8266::_oob_socket0_closed()
11401225
{
11411226
static const int id = 0;
11421227
_sock_i[id].open = false;
1228+
// Closed, so this socket escapes from SEND FAIL status
1229+
_clear_socket_sending(id);
11431230
tr_debug("_oob_socket0_closed(): Socket %d closed.", id);
11441231
}
11451232

11461233
void ESP8266::_oob_socket1_closed()
11471234
{
11481235
static const int id = 1;
11491236
_sock_i[id].open = false;
1237+
// Closed, so this socket escapes from SEND FAIL status
1238+
_clear_socket_sending(id);
11501239
tr_debug("_oob_socket1_closed(): Socket %d closed.", id);
11511240
}
11521241

11531242
void ESP8266::_oob_socket2_closed()
11541243
{
11551244
static const int id = 2;
11561245
_sock_i[id].open = false;
1246+
_clear_socket_sending(id);
11571247
tr_debug("_oob_socket2_closed(): Socket %d closed.", id);
11581248
}
11591249

11601250
void ESP8266::_oob_socket3_closed()
11611251
{
11621252
static const int id = 3;
11631253
_sock_i[id].open = false;
1254+
_clear_socket_sending(id);
11641255
tr_debug("_oob_socket3_closed(): %d closed.", id);
11651256
}
11661257

11671258
void ESP8266::_oob_socket4_closed()
11681259
{
11691260
static const int id = 4;
11701261
_sock_i[id].open = false;
1262+
// Closed, so this socket escapes from SEND FAIL status
1263+
_clear_socket_sending(id);
11711264
tr_debug("_oob_socket0_closed(): Socket %d closed.", id);
11721265
}
11731266

@@ -1205,6 +1298,21 @@ void ESP8266::_oob_connection_status()
12051298
_conn_stat_cb();
12061299
}
12071300

1301+
void ESP8266::_oob_send_ok_received()
1302+
{
1303+
tr_debug("_oob_send_ok_received called for socket %d", _sock_sending_id);
1304+
_sock_sending_id = -1;
1305+
}
1306+
1307+
void ESP8266::_oob_send_fail_received()
1308+
{
1309+
tr_debug("_oob_send_fail_received called for socket %d", _sock_sending_id);
1310+
if (_sock_sending_id >= 0 && _sock_sending_id < SOCKET_COUNT) {
1311+
_sock_i[_sock_sending_id].send_fail = true;
1312+
}
1313+
_sock_sending_id = -1;
1314+
}
1315+
12081316
int8_t ESP8266::default_wifi_mode()
12091317
{
12101318
int8_t mode;

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

Lines changed: 8 additions & 3 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,6 +470,8 @@ class ESP8266 {
469470
void _oob_tcp_data_hdlr();
470471
void _oob_ready();
471472
void _oob_scan_results();
473+
void _oob_send_ok_received();
474+
void _oob_send_fail_received();
472475

473476
// OOB state variables
474477
int _connect_error;
@@ -479,6 +482,7 @@ class ESP8266 {
479482
bool _error;
480483
bool _busy;
481484
bool _reset_done;
485+
int _sock_sending_id;
482486

483487
// Modem's address info
484488
char _ip_buffer[16];
@@ -493,6 +497,7 @@ class ESP8266 {
493497
char *tcp_data;
494498
int32_t tcp_data_avbl; // Data waiting on modem
495499
int32_t tcp_data_rcvd;
500+
bool send_fail; // Received 'SEND FAIL'. Expect user will close the socket.
496501
};
497502
struct _sock_info _sock_i[SOCKET_COUNT];
498503

components/wifi/esp8266-driver/ESP8266Interface.cpp

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -775,8 +775,27 @@ int ESP8266Interface::socket_close(void *handle)
775775
return NSAPI_ERROR_NO_SOCKET;
776776
}
777777

778-
if (socket->connected && !_esp.close(socket->id)) {
779-
err = NSAPI_ERROR_DEVICE_ERROR;
778+
// With send(...) changing to non-block, modem can be busy in
779+
// sending previous data and 'busy' the current 'AT+CIPCLOSE'
780+
// command. In blocking mode, add retries to avoid spurious
781+
// close to some degree. This is required to pass GT netsocket-tcp/
782+
// netsocket-tls tests which expect close(...) to be OK.
783+
if (socket->connected) {
784+
if (_if_blocking) {
785+
err = NSAPI_ERROR_DEVICE_ERROR;
786+
for (int i = 0; i < 10; i ++) {
787+
if (_esp.close(socket->id)) {
788+
err = 0;
789+
break;
790+
} else {
791+
rtos::ThisThread::sleep_for(1000);
792+
}
793+
}
794+
} else {
795+
if (!_esp.close(socket->id)) {
796+
err = NSAPI_ERROR_WOULD_BLOCK;
797+
}
798+
}
780799
}
781800

782801
_cbs[socket->id].callback = NULL;
@@ -849,7 +868,7 @@ int ESP8266Interface::socket_accept(void *server, void **socket, SocketAddress *
849868

850869
int ESP8266Interface::socket_send(void *handle, const void *data, unsigned size)
851870
{
852-
nsapi_error_t status;
871+
nsapi_size_or_error_t status;
853872
struct esp8266_socket *socket = (struct esp8266_socket *)handle;
854873
uint8_t expect_false = false;
855874

@@ -881,7 +900,7 @@ int ESP8266Interface::socket_send(void *handle, const void *data, unsigned size)
881900
status = NSAPI_ERROR_DEVICE_ERROR;
882901
}
883902

884-
return status != NSAPI_ERROR_OK ? status : size;
903+
return status;
885904
}
886905

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

0 commit comments

Comments
 (0)