From f5ccbe7972fa4f2553aca73a4715933cb2067593 Mon Sep 17 00:00:00 2001 From: Dominika Maziec Date: Mon, 2 Sep 2019 11:55:46 +0200 Subject: [PATCH] esp8266 nonblocking connect/disconnect `ESP8266Interface::connect()` and `ESP8266Interface::disconnect()` can be used in terms of asynchronous operations. Mainly, it is based on newly added private variable of type `esp_connection_software_status` which is used to keep tracking of state of connection.`wifi_connect_nonblock` test was renamed and amended to test both `connect()` and `disconnect()` operation --- TESTS/network/wifi/README.md | 28 ++- TESTS/network/wifi/main.cpp | 2 +- ...p => wifi_connect_disconnect_nonblock.cpp} | 19 +- TESTS/network/wifi/wifi_tests.h | 4 +- .../wifi/esp8266-driver/ESP8266Interface.cpp | 195 +++++++++++++----- .../wifi/esp8266-driver/ESP8266Interface.h | 19 ++ 6 files changed, 204 insertions(+), 63 deletions(-) rename TESTS/network/wifi/{wifi_connect_nonblock.cpp => wifi_connect_disconnect_nonblock.cpp} (73%) diff --git a/TESTS/network/wifi/README.md b/TESTS/network/wifi/README.md index 2308d44242..fb974d81b1 100644 --- a/TESTS/network/wifi/README.md +++ b/TESTS/network/wifi/README.md @@ -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( , 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 diff --git a/TESTS/network/wifi/main.cpp b/TESTS/network/wifi/main.cpp index 73859eb163..b1b3f66f43 100644 --- a/TESTS/network/wifi/main.cpp +++ b/TESTS/network/wifi/main.cpp @@ -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) diff --git a/TESTS/network/wifi/wifi_connect_nonblock.cpp b/TESTS/network/wifi/wifi_connect_disconnect_nonblock.cpp similarity index 73% rename from TESTS/network/wifi/wifi_connect_nonblock.cpp rename to TESTS/network/wifi/wifi_connect_disconnect_nonblock.cpp index ed36d0e929..f1b68d9685 100644 --- a/TESTS/network/wifi/wifi_connect_nonblock.cpp +++ b/TESTS/network/wifi/wifi_connect_disconnect_nonblock.cpp @@ -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) diff --git a/TESTS/network/wifi/wifi_tests.h b/TESTS/network/wifi/wifi_tests.h index b7f156f722..d2b17025e3 100644 --- a/TESTS/network/wifi/wifi_tests.h +++ b/TESTS/network/wifi/wifi_tests.h @@ -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); diff --git a/components/wifi/esp8266-driver/ESP8266Interface.cpp b/components/wifi/esp8266-driver/ESP8266Interface.cpp index 308de3c979..4711ca36ef 100644 --- a/components/wifi/esp8266-driver/ESP8266Interface.cpp +++ b/components/wifi/esp8266-driver/ESP8266Interface.cpp @@ -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; diff --git a/components/wifi/esp8266-driver/ESP8266Interface.h b/components/wifi/esp8266-driver/ESP8266Interface.h index f924cd59e1..ee1a927377 100644 --- a/components/wifi/esp8266-driver/ESP8266Interface.h +++ b/components/wifi/esp8266-driver/ESP8266Interface.h @@ -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