Merge pull request #11409 from dmaziec1/esp8266-nonblocking

esp8266 nonblocking connect/disconnect
pull/11460/head
Martin Kojtal 2019-09-13 16:12:13 +02:00 committed by GitHub
commit ac7b851ba0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 204 additions and 63 deletions

View File

@ -62,7 +62,7 @@ Please refer to the following table for priorities of test cases. Priorities are
| 9 | WIFI_CONNECT_PARAMS_CHANNEL | | SHOULD |
| 10 | WIFI_CONNECT_PARAMS_CHANNEL_FAIL | | SHOULD |
| 11 | WIFI_CONNECT | | MUST |
| 12 | WIFI_CONNECT_NONBLOCK | | SHOULD |
| 12 | WIFI_CONNECT_DISCONNECT_NONBLOCK | | SHOULD |
| 13 | WIFI_CONNECT_SECURE | With security type: | |
| | | NSAPI_SECURITY_WEP | SHOULD |
| | | NSAPI_SECURITY_WPA | SHOULD |
@ -385,7 +385,7 @@ Test `WiFiInterface::connect()` without parameters. Use `set_credentials()` for
`connect()` calls return `NSAPI_ERROR_OK`.
### WIFI_CONNECT_NONBLOCK
### WIFI_CONNECT_DISCONNECT_NONBLOCK
**Description:**
@ -399,18 +399,24 @@ Test `WiFiInterface::connect()` and `WiFiInterface::disconnect()` in non-blockin
1. Initialize the driver.
2. `Call WiFiInterface::set_credentials( <ssid:unsecure>, NULL)`.
3. `Call WiFiInterface::connect()`.
4. `Call WiFiInterface::set_blocking(false)`
5. `Call WiFiInterface::get_connection_status()`
6. `disconnect()`
7. `Call WiFiInterface::get_connection_status()`
8. `Call WiFiInterface::set_blocking(true)`
3. `Call WiFiInterface::set_blocking(false)`
4. `Call WiFiInterface::connect()`.
5. `Cal WiFiInterface::set_credentials(const char *ssid, const char *pass, nsapi_security_t security)`
6. `Call WiFiInterface::connect()`.
7. `disconnect()`
8. `disconnect()`
9. `Call WiFiInterface::set_blocking(true)`
**Expected result:**
In case of drivers which do not support asynchronous mode `set_blocking(false)` call returns `NSAPI_ERROR_UNSUPPORTED` and skips test case, otherwise:
`connect()` call returns `NSAPI_ERROR_OK`. To confirm connection `get_connection_status()` calls return `NSAPI_STATUS_GLOBAL_UP` or `NSAPI_STATUS_LOCAL_UP`.
`disconnect()` call returns `NSAPI_ERROR_OK`. To confirm disconnection `get_connection_status()` calls return `NSAPI_STATUS_DISCONNECTED`.
1. Drivers which do not support asynchronous mode `set_blocking(false)` call returns `NSAPI_ERROR_UNSUPPORTED` and skips test case.
2. `connect()` call returns `NSAPI_ERROR_OK`.
3. `set_credentials(...)` call returns `NSAPI_ERROR_BUSY`.
4. Second `connect()` call returns `NSAPI_ERROR_BUSY` or `NSAPI_ERROR_IS_CONNECTED`.
5. Attached callback informs about connection status. Callback reports status `NSAPI_STATUS_CONNECTING` and `NSAPI_STATUS_CONNECTED`.
6. `disconnect()` call returns `NSAPI_ERROR_OK`.
7. Second `disconnect()` call returns `NSAPI_ERROR_BUSY` or `NSAPI_ERROR_IS_CONNECTED`.
8. To confirm disconnection callback reports `NSAPI_STATUS_DISCONNECTED`.
### WIFI_CONNECT_SECURE

View File

