Skip to content

Commit 4405ab4

Browse files
ccli8michalpasztamobica
authored andcommitted
ESP8266: Simplify flow control and enable per-socket reconnection
1. Fix 'spurious close' by adding close() in open(). 'spurious close' gets frequent and cannot ignore when send() changes to asynchronous. User can retry open() until 'spurious close' gets true. 2. 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. 3. Register 'SEND OK'/'SEND FAIL' oobs, like others in ESP8266::ESP8266 constructor. Don't get involved in oob management with send status because ESP8266 modem possibly doesn't reply these packets on error case. 4. Now that ESP8266::send() changes to asynchronous, drop the code with _parser.recv("SEND OK")/_parser.recv("SEND FAIL"). _parser.recv("SEND OK")/_parser.recv("SEND FAIL") and 'SEND OK'/'SEND FAIL' oobs both consume 'SEND OK'/'SEND FAIL' packets and complicate flow control.
1 parent 6dfa2d7 commit 4405ab4

File tree

2 files changed

+85
-73
lines changed

2 files changed

+85
-73
lines changed

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

Lines changed: 83 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +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-
_send_status(SEND_STATUS_OK),
61+
_sock_sending_id(-1),
6262
_conn_status(NSAPI_STATUS_DISCONNECTED)
6363
{
6464
_serial.set_baud(MBED_CONF_ESP8266_SERIAL_BAUDRATE);
@@ -90,13 +90,18 @@ ESP8266::ESP8266(PinName tx, PinName rx, bool debug, PinName rts, PinName cts)
9090
_parser.oob("busy ", callback(this, &ESP8266::_oob_busy));
9191
// NOTE: documentation v3.0 says '+CIPRECVDATA:<data_len>,' but it's not how the FW responds...
9292
_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));
9397

9498
for (int i = 0; i < SOCKET_COUNT; i++) {
9599
_sock_i[i].open = false;
96100
_sock_i[i].proto = NSAPI_UDP;
97101
_sock_i[i].tcp_data = NULL;
98102
_sock_i[i].tcp_data_avbl = 0;
99103
_sock_i[i].tcp_data_rcvd = 0;
104+
_sock_i[i].send_fail = false;
100105
}
101106

102107
_scan_r.res = NULL;
@@ -289,9 +294,7 @@ bool ESP8266::reset(void)
289294
tr_debug("reset(): Done: %s.", done ? "OK" : "FAIL");
290295

291296
_clear_socket_packets(ESP8266_ALL_SOCKET_IDS);
292-
_send_status = SEND_STATUS_OK;
293-
_parser.remove_oob("SEND OK");
294-
_parser.remove_oob("SEND FAIL");
297+
_sock_sending_id = -1;
295298
set_timeout();
296299
_smutex.unlock();
297300

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

518-
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) {
519528
_smutex.unlock();
520529
return NSAPI_ERROR_PARAMETER;
530+
} else if (_sock_i[id].open) {
531+
close(id);
521532
}
522533

