@@ -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
665711END:
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+
9591022bool 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
11631235void 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
11701244void 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
11771252void 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
11841260void 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
12311318int8_t ESP8266::default_wifi_mode ()
0 commit comments