@ -75,7 +75,7 @@ Case cases[] = {
Case("WIFI-CONNECT-PARAMS-VALID-UNSECURE", wifi_connect_params_valid_unsecure),
Case("WIFI-CONNECT", wifi_connect),
//Most boards are not passing this test, but they should if they support non-blocking API.
//Case("WIFI-CONNECT-NONBLOCKING", wifi_connect_nonblock),
//Case("WIFI_CONNECT_DISCONNECT_NONBLOCK", wifi_connect_disconnect_nonblock),
Case("WIFI-CONNECT-DISCONNECT-REPEAT", wifi_connect_disconnect_repeat),
#endif
#if defined(MBED_CONF_APP_WIFI_SECURE_SSID)

View File

@ -29,6 +29,7 @@ using namespace utest::v1;
nsapi_connection_status_t status_connection;
Semaphore sem_conn(0, 1);
Semaphore sem_disconn(0, 1);
Semaphore sem_connecting(0, 1);
void status_callback(nsapi_event_t e, intptr_t d)
{
if (d == NSAPI_STATUS_LOCAL_UP || d == NSAPI_STATUS_GLOBAL_UP) {
@ -40,9 +41,14 @@ void status_callback(nsapi_event_t e, intptr_t d)
status_connection = (nsapi_connection_status_t)d;
sem_disconn.release();
}
if (d == NSAPI_STATUS_CONNECTING) {
status_connection = (nsapi_connection_status_t)d;
sem_connecting.release();
}
}
void wifi_connect_nonblock(void)
void wifi_connect_disconnect_nonblock(void)
{
WiFiInterface *wifi = get_interface();
char ssid[SSID_MAX_LEN + 1] = MBED_CONF_APP_WIFI_UNSECURE_SSID;
@ -50,16 +56,25 @@ void wifi_connect_nonblock(void)
wifi->attach(status_callback);
TEST_SKIP_UNLESS(wifi->set_blocking(false) != NSAPI_ERROR_UNSUPPORTED);
nsapi_error_t ret = wifi->connect();
nsapi_error_t ret2 = wifi->set_credentials("1234", "1234", NSAPI_SECURITY_WPA_WPA2);
nsapi_error_t ret3 = wifi->connect();
TEST_ASSERT_EQUAL_INT(NSAPI_ERROR_OK, ret);
bool res = sem_conn.try_acquire_for(30000);
TEST_ASSERT_EQUAL_INT(NSAPI_ERROR_BUSY, ret2);
TEST_ASSERT_TRUE(ret3 == NSAPI_ERROR_BUSY || ret3 == NSAPI_ERROR_IS_CONNECTED);
bool res = sem_connecting.try_acquire_for(30000);
TEST_ASSERT_EQUAL_INT(NSAPI_STATUS_CONNECTING, status_connection);
res = sem_conn.try_acquire_for(30000);
TEST_ASSERT_TRUE(res == true);
TEST_ASSERT_TRUE(status_connection == NSAPI_STATUS_GLOBAL_UP || status_connection == NSAPI_STATUS_LOCAL_UP);
ret = wifi->disconnect();
ret3 = wifi->disconnect();
TEST_ASSERT_EQUAL_INT(NSAPI_ERROR_OK, ret);
TEST_ASSERT_TRUE(ret3 == NSAPI_ERROR_BUSY || ret3 == NSAPI_ERROR_NO_CONNECTION);
res = sem_disconn.try_acquire_for(30000);
TEST_ASSERT_TRUE(res == true);
TEST_ASSERT_EQUAL_INT(NSAPI_STATUS_DISCONNECTED, status_connection);
wifi->set_blocking(true);
wifi->attach(0);
}
#endif // defined(MBED_CONF_APP_WIFI_UNSECURE_SSID)

View File

@ -63,8 +63,8 @@ void wifi_connect_params_channel_fail(void);
/** Test WiFiInterface::connect() without parameters. Use set_credentials() for setting parameters. */
void wifi_connect(void);
/** Test WiFiInterface::connect() in nonblocking mode. Use set_credentials() for setting parameters. */
void wifi_connect_nonblock(void);
/** Test WiFiInterface::connect() and disconnect() in nonblocking mode. Use set_credentials() for setting parameters. */
void wifi_connect_disconnect_nonblock(void);
/** Test WiFiInterface::connect() without parameters. Don't set parameters with set_credentials() */
void wifi_connect_nocredentials(void);

View File

@ -69,7 +69,8 @@ ESP8266Interface::ESP8266Interface()
_conn_stat_cb(NULL),
_global_event_queue(mbed_event_queue()), // Needs to be set before attaching event() to SIGIO
_oob_event_id(0),
_connect_event_id(0)
_connect_event_id(0),
_software_conn_stat(IFACE_STATUS_DISCONNECTED)
{
memset(_cbs, 0, sizeof(_cbs));
memset(ap_ssid, 0, sizeof(ap_ssid));
@ -192,6 +193,12 @@ void ESP8266Interface::PowerPin::power_off()
}
}
void ESP8266Interface::_power_off()
{
_rst_pin.rst_assert();
_pwr_pin.power_off();
}
bool ESP8266Interface::PowerPin::is_connected()
{
return _pwr_pin.is_connected();
@ -214,6 +221,20 @@ int ESP8266Interface::connect(const char *ssid, const char *pass, nsapi_security
void ESP8266Interface::_connect_async()
{
nsapi_error_t status = _init();
if (status != NSAPI_ERROR_OK) {
_connect_retval = status;
_software_conn_stat = IFACE_STATUS_DISCONNECTED;
//_conn_stat_cb will be called from refresh_conn_state_cb
return;
}
if (!_esp.dhcp(true, 1)) {
_connect_retval = NSAPI_ERROR_DHCP_FAILURE;
_software_conn_stat = IFACE_STATUS_DISCONNECTED;
//_conn_stat_cb will be called from refresh_conn_state_cb
return;
}
_cmutex.lock();
if (!_connect_event_id) {
tr_debug("_connect_async(): cancelled");
@ -222,14 +243,18 @@ void ESP8266Interface::_connect_async()
}
_connect_retval = _esp.connect(ap_ssid, ap_pass);
int timeleft_ms = ESP8266_INTERFACE_CONNECT_TIMEOUT_MS - _conn_timer.read_ms();
if (_connect_retval == NSAPI_ERROR_OK || _connect_retval == NSAPI_ERROR_AUTH_FAILURE
if (_connect_retval == NSAPI_ERROR_OK
|| _connect_retval == NSAPI_ERROR_AUTH_FAILURE
|| _connect_retval == NSAPI_ERROR_NO_SSID
|| ((_if_blocking == true) && (timeleft_ms <= 0))) {
_connect_event_id = 0;
_conn_timer.stop();
if (timeleft_ms <= 0) {
if (timeleft_ms <= 0 && _connect_retval != NSAPI_ERROR_OK) {
_connect_retval = NSAPI_ERROR_CONNECTION_TIMEOUT;
}
if (_connect_retval != NSAPI_ERROR_OK) {
_software_conn_stat = IFACE_STATUS_DISCONNECTED;
}
_if_connected.notify_all();
} else {
// Postpone to give other stuff time to run
@ -241,13 +266,24 @@ void ESP8266Interface::_connect_async()
}
}
_cmutex.unlock();
if (_connect_event_id == 0) {
if (_conn_stat_cb) {
_conn_stat_cb(NSAPI_EVENT_CONNECTION_STATUS_CHANGE, _conn_stat);
}
if (_conn_stat == NSAPI_STATUS_GLOBAL_UP || _conn_stat == NSAPI_STATUS_LOCAL_UP) {
_software_conn_stat = IFACE_STATUS_CONNECTED;
}
}
}
int ESP8266Interface::connect()
{
nsapi_error_t status = _conn_status_to_error();
if (status != NSAPI_ERROR_NO_CONNECTION) {
return status;
if (_software_conn_stat == IFACE_STATUS_CONNECTING) {
return NSAPI_ERROR_BUSY;
}
if (_software_conn_stat == IFACE_STATUS_CONNECTED) {
return NSAPI_ERROR_IS_CONNECTED;
}
if (strlen(ap_ssid) == 0) {
@ -259,22 +295,15 @@ int ESP8266Interface::connect()
return NSAPI_ERROR_PARAMETER;
}
}
status = _init();
if (status != NSAPI_ERROR_OK) {
return status;
if (!_if_blocking) {
bool ret = _cmutex.trylock();
if (ret == false) {
return NSAPI_ERROR_BUSY;
}
} else {
_cmutex.lock();
}
if (get_ip_address()) {
return NSAPI_ERROR_IS_CONNECTED;
}
if (!_esp.dhcp(true, 1)) {
return NSAPI_ERROR_DHCP_FAILURE;
}
_cmutex.lock();
_software_conn_stat = IFACE_STATUS_CONNECTING;
_connect_retval = NSAPI_ERROR_NO_CONNECTION;
MBED_ASSERT(!_connect_event_id);
_conn_timer.stop();
@ -304,6 +333,9 @@ int ESP8266Interface::connect()
int ESP8266Interface::set_credentials(const char *ssid, const char *pass, nsapi_security_t security)
{
nsapi_error_t status = _conn_status_to_error();
if (_software_conn_stat == IFACE_STATUS_CONNECTING) {
return NSAPI_ERROR_BUSY;
}
if (status != NSAPI_ERROR_NO_CONNECTION) {
return status;
}
@ -351,41 +383,100 @@ int ESP8266Interface::set_channel(uint8_t channel)
}
int ESP8266Interface::disconnect()
void ESP8266Interface::_disconnect_async()
{
_cmutex.lock();
_disconnect_retval = _esp.disconnect() ? NSAPI_ERROR_OK : NSAPI_ERROR_DEVICE_ERROR;
int timeleft_ms = ESP8266_INTERFACE_CONNECT_TIMEOUT_MS - _conn_timer.read_ms();
if (_disconnect_retval == NSAPI_ERROR_OK || ((_if_blocking == true) && (timeleft_ms <= 0))) {
if (timeleft_ms <= 0 && _connect_retval != NSAPI_ERROR_OK) {
_disconnect_retval = NSAPI_ERROR_CONNECTION_TIMEOUT;
} else {
if (_conn_stat != NSAPI_STATUS_DISCONNECTED) {
_conn_stat = NSAPI_STATUS_DISCONNECTED;
}
// In case the status update arrives later inform upper layers manually
_disconnect_event_id = 0;
_conn_timer.stop();
_connect_retval = NSAPI_ERROR_NO_CONNECTION;
}
_power_off();
_if_connected.notify_all();
} else {
// Postpone to give other stuff time to run
_disconnect_event_id = _global_event_queue->call_in(
ESP8266_INTERFACE_CONNECT_INTERVAL_MS,
callback(this, &ESP8266Interface::_disconnect_async));
if (!_disconnect_event_id) {
MBED_ERROR(
MBED_MAKE_ERROR(MBED_MODULE_DRIVER, MBED_ERROR_CODE_ENOMEM), \
"ESP8266Interface::_disconnect_async(): unable to add event to queue. Increase \"events.shared-eventsize\"\n");
}
}
_cmutex.unlock();
_software_conn_stat = IFACE_STATUS_DISCONNECTED;
if (_disconnect_event_id == 0) {
if (_conn_stat_cb) {
_conn_stat_cb(NSAPI_EVENT_CONNECTION_STATUS_CHANGE, _conn_stat);
}
}
}
int ESP8266Interface::disconnect()
{
if (_software_conn_stat == IFACE_STATUS_DISCONNECTING) {
return NSAPI_ERROR_BUSY;
}
if (_software_conn_stat == IFACE_STATUS_DISCONNECTED) {
return NSAPI_ERROR_NO_CONNECTION;
}
if (!_if_blocking) {
bool ret = _cmutex.trylock();
if (ret == false) {
return NSAPI_ERROR_BUSY;
}
} else {
_cmutex.lock();
}
if (_connect_event_id) {
_global_event_queue->cancel(_connect_event_id);
_connect_event_id = 0; // cancel asynchronous connection attempt if one is ongoing
}
_cmutex.unlock();
_software_conn_stat = IFACE_STATUS_DISCONNECTING;
_disconnect_retval = NSAPI_ERROR_IS_CONNECTED;
_disconnect_event_id = 0;
_initialized = false;
_conn_timer.stop();
_conn_timer.reset();
_conn_timer.start();
nsapi_error_t status = _conn_status_to_error();
if (status == NSAPI_ERROR_NO_CONNECTION) {
return NSAPI_ERROR_NO_CONNECTION;
_disconnect_event_id = _global_event_queue->call(
callback(this, &ESP8266Interface::_disconnect_async));
if (!_disconnect_event_id) {
MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER, MBED_ERROR_CODE_ENOMEM),
"disconnect(): unable to add event to queue. Increase \"events.shared-eventsize\"\n");
}
int ret = _esp.disconnect() ? NSAPI_ERROR_OK : NSAPI_ERROR_DEVICE_ERROR;
if (ret == NSAPI_ERROR_OK) {
// Try to lure the nw status update from ESP8266, might come later
_esp.bg_process_oob(ESP8266_RECV_TIMEOUT, true);
// In case the status update arrives later inform upper layers manually
if (_conn_stat != NSAPI_STATUS_DISCONNECTED) {
_conn_stat = NSAPI_STATUS_DISCONNECTED;
if (_conn_stat_cb) {
_conn_stat_cb(NSAPI_EVENT_CONNECTION_STATUS_CHANGE, _conn_stat);
}
}
while (_if_blocking
&& (_conn_status_to_error() != NSAPI_ERROR_NO_CONNECTION)
&& (_disconnect_retval != NSAPI_ERROR_OK)) {
_if_connected.wait();
}
// Power down the modem
_rst_pin.rst_assert();
// Power off the modem
_pwr_pin.power_off();
return ret;
_cmutex.unlock();
if (!_if_blocking) {
return NSAPI_ERROR_OK;
} else {
return _disconnect_retval;
}
}
const char *ESP8266Interface::get_ip_address()
@ -866,9 +957,15 @@ void ESP8266Interface::refresh_conn_state_cb()
// Doesn't require changes
case NSAPI_STATUS_CONNECTING:
case NSAPI_STATUS_GLOBAL_UP:
if (_software_conn_stat == IFACE_STATUS_DISCONNECTED) {
_software_conn_stat = IFACE_STATUS_CONNECTED;
}
break;
// Start from scratch if connection drops/is dropped
case NSAPI_STATUS_DISCONNECTED:
if (_software_conn_stat == IFACE_STATUS_CONNECTED) {
_software_conn_stat = IFACE_STATUS_DISCONNECTED;
}
break;
// Handled on AT layer
case NSAPI_STATUS_LOCAL_UP:
@ -888,8 +985,14 @@ void ESP8266Interface::refresh_conn_state_cb()
tr_debug("refresh_conn_state_cb(): changed to %d", _conn_stat);
// Inform upper layers
if (_conn_stat_cb) {
// _conn_stat_cb will be called in _connect_async or disconnect_assync to avoid race condition
if ((_software_conn_stat == IFACE_STATUS_CONNECTING
|| _software_conn_stat == IFACE_STATUS_DISCONNECTING)
&& (_conn_stat != NSAPI_STATUS_CONNECTING)) {
return;
}
_conn_stat_cb(NSAPI_EVENT_CONNECTION_STATUS_CHANGE, _conn_stat);
}
}
@ -904,8 +1007,6 @@ nsapi_error_t ESP8266Interface::_conn_status_to_error()
{
nsapi_error_t ret;
_esp.bg_process_oob(ESP8266_RECV_TIMEOUT, true);
switch (_conn_stat) {
case NSAPI_STATUS_DISCONNECTED:
ret = NSAPI_ERROR_NO_CONNECTION;

View File

@ -381,6 +381,15 @@ private:
ESP8266 _esp;
void refresh_conn_state_cb();
/** Status of software connection
*/
typedef enum esp_connection_software_status {
IFACE_STATUS_DISCONNECTED = 0,
IFACE_STATUS_CONNECTING = 1,
IFACE_STATUS_CONNECTED = 2,
IFACE_STATUS_DISCONNECTING = 3
} esp_connection_software_status_t;
// HW reset pin
class ResetPin {
public:
@ -403,6 +412,12 @@ private:
mbed::DigitalOut _pwr_pin;
} _pwr_pin;
/** Assert the reset and power pins
* ESP8266 has two pins serving similar purpose and this function asserts them both
* if they are configured in mbed_app.json.
*/
void _power_off();
// Credentials
static const int ESP8266_SSID_MAX_LENGTH = 32; /* 32 is what 802.11 defines as longest possible name */
char ap_ssid[ESP8266_SSID_MAX_LENGTH + 1]; /* The longest possible name; +1 for the \0 */
@ -437,6 +452,7 @@ private:
// Driver's state
int _initialized;
nsapi_error_t _connect_retval;
nsapi_error_t _disconnect_retval;
bool _get_firmware_ok();
nsapi_error_t _init(void);
nsapi_error_t _reset();
@ -459,9 +475,12 @@ private:
events::EventQueue *_global_event_queue;
int _oob_event_id;
int _connect_event_id;
int _disconnect_event_id;
void proc_oob_evnt();
void _connect_async();
void _disconnect_async();
rtos::Mutex _cmutex; // Protect asynchronous connection logic
esp_connection_software_status_t _software_conn_stat ;
};
#endif