523534
for (int i = 0; i < 2; i++) {
@@ -566,9 +577,12 @@ nsapi_error_t ESP8266::open_tcp(int id, const char *addr, int port, int keepaliv
566577
// process OOB so that _sock_i reflects the correct state of the socket
567578
_process_oob(ESP8266_SEND_TIMEOUT, true);
568579

569-
if (id >= SOCKET_COUNT || _sock_i[id].open) {
580+
// See the reason above with close()
581+
if (id >= SOCKET_COUNT) {
570582
_smutex.unlock();
571583
return NSAPI_ERROR_PARAMETER;
584+
} else if (_sock_i[id].open) {
585+
close(id);
572586
}
573587

574588
for (int i = 0; i < 2; i++) {
@@ -619,13 +633,14 @@ bool ESP8266::dns_lookup(const char *name, char *ip)
619633
nsapi_size_or_error_t ESP8266::send(int id, const void *data, uint32_t amount)
620634
{
621635
if (_sock_i[id].proto == NSAPI_TCP) {
622-
if (_send_status == SEND_STATUS_PENDING) {
623-
tr_debug("send(): Previous packet was not yet ACK-ed with SEND OK.");
624-
return NSAPI_ERROR_WOULD_BLOCK;
625-
} else if (_send_status == SEND_STATUS_FAILED) {
626-
tr_debug("send(): Previous packet failed.");
627-
_send_status = SEND_STATUS_OK;
628-
return NSAPI_ERROR_DEVICE_ERROR;
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+
}
629644
}
630645
}
631646

@@ -644,6 +659,10 @@ nsapi_size_or_error_t ESP8266::send(int id, const void *data, uint32_t amount)
644659
}
645660

646661
_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;
647666
set_timeout(ESP8266_SEND_TIMEOUT);
648667
_busy = false;
649668
_error = false;
@@ -685,44 +704,27 @@ nsapi_size_or_error_t ESP8266::send(int id, const void *data, uint32_t amount)
685704
goto END;
686705
}
687706

688-
//We might receive "busy s/p...", "SEND OK" or "SEND FAIL" from modem, so we need to check that also
689-
_parser.oob("SEND FAIL", callback(this, &ESP8266::_oob_send_fail_received));
690-
for (unsigned int i = send_ack_retries; i > 0; i--) {
691-
if (!_parser.recv("SEND OK")) {
692-
if (_error || _send_status == SEND_STATUS_FAILED) {
693-
_parser.remove_oob("SEND FAIL");
694-
goto END;
695-
}
696-
if (_busy) {
697-
_busy = false;
698-
tr_debug("send(): Busy, %d retries left...", i - 1);
699-
}
700-
} else {
701-
ret = amount; // Got "SEND OK" - return number of bytes.
702-
goto END;
703-
}
704-
}
705-
706-
// ESP8266 ACKed data over serial, but did not ACK with SEND OK or report any error.
707-
_send_status = SEND_STATUS_PENDING;
708-
_parser.oob("SEND OK", callback(this, &ESP8266::_oob_send_ok_received));
709707
ret = amount;
710708

711709
END:
712710
_process_oob(ESP8266_RECV_TIMEOUT, true); // Drain USART receive register to avoid data overrun
713711

714712
// error hierarchy, from low to high
715-
if (_busy) {
713+
// NOTE: We cannot return NSAPI_ERROR_WOULD_BLOCK when "Recv X bytes" has reached, otherwise duplicate data send.
714+
if (_busy && ret < 0) {
716715
ret = NSAPI_ERROR_WOULD_BLOCK;
717716
tr_debug("send(): Modem busy.");
718717
}
719718

720719
if (_error) {
720+
// FIXME: Not sure clear or not of _error. See it as device error and it can recover only via reset?
721+
_sock_sending_id = -1;
721722
ret = NSAPI_ERROR_CONNECTION_LOST;
722723
tr_debug("send(): Connection disrupted.");
723724
}
724725

725-
if (_send_status == SEND_STATUS_FAILED) {
726+
if (_sock_i[id].send_fail) {
727+
_sock_sending_id = -1;
726728
if (_sock_i[id].proto == NSAPI_TCP) {
727729
ret = NSAPI_ERROR_DEVICE_ERROR;
728730
} else {
@@ -732,6 +734,7 @@ nsapi_size_or_error_t ESP8266::send(int id, const void *data, uint32_t amount)
732734
}
733735

734736
if (!_sock_i[id].open && ret < 0) {
737+
_sock_sending_id = -1;
735738
ret = NSAPI_ERROR_CONNECTION_LOST;
736739
tr_debug("send(): Socket %d closed abruptly.", id);
737740
}
@@ -1005,14 +1008,6 @@ void ESP8266::_clear_socket_packets(int id)
10051008
_sock_i[id].tcp_data_avbl = 0;
10061009
}
10071010
}
1008-
void ESP8266::_clear_send_status(void)
1009-
{
1010-
_smutex.lock(); // remove_oob doesn't use serial, but we don't want to race against it.
1011-
_send_status = SEND_STATUS_OK;
1012-
_parser.remove_oob("SEND OK");
1013-
_parser.remove_oob("SEND FAIL");
1014-
_smutex.unlock();
1015-
}
10161011

10171012
bool ESP8266::close(int id)
10181013
{
@@ -1025,20 +1020,33 @@ bool ESP8266::close(int id)
10251020
_closed = false;
10261021
_sock_i[id].open = false;
10271022
_clear_socket_packets(id);
1023+
// Closed, so this socket escapes from SEND FAIL status.
1024+
if (id == _sock_sending_id) {
1025+
_sock_sending_id = -1;
1026+
}
1027+
_sock_i[id].send_fail = false;
10281028
_smutex.unlock();
10291029
// ESP8266 has a habit that it might close a socket on its own.
1030+
tr_debug("close(%d): socket close OK with UNLINK ERROR", id);
10301031
return true;
10311032
}
10321033
} else {
10331034
// _sock_i[id].open set to false with an OOB
10341035
_clear_socket_packets(id);
1036+
// Closed, so this socket escapes from SEND FAIL status
1037+
if (id == _sock_sending_id) {
1038+
_sock_sending_id = -1;
1039+
}
1040+
_sock_i[id].send_fail = false;
10351041
_smutex.unlock();
1042+
tr_debug("close(%d): socket close OK with AT+CIPCLOSE OK", id);
10361043
return true;
10371044
}
10381045
}
10391046
_smutex.unlock();
10401047
}
10411048

1049+
tr_debug("close(%d): socket close FAIL'ed (spurious close)", id);
10421050
return false;
10431051
}
10441052

@@ -1215,39 +1223,59 @@ void ESP8266::_oob_socket0_closed()
12151223
{
12161224
static const int id = 0;
12171225
_sock_i[id].open = false;
1218-
_clear_send_status();
1226+
// Closed, so this socket escapes from SEND FAIL status
1227+
if (id == _sock_sending_id) {
1228+
_sock_sending_id = -1;
1229+
}
1230+
_sock_i[id].send_fail = false;
12191231
tr_debug("_oob_socket0_closed(): Socket %d closed.", id);
12201232
}
12211233

12221234
void ESP8266::_oob_socket1_closed()
12231235
{
12241236
static const int id = 1;
12251237
_sock_i[id].open = false;
1226-
_clear_send_status();
1238+
// Closed, so this socket escapes from SEND FAIL status
1239+
if (id == _sock_sending_id) {
1240+
_sock_sending_id = -1;
1241+
}
1242+
_sock_i[id].send_fail = false;
12271243
tr_debug("_oob_socket1_closed(): Socket %d closed.", id);
12281244
}
12291245

12301246
void ESP8266::_oob_socket2_closed()
12311247
{
12321248
static const int id = 2;
12331249
_sock_i[id].open = false;
1234-
_clear_send_status();
1250+
// Closed, so this socket escapes from SEND FAIL status
1251+
if (id == _sock_sending_id) {
1252+
_sock_sending_id = -1;
1253+
}
1254+
_sock_i[id].send_fail = false;
12351255
tr_debug("_oob_socket2_closed(): Socket %d closed.", id);
12361256
}
12371257

12381258
void ESP8266::_oob_socket3_closed()
12391259
{
12401260
static const int id = 3;
12411261
_sock_i[id].open = false;
1242-
_clear_send_status();
1262+
// Closed, so this socket escapes from SEND FAIL status
1263+
if (id == _sock_sending_id) {
1264+
_sock_sending_id = -1;
1265+
}
1266+
_sock_i[id].send_fail = false;
12431267
tr_debug("_oob_socket3_closed(): %d closed.", id);
12441268
}
12451269

12461270
void ESP8266::_oob_socket4_closed()
12471271
{
12481272
static const int id = 4;
12491273
_sock_i[id].open = false;
1250-
_clear_send_status();
1274+
// Closed, so this socket escapes from SEND FAIL status
1275+
if (id == _sock_sending_id) {
1276+
_sock_sending_id = -1;
1277+
}
1278+
_sock_i[id].send_fail = false;
12511279
tr_debug("_oob_socket0_closed(): Socket %d closed.", id);
12521280
}
12531281

@@ -1287,22 +1315,17 @@ void ESP8266::_oob_connection_status()
12871315

12881316
void ESP8266::_oob_send_ok_received()
12891317
{
1290-
tr_debug("_oob_send_ok_received called with _send_status %d", _send_status);
1291-
if (_send_status == SEND_STATUS_PENDING) {
1292-
_send_status = SEND_STATUS_OK;
1293-
}
1294-
_parser.remove_oob("SEND OK");
1295-
_parser.remove_oob("SEND FAIL");
1318+
tr_debug("_oob_send_ok_received called for socket %d", _sock_sending_id);
1319+
_sock_sending_id = -1;
12961320
}
12971321

12981322
void ESP8266::_oob_send_fail_received()
12991323
{
1300-
tr_debug("_oob_send_fail_received called with _send_status %d", _send_status);
1301-
if (_send_status == SEND_STATUS_PENDING) {
1302-
_send_status = SEND_STATUS_FAILED;
1324+
tr_debug("_oob_send_fail_received called for socket %d", _sock_sending_id);
1325+
if (_sock_sending_id >= 0 && _sock_sending_id < SOCKET_COUNT) {
1326+
_sock_i[_sock_sending_id].send_fail = true;
13031327
}
1304-
_parser.remove_oob("SEND FAIL");
1305-
_parser.remove_oob("SEND OK");
1328+
_sock_sending_id = -1;
13061329
}
13071330

13081331
int8_t ESP8266::default_wifi_mode()

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

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -250,17 +250,6 @@ class ESP8266 {
250250
*/
251251
nsapi_error_t open_tcp(int id, const char *addr, int port, int keepalive = 0);
252252

253-
/** ESP8266 send status.
254-
*
255-
* There is one send status per device, because the ACKs are just simple "SEND OK/FAIL"
256-
* with no way to tell which socket is reporting back.
257-
*/
258-
enum send_status {
259-
SEND_STATUS_OK = 0, /*!< device is able to send */
260-
SEND_STATUS_PENDING = 1, /*!< previous send is pending for OK/FAIL */
261-
SEND_STATUS_FAILED = 2 /*!< previous send FAILed */
262-
};
263-
264253
/**
265254
* Sends data to an open socket
266255
*
@@ -455,7 +444,6 @@ class ESP8266 {
455444
// data follows
456445
} *_packets, * *_packets_end;
457446
void _clear_socket_packets(int id);
458-
void _clear_send_status(void);
459447
int _sock_active_id;
460448

461449
// Memory statistics
@@ -493,7 +481,7 @@ class ESP8266 {
493481
bool _error;
494482
bool _busy;
495483
bool _reset_done;
496-
send_status _send_status;
484+
int _sock_sending_id;
497485

498486
// Modem's address info
499487
char _ip_buffer[16];
@@ -508,6 +496,7 @@ class ESP8266 {
508496
char *tcp_data;
509497
int32_t tcp_data_avbl; // Data waiting on modem
510498
int32_t tcp_data_rcvd;
499+
bool send_fail; // Received 'SEND FAIL'. Expect user will close the socket.
511500
};
512501
struct _sock_info _sock_i[SOCKET_COUNT];
513502

0 commit comments

Comments
 (0)