wifi test
Dependencies: X_NUCLEO_IKS01A2 mbed-http
Diff: easy-connect/wifi-x-nucleo-idw01m1/SPWFSAxx.cpp
- Revision:
- 0:24d3eb812fd4
diff -r 000000000000 -r 24d3eb812fd4 easy-connect/wifi-x-nucleo-idw01m1/SPWFSAxx.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/easy-connect/wifi-x-nucleo-idw01m1/SPWFSAxx.cpp Wed Sep 05 14:28:24 2018 +0000 @@ -0,0 +1,1287 @@ +/* SPWFSAxx Devices + * Copyright (c) 2015 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mbed_debug.h" + +#include "SpwfSAInterface.h" /* must be included first */ +#include "SPWFSAxx.h" + +static const char out_delim[] = {SPWFSAxx::_cr_, '\0'}; + +SPWFSAxx::SPWFSAxx(PinName tx, PinName rx, + PinName rts, PinName cts, + SpwfSAInterface &ifce, bool debug, + PinName wakeup, PinName reset) +: _serial(tx, rx, SPWFXX_DEFAULT_BAUD_RATE), _parser(&_serial, out_delim), + _wakeup(wakeup, 1), _reset(reset, 1), + _rts(rts), _cts(cts), + _timeout(SPWF_INIT_TIMEOUT), _dbg_on(debug), + _pending_sockets_bitmap(0), + _network_lost_flag(false), + _associated_interface(ifce), + _call_event_callback_blocked(0), + _callback_func(), + _packets(0), _packets_end(&_packets) +{ + memset(_pending_pkt_sizes, 0, sizeof(_pending_pkt_sizes)); + + _serial.sigio(Callback<void()>(this, &SPWFSAxx::_event_handler)); + _parser.debug_on(debug); + _parser.set_timeout(_timeout); + + /* unlikely OOBs */ + _parser.oob("+WIND:5:WiFi Hardware Failure", callback(this, &SPWFSAxx::_wifi_hwfault_handler)); + _parser.oob("+WIND:33:WiFi Network Lost", callback(this, &SPWFSAxx::_network_lost_handler_th)); +#if MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1 + _parser.oob("+WIND:24:WiFi Up::", callback(this, &SPWFSAxx::_skip_oob)); +#endif + _parser.oob("+WIND:8:Hard Fault", callback(this, &SPWFSAxx::_hard_fault_handler)); + + /* most likely OOBs */ + _parser.oob(SPWFXX_OOB_ERROR, callback(this, &SPWFSAxx::_error_handler)); + _parser.oob("+WIND:58:Socket Closed", callback(this, &SPWFSAxx::_server_gone_handler)); + _parser.oob("+WIND:55:Pending Data", callback(this, &SPWFSAxx::_packet_handler_th)); +} + +bool SPWFSAxx::startup(int mode) +{ + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + + /*Reset module*/ + if(!hw_reset()) { + debug_if(_dbg_on, "\r\nSPWF> HW reset failed\r\n"); + return false; + } + + /* factory reset */ + if(!(_parser.send(SPWFXX_SEND_FWCFG) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error restore factory default settings\r\n"); + return false; + } + + /*switch off led*/ + if(!(_parser.send("AT+S.SCFG=blink_led,0") && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error stop blinking led (%d)\r\n", __LINE__); + return false; + } + + /*set local echo to 0*/ + if(!(_parser.send(SPWFXX_SEND_DISABLE_LE) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error local echo set\r\n"); + return false; + } + + /*set the operational rates*/ + if(!(_parser.send("AT+S.SCFG=wifi_opr_rate_mask,0x003FFFCF") && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error setting ht_mode\r\n"); + return false; + } + + /*enable the 802.11n mode*/ + if(!(_parser.send("AT+S.SCFG=wifi_ht_mode,1") && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error setting operational rates\r\n"); + return false; + } + + /*set idle mode (0->idle, 1->STA,3->miniAP, 2->IBSS)*/ + if(!(_parser.send("AT+S.SCFG=wifi_mode,%d", mode) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error WiFi mode set idle (%d)\r\n", __LINE__); + return false; + } + +#if defined(MBED_MAJOR_VERSION) +#if !DEVICE_SERIAL_FC || (MBED_VERSION < MBED_ENCODE_VERSION(5, 7, 0)) + /*disable HW flow control*/ + if(!(_parser.send(SPWFXX_SEND_DISABLE_FC) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error disabling HW flow control\r\n"); + return false; + } +#else // DEVICE_SERIAL_FC && (MBED_VERSION >= MBED_ENCODE_VERSION(5, 7, 0)) + if((_rts != NC) && (_cts != NC)) { + /*enable HW flow control*/ + if(!(_parser.send(SPWFXX_SEND_ENABLE_FC) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error enabling HW flow control\r\n"); + return false; + } + + /*configure pins for HW flow control*/ + _serial.set_flow_control(SerialBase::RTSCTS, _rts, _cts); + } else { + /*disable HW flow control*/ + if(!(_parser.send(SPWFXX_SEND_DISABLE_FC) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error disabling HW flow control\r\n"); + return false; + } + } +#endif // DEVICE_SERIAL_FC && (MBED_VERSION >= MBED_ENCODE_VERSION(5, 7, 0)) +#else // !defined(MBED_MAJOR_VERSION) - Assuming `master` branch +#if !DEVICE_SERIAL_FC + /*disable HW flow control*/ + if(!(_parser.send(SPWFXX_SEND_DISABLE_FC) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error disabling HW flow control\r\n"); + return false; + } +#else // DEVICE_SERIAL_FC + if((_rts != NC) && (_cts != NC)) { + /*enable HW flow control*/ + if(!(_parser.send(SPWFXX_SEND_ENABLE_FC) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error enabling HW flow control\r\n"); + return false; + } + + /*configure pins for HW flow control*/ + _serial.set_flow_control(SerialBase::RTSCTS, _rts, _cts); + } else { + /*disable HW flow control*/ + if(!(_parser.send(SPWFXX_SEND_DISABLE_FC) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error disabling HW flow control\r\n"); + return false; + } + } +#endif // DEVICE_SERIAL_FC +#endif // !defined(MBED_MAJOR_VERSION) + + /* Disable selected WINDs */ + _winds_on(); + + /* sw reset */ + if(!reset()) { + debug_if(_dbg_on, "\r\nSPWF> SW reset failed (%s, %d)\r\n", __func__, __LINE__); + return false; + } + +#ifndef NDEBUG + if (!(_parser.send(SPWFXX_SEND_GET_CONS_STATE) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> error getting console state\r\n"); + return false; + } + + if (!(_parser.send(SPWFXX_SEND_GET_CONS_SPEED) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> error getting console speed\r\n"); + return false; + } + + if (!(_parser.send(SPWFXX_SEND_GET_HWFC_STATE) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> error getting hwfc state\r\n"); + return false; + } + +#if (MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1) || defined(IDW01M1_FW_REL_35X) + /* betzw: IDW01M1 FW versions <3.5 seem to have problems with the following two commands. + * For the sake of simplicity, just excluding them for IDW01M1 in general. + */ + if (!(_parser.send(SPWFXX_SEND_GET_CONS_DELIM) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> error getting console delimiter\r\n"); + return false; + } + + if (!(_parser.send(SPWFXX_SEND_GET_CONS_ERRS) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> error getting console error setting\r\n"); + return false; + } +#endif // (MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1) || defined(IDW01M1_FW_REL_35X) + + if (!(_parser.send("AT+S.GCFG=sleep_enabled") + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> error getting sleep state enabled\r\n"); + return false; + } + + if (!(_parser.send("AT+S.GCFG=wifi_powersave") + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> error getting powersave mode\r\n"); + return false; + } + + if (!(_parser.send("AT+S.GCFG=standby_enabled") + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> error getting standby state enabled\r\n"); + return false; + } +#endif + + return true; +} + +bool SPWFSAxx::_wait_console_active(void) { + int trials = 0; + + while(true) { + if (_parser.recv("+WIND:0:Console active\n") && _recv_delim_lf()) { + debug_if(_dbg_on, "AT^ +WIND:0:Console active\r\n"); + return true; + } + if(++trials >= SPWFXX_MAX_TRIALS) { + debug("\r\nSPWF> ERROR: Should never happen! (%s, %d)\r\n", __func__, __LINE__); + empty_rx_buffer(); + return false; + } + } +} + +bool SPWFSAxx::_wait_wifi_hw_started(void) { + int trials = 0; + + while(true) { + if (_parser.recv("+WIND:32:WiFi Hardware Started\n") && _recv_delim_lf()) { + debug_if(_dbg_on, "AT^ +WIND:32:WiFi Hardware Started\r\n"); + return true; + } + if(++trials >= SPWFXX_MAX_TRIALS) { + debug("\r\nSPWF> ERROR: Should never happen! (%s, %d)\r\n", __func__, __LINE__); + empty_rx_buffer(); + return false; + } + } +} + +bool SPWFSAxx::hw_reset(void) +{ +#if (MBED_CONF_IDW0XX1_EXPANSION_BOARD != IDW04A1) || !defined(IDW04A1_WIFI_HW_BUG_WA) // betzw: HW reset doesn't work as expected on unmodified X_NUCLEO_IDW04A1 expansion boards + _reset.write(0); + wait_ms(200); + _reset.write(1); +#else // (MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1) && defined(IDW04A1_WIFI_HW_BUG_WA): substitute with SW reset + _parser.send(SPWFXX_SEND_SW_RESET); +#endif // (MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1) && defined(IDW04A1_WIFI_HW_BUG_WA) + return _wait_console_active(); +} + +bool SPWFSAxx::reset(void) +{ + bool ret; + + /* save current setting in flash */ + if(!(_parser.send(SPWFXX_SEND_SAVE_SETTINGS) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error saving configuration to flash (%s, %d)\r\n", __func__, __LINE__); + return false; + } + + if(!_parser.send(SPWFXX_SEND_SW_RESET)) return false; /* betzw - NOTE: "keep the current state and reset the device". + We assume that the module informs us about the + eventual closing of sockets via "WIND" asynchronous + indications! So everything regarding the clean-up + of these situations is handled there. */ + + /* waiting for HW to start */ + ret = _wait_wifi_hw_started(); + + return ret; +} + +/* Security Mode + None = 0, + WEP = 1, + WPA_Personal = 2, + */ +bool SPWFSAxx::connect(const char *ap, const char *passPhrase, int securityMode) +{ + int trials; + + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + + //AT+S.SCFG=wifi_wpa_psk_text,%s + if(!(_parser.send("AT+S.SCFG=wifi_wpa_psk_text,%s", passPhrase) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error pass set\r\n"); + return false; + } + + //AT+S.SSIDTXT=%s + if(!(_parser.send("AT+S.SSIDTXT=%s", ap) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error ssid set\r\n"); + return false; + } + + //AT+S.SCFG=wifi_priv_mode,%d + if(!(_parser.send("AT+S.SCFG=wifi_priv_mode,%d", securityMode) && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error security mode set\r\n"); + return false; + } + + /*set STA mode (0->idle, 1->STA,3->miniAP, 2->IBSS)*/ + if(!(_parser.send("AT+S.SCFG=wifi_mode,1") && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error WiFi mode set 1 (STA)\r\n"); + return false; + } + + /* sw reset */ + if(!reset()) { + debug_if(_dbg_on, "\r\nSPWF> SW reset failed (%s, %d)\r\n", __func__, __LINE__); + return false; + } + + trials = 0; + while(true) { + if(_parser.recv("%255[^\n]\n", _msg_buffer) && _recv_delim_lf()) + { + if(strstr(_msg_buffer, ":24:") != NULL) { // WiFi Up + debug_if(_dbg_on, "AT^ %s\n", _msg_buffer); + if(strchr(_msg_buffer, '.') != NULL) { // IPv4 address + break; + } else { + continue; + } + } + if(strstr(_msg_buffer, ":40:") != NULL) { // Deauthentication + debug_if(_dbg_on, "AT~ %s\n", _msg_buffer); + if(++trials < SPWFXX_MAX_TRIALS) { // give it three trials + continue; + } + disconnect(); + empty_rx_buffer(); + return false; + } else { + debug_if(_dbg_on, "AT] %s\n", _msg_buffer); + } + continue; + } + if(++trials >= SPWFXX_MAX_TRIALS) { + debug("\r\nSPWF> ERROR: Should never happen! (%s, %d)\r\n", __func__, __LINE__); + empty_rx_buffer(); + return false; + } + } + + return true; +} + +bool SPWFSAxx::disconnect(void) +{ + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + +#if MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1 + /*disable Wi-Fi device*/ + if(!(_parser.send("AT+S.WIFI=0") && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error disabling WiFi\r\n"); + return false; + } +#endif // IDW04A1 + + /*set idle mode (0->idle, 1->STA,3->miniAP, 2->IBSS)*/ + if(!(_parser.send("AT+S.SCFG=wifi_mode,0") && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error WiFi mode set idle (%d)\r\n", __LINE__); + return false; + } + +#if MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1 + /*enable Wi-Fi device*/ + if(!(_parser.send("AT+S.WIFI=1") && _recv_ok())) + { + debug_if(_dbg_on, "\r\nSPWF> error enabling WiFi\r\n"); + return false; + } +#endif // IDW04A1 + + // reset module + if(!reset()) { + debug_if(_dbg_on, "\r\nSPWF> SW reset failed (%s, %d)\r\n", __func__, __LINE__); + return false; + } + + /* clean up state */ + _associated_interface.inner_constructor(); + _free_all_packets(); + + return true; +} + +const char *SPWFSAxx::getIPAddress(void) +{ + unsigned int n1, n2, n3, n4; + + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + + if (!(_parser.send("AT+S.STS=ip_ipaddr") + && _parser.recv(SPWFXX_RECV_IP_ADDR, &n1, &n2, &n3, &n4) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> get IP address error\r\n"); + return NULL; + } + + debug_if(_dbg_on, "AT^ ip_ipaddr = %u.%u.%u.%u\r\n", n1, n2, n3, n4); + + sprintf((char*)_ip_buffer,"%u.%u.%u.%u", n1, n2, n3, n4); + return _ip_buffer; +} + +const char *SPWFSAxx::getGateway(void) +{ + unsigned int n1, n2, n3, n4; + + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + + if (!(_parser.send("AT+S.STS=ip_gw") + && _parser.recv(SPWFXX_RECV_GATEWAY, &n1, &n2, &n3, &n4) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> get gateway error\r\n"); + return NULL; + } + + debug_if(_dbg_on, "AT^ ip_gw = %u.%u.%u.%u\r\n", n1, n2, n3, n4); + + sprintf((char*)_gateway_buffer,"%u.%u.%u.%u", n1, n2, n3, n4); + return _gateway_buffer; +} + +const char *SPWFSAxx::getNetmask(void) +{ + unsigned int n1, n2, n3, n4; + + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + + if (!(_parser.send("AT+S.STS=ip_netmask") + && _parser.recv(SPWFXX_RECV_NETMASK, &n1, &n2, &n3, &n4) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> get netmask error\r\n"); + return NULL; + } + + debug_if(_dbg_on, "AT^ ip_netmask = %u.%u.%u.%u\r\n", n1, n2, n3, n4); + + sprintf((char*)_netmask_buffer,"%u.%u.%u.%u", n1, n2, n3, n4); + return _netmask_buffer; +} + +int8_t SPWFSAxx::getRssi(void) +{ + int ret; + + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + + if (!(_parser.send("AT+S.PEERS=0,rx_rssi") + && _parser.recv(SPWFXX_RECV_RX_RSSI, &ret) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> get RX rssi error\r\n"); + return 0; + } + + return (int8_t)ret; +} + +const char *SPWFSAxx::getMACAddress(void) +{ + unsigned int n1, n2, n3, n4, n5, n6; + + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + + if (!(_parser.send("AT+S.GCFG=nv_wifi_macaddr") + && _parser.recv(SPWFXX_RECV_MAC_ADDR, &n1, &n2, &n3, &n4, &n5, &n6) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> get MAC address error\r\n"); + return 0; + } + + debug_if(_dbg_on, "AT^ nv_wifi_macaddr = %x:%x:%x:%x:%x:%x\r\n", n1, n2, n3, n4, n5, n6); + + sprintf((char*)_mac_buffer,"%02X:%02X:%02X:%02X:%02X:%02X", n1, n2, n3, n4, n5, n6); + return _mac_buffer; +} + +bool SPWFSAxx::isConnected(void) +{ + return _associated_interface._connected_to_network; +} + +nsapi_size_or_error_t SPWFSAxx::send(int spwf_id, const void *data, uint32_t amount, int internal_id) +{ + uint32_t sent = 0U, to_send; + nsapi_size_or_error_t ret; + + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + + _process_winds(); // perform async indication handling (to early detect eventually closed sockets) + + /* betzw - WORK AROUND module FW issues: split up big packages in smaller ones */ + for(to_send = (amount > SPWFXX_SEND_RECV_PKTSIZE) ? SPWFXX_SEND_RECV_PKTSIZE : amount; + sent < amount; + to_send = ((amount - sent) > SPWFXX_SEND_RECV_PKTSIZE) ? SPWFXX_SEND_RECV_PKTSIZE : (amount - sent)) { + { + BlockExecuter bh_handler(Callback<void()>(this, &SPWFSAxx::_execute_bottom_halves)); + + // betzw - TODO: handle different errors more accurately! + if (!_associated_interface._socket_is_still_connected(internal_id)) { + debug_if(_dbg_on, "\r\nSPWF> Socket not connected anymore: sent=%u, to_send=%u! (%s, %d)\r\n", sent, to_send, __func__, __LINE__); + break; + } else if(!_parser.send("AT+S.SOCKW=%d,%d", spwf_id, (unsigned int)to_send)) { + debug_if(_dbg_on, "\r\nSPWF> Sending command failed: sent=%u, to_send=%u! (%s, %d)\r\n", sent, to_send, __func__, __LINE__); + break; + } else if(_parser.write(((char*)data)+sent, (int)to_send) != (int)to_send) { + debug_if(_dbg_on, "\r\nSPWF> Sending data failed: sent=%u, to_send=%u! (%s, %d)\r\n", sent, to_send, __func__, __LINE__); + break; + } else if(!_recv_ok()) { + debug_if(_dbg_on, "\r\nSPWF> Sending did not receive OK: sent=%u, to_send=%u! (%s, %d)\r\n", sent, to_send, __func__, __LINE__); + break; + } + } + + sent += to_send; + } + + if(sent > 0) { // `sent == 0` indicates a potential error + ret = sent; + } else if(amount == 0) { + ret = NSAPI_ERROR_OK; + } else if(_associated_interface._socket_is_still_connected(internal_id)) { + ret = NSAPI_ERROR_DEVICE_ERROR; + } else { + ret = NSAPI_ERROR_CONNECTION_LOST; + } + + return ret; +} + +int SPWFSAxx::_read_len(int spwf_id) { + unsigned int amount; + + if (!(_parser.send("AT+S.SOCKQ=%d", spwf_id) + && _parser.recv(SPWFXX_RECV_DATALEN, &amount) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> %s failed\r\n", __func__); + return SPWFXX_ERR_LEN; + } + + if(amount > 0) { + debug_if(_dbg_on, "\r\nSPWF> %s():\t\t%d:%d\r\n", __func__, spwf_id, amount); + } + + MBED_ASSERT(((int)amount) >= 0); + + return (int)amount; +} + +#define SPWFXX_WINDS_OFF "0xFFFFFFFF" + +void SPWFSAxx::_winds_on(void) { + MBED_ASSERT(_is_event_callback_blocked()); + + if(!(_parser.send(SPWFXX_SEND_WIND_OFF_HIGH SPWFXX_WINDS_HIGH_ON) && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__); + } + if(!(_parser.send(SPWFXX_SEND_WIND_OFF_MEDIUM SPWFXX_WINDS_MEDIUM_ON) && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__); + } + if(!(_parser.send(SPWFXX_SEND_WIND_OFF_LOW SPWFXX_WINDS_LOW_ON) && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__); + } +} + +/* Define beyond macro in case you want to report back failures in switching off WINDs to the caller */ +// #define SPWFXX_SOWF +/* Note: in case of error blocking has been (tried to be) lifted */ +bool SPWFSAxx::_winds_off(void) { + MBED_ASSERT(_is_event_callback_blocked()); + + if (!(_parser.send(SPWFXX_SEND_WIND_OFF_LOW SPWFXX_WINDS_OFF) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__); +#ifdef SPWFXX_SOWF // betzw: try to continue + _winds_on(); + return false; +#endif + } + + if (!(_parser.send(SPWFXX_SEND_WIND_OFF_MEDIUM SPWFXX_WINDS_OFF) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__); +#ifdef SPWFXX_SOWF // betzw: try to continue + _winds_on(); + return false; +#endif + } + + if (!(_parser.send(SPWFXX_SEND_WIND_OFF_HIGH SPWFXX_WINDS_OFF) + && _recv_ok())) { + debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__); +#ifdef SPWFXX_SOWF // betzw: try to continue + _winds_on(); + return false; +#endif + } + + return true; +} + +void SPWFSAxx::_execute_bottom_halves(void) { + _network_lost_handler_bh(); + _packet_handler_bh(); +} + +void SPWFSAxx::_read_in_pending(void) { + static int internal_id_cnt = 0; + + while(_is_data_pending()) { + if(_associated_interface._socket_has_connected(internal_id_cnt)) { + int spwf_id = _associated_interface._ids[internal_id_cnt].spwf_id; + + if(_is_data_pending(spwf_id)) { + int amount; + + amount = _read_in_pkt(spwf_id, false); + if(amount == SPWFXX_ERR_OOM) { /* consider only 'SPWFXX_ERR_OOM' as non recoverable */ + return; + } + } + + if(!_is_data_pending(spwf_id)) { + internal_id_cnt++; + internal_id_cnt %= SPWFSA_SOCKET_COUNT; + } + } else { + internal_id_cnt++; + internal_id_cnt %= SPWFSA_SOCKET_COUNT; + } + } +} + +/* Note: returns + * 'SPWFXX_ERR_OK' in case of success + * 'SPWFXX_ERR_OOM' in case of "out of memory" + * 'SPWFXX_ERR_READ' in case of `_read_in()` error + */ +int SPWFSAxx::_read_in_packet(int spwf_id, uint32_t amount) { + struct packet *packet = (struct packet*)malloc(sizeof(struct packet) + amount); + if (!packet) { +#ifndef NDEBUG + error("\r\nSPWF> %s(%d): Out of memory!\r\n", __func__, __LINE__); +#else // NDEBUG + debug("\r\nSPWF> %s(%d): Out of memory!\r\n", __func__, __LINE__); +#endif + debug_if(_dbg_on, "\r\nSPWF> %s failed (%d)\r\n", __func__, __LINE__); + return SPWFXX_ERR_OOM; /* out of memory: give up here! */ + } + + /* init packet */ + packet->id = spwf_id; + packet->len = amount; + packet->next = 0; + + /* read data in */ + if(!(_read_in((char*)(packet + 1), spwf_id, amount) > 0)) { + free(packet); + debug_if(_dbg_on, "\r\nSPWF> %s failed (%d)\r\n", __func__, __LINE__); + return SPWFXX_ERR_READ; + } else { + debug_if(_dbg_on, "\r\nSPWF> %s():\t%d:%d\r\n", __func__, spwf_id, amount); + + /* append to packet list */ + *_packets_end = packet; + _packets_end = &packet->next; + + /* force call of (external) callback */ + _call_callback(); + } + + return SPWFXX_ERR_OK; +} + +void SPWFSAxx::_free_packets(int spwf_id) { + // check if any packets are ready for `spwf_id` + for(struct packet **p = &_packets; *p;) { + if ((*p)->id == spwf_id) { + struct packet *q = *p; + if (_packets_end == &(*p)->next) { + _packets_end = p; + } + *p = (*p)->next; + free(q); + } else { + p = &(*p)->next; + } + } +} + +void SPWFSAxx::_free_all_packets() { + for (int spwf_id = 0; spwf_id < SPWFSA_SOCKET_COUNT; spwf_id++) { + _free_packets(spwf_id); + } +} + +bool SPWFSAxx::close(int spwf_id) +{ + bool ret = false; + + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */ + + MBED_ASSERT(((unsigned int)spwf_id) < ((unsigned int)SPWFSA_SOCKET_COUNT)); // `spwf_id` is valid + + for(int retry_cnt = 0; retry_cnt < SPWFXX_MAX_TRIALS; retry_cnt++) { + Timer timer; + timer.start(); + + // Flush out pending data + while(true) { + int amount = _read_in_pkt(spwf_id, true); + if(amount < 0) { // SPWFXX error + /* empty RX buffer & try to close */ + empty_rx_buffer(); + break; + } + if(amount == 0) break; // no more data to be read + + /* Try to work around module API bug: + * break out & try to close after 20 seconds + */ + if(timer.read() > 20) { + break; + } + + /* immediately free packet(s) (to avoid "out of memory") */ + _free_packets(spwf_id); + + /* interleave bottom halves */ + _execute_bottom_halves(); + } + + // Close socket + if (_parser.send("AT+S.SOCKC=%d", spwf_id) + && _recv_ok()) { + ret = true; + break; // finish closing + } else { // close failed + debug_if(_dbg_on, "\r\nSPWF> %s failed (%d)\r\n", __func__, __LINE__); + /* interleave bottom halves */ + _execute_bottom_halves(); + + /* free packets */ + _free_packets(spwf_id); + } + } + + /* anticipate bottom halves */ + _execute_bottom_halves(); + + if(ret) { + /* clear pending data flag (should be redundant) */ + _clear_pending_data(spwf_id); + + /* free packets for this socket */ + _free_packets(spwf_id); + + /* reset pending data sizes */ + _reset_pending_pkt_sizes(spwf_id); + } else { + debug_if(_dbg_on, "\r\nSPWF> SPWFSAxx::close failed (%d)\r\n", __LINE__); + + int internal_id = _associated_interface.get_internal_id(spwf_id); + if(!_associated_interface._socket_is_still_connected(internal_id)) { + /* clear pending data flag (should be redundant) */ + _clear_pending_data(spwf_id); + + /* free packets for this socket */ + _free_packets(spwf_id); + + /* reset pending data sizes */ + _reset_pending_pkt_sizes(spwf_id); + + ret = true; + } + } + + return ret; +} + +/* + * Buffered serial event handler + * + * Note: executed in IRQ context! + * Note: do not call (external) callback in IRQ context while performing critical module operations + */ +void SPWFSAxx::_event_handler(void) +{ + if(!_is_event_callback_blocked()) { + _call_callback(); + } +} + +/* + * Common error handler + */ +void SPWFSAxx::_error_handler(void) +{ + if(_parser.recv("%255[^\n]\n", _msg_buffer) && _recv_delim_lf()) { + debug_if(_dbg_on, "AT^ ERROR:%s (%d)\r\n", _msg_buffer, __LINE__); + } else { + debug_if(_dbg_on, "\r\nSPWF> Unknown ERROR string in SPWFSAxx::_error_handler (%d)\r\n", __LINE__); + } + + /* force call of (external) callback */ + _call_callback(); +} + +/* + * Handling oob ("+WIND:33:WiFi Network Lost") + */ +void SPWFSAxx::_network_lost_handler_th(void) +{ +#ifndef NDEBUG + static unsigned int net_loss_cnt = 0; + net_loss_cnt++; +#endif + + _recv_delim_cr_lf(); + + debug_if(_dbg_on, "AT^ +WIND:33:WiFi Network Lost\r\n"); + +#ifndef NDEBUG + debug_if(_dbg_on, "\r\nSPWF> Getting out of SPWFSAxx::_network_lost_handler_th: %d\r\n", net_loss_cnt); +#else // NDEBUG + debug_if(_dbg_on, "\r\nSPWF> Getting out of SPWFSAxx::_network_lost_handler_th: %d\r\n", __LINE__); +#endif // NDEBUG + + /* set flag to signal network loss */ + _network_lost_flag = true; + + /* force call of (external) callback */ + _call_callback(); + + return; +} + +/* betzw - WORK AROUND module FW issues: split up big packages in smaller ones */ +void SPWFSAxx::_add_pending_packet_sz(int spwf_id, uint32_t size) { + uint32_t to_add; + uint32_t added = _get_cumulative_size(spwf_id); + + if(size <= added) { // might happen due to delayed WIND delivery + debug_if(_dbg_on, "\r\nSPWF> WARNING: %s failed at line #%d\r\n", __func__, __LINE__); + return; + } + + for(to_add = ((size - added) > SPWFXX_SEND_RECV_PKTSIZE) ? SPWFXX_SEND_RECV_PKTSIZE : (size - added); + added < size; + to_add = ((size - added) > SPWFXX_SEND_RECV_PKTSIZE) ? SPWFXX_SEND_RECV_PKTSIZE : (size - added)) { + _add_pending_pkt_size(spwf_id, added + to_add); + added += to_add; + } + + /* force call of (external) callback */ + _call_callback(); + + /* set that data is pending */ + _set_pending_data(spwf_id); +} + +/* + * Handling oob ("+WIND:55:Pending Data") + */ +void SPWFSAxx::_packet_handler_th(void) +{ + int internal_id, spwf_id; + int amount; + + /* parse out the socket id & amount */ + if (!(_parser.recv(SPWFXX_RECV_PENDING_DATA, &spwf_id, &amount) && _recv_delim_lf())) { +#ifndef NDEBUG + error("\r\nSPWF> SPWFSAxx::%s failed!\r\n", __func__); +#endif + return; + } + + debug_if(_dbg_on, "AT^ +WIND:55:Pending Data:%d:%d\r\n", spwf_id, amount); + + /* check for the module to report a valid id */ + MBED_ASSERT(((unsigned int)spwf_id) < ((unsigned int)SPWFSA_SOCKET_COUNT)); + + /* set that there is pending data for socket */ + /* NOTE: it seems as if asynchronous indications might report not up-to-date data length values + * therefore we just record the socket id without considering the `amount` of data reported! + */ + internal_id = _associated_interface.get_internal_id(spwf_id); + if(internal_id != SPWFSA_SOCKET_COUNT) { + debug_if(_dbg_on, "AT^ +WIND:55:Pending Data:%d:%d - #2\r\n", spwf_id, amount); + _add_pending_packet_sz(spwf_id, amount); + + MBED_ASSERT(_get_pending_pkt_size(spwf_id) != 0); + } else { + debug_if(_dbg_on, "\r\nSPWFSAxx::%s got invalid id %d\r\n", __func__, spwf_id); + } +} + +void SPWFSAxx::_network_lost_handler_bh(void) +{ + if(!_network_lost_flag) return; + _network_lost_flag = false; + + { + bool were_connected; + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* do not call (external) callback in IRQ context as long as network is lost */ + Timer timer; + timer.start(); + + _parser.set_timeout(SPWF_NETLOST_TIMEOUT); + + were_connected = isConnected(); + _associated_interface._connected_to_network = false; + + if(were_connected) { + unsigned int n1, n2, n3, n4; + + while(true) { + if (timer.read_ms() > SPWF_CONNECT_TIMEOUT) { + debug_if(_dbg_on, "\r\nSPWF> SPWFSAxx::_network_lost_handler_bh() #%d\r\n", __LINE__); + disconnect(); + empty_rx_buffer(); + goto nlh_get_out; + } + + if((_parser.recv(SPWFXX_RECV_WIFI_UP, &n1, &n2, &n3, &n4)) && _recv_delim_lf()) { + debug_if(_dbg_on, "\r\nSPWF> Re-connected (%u.%u.%u.%u)!\r\n", n1, n2, n3, n4); + + _associated_interface._connected_to_network = true; + goto nlh_get_out; + } + } + } else { + debug_if(_dbg_on, "\r\nSPWF> Leaving SPWFSAxx::_network_lost_handler_bh\r\n"); + goto nlh_get_out; + } + + nlh_get_out: + debug_if(_dbg_on, "\r\nSPWF> Getting out of SPWFSAxx::_network_lost_handler_bh\r\n"); + _parser.set_timeout(_timeout); + + /* force call of (external) callback */ + _call_callback(); + + return; + } +} + +void SPWFSAxx::_recover_from_hard_faults(void) { + disconnect(); + empty_rx_buffer(); + + /* force call of (external) callback */ + _call_callback(); +} + +/* + * Handling oob ("+WIND:8:Hard Fault") + */ +void SPWFSAxx::_hard_fault_handler(void) +{ + _parser.set_timeout(SPWF_RECV_TIMEOUT); + if(_parser.recv("%255[^\n]\n", _msg_buffer) && _recv_delim_lf()) { +#ifndef NDEBUG + error("\r\nSPWF> hard fault error:\r\n%s\r\n", _msg_buffer); +#else // NDEBUG + debug("\r\nSPWF> hard fault error:\r\n%s\r\n", _msg_buffer); +#endif // NDEBUG + } else { +#ifndef NDEBUG + error("\r\nSPWF> unknown hard fault error\r\n"); +#else // NDEBUG + debug("\r\nSPWF> unknown hard fault error\r\n"); +#endif // NDEBUG + } + + // This is most likely the best we can do to recover from this module hard fault + _parser.set_timeout(SPWF_HF_TIMEOUT); + _recover_from_hard_faults(); + _parser.set_timeout(_timeout); + + /* force call of (external) callback */ + _call_callback(); +} + +/* + * Handling oob ("+WIND:5:WiFi Hardware Failure") + */ +void SPWFSAxx::_wifi_hwfault_handler(void) +{ + unsigned int failure_nr; + + /* parse out the socket id & amount */ + _parser.recv(":%u\n", &failure_nr); + _recv_delim_lf(); + +#ifndef NDEBUG + error("\r\nSPWF> WiFi HW fault error: %u\r\n", failure_nr); +#else // NDEBUG + debug("\r\nSPWF> WiFi HW fault error: %u\r\n", failure_nr); + + // This is most likely the best we can do to recover from this module hard fault + _parser.set_timeout(SPWF_HF_TIMEOUT); + _recover_from_hard_faults(); + _parser.set_timeout(_timeout); +#endif // NDEBUG + + /* force call of (external) callback */ + _call_callback(); +} + +/* + * Handling oob ("+WIND:58:Socket Closed") + * when server closes a client connection + * + * NOTE: When a socket client receives an indication about socket server gone (only for TCP sockets, WIND:58), + * the socket connection is NOT automatically closed! + */ +void SPWFSAxx::_server_gone_handler(void) +{ + int spwf_id, internal_id; + + if(!(_parser.recv(SPWFXX_RECV_SOCKET_CLOSED, &spwf_id) && _recv_delim_lf())) { +#ifndef NDEBUG + error("\r\nSPWF> SPWFSAxx::%s failed!\r\n", __func__); +#endif + goto _get_out; + } + + debug_if(_dbg_on, "AT^ +WIND:58:Socket Closed:%d\r\n", spwf_id); + + /* check for the module to report a valid id */ + MBED_ASSERT(((unsigned int)spwf_id) < ((unsigned int)SPWFSA_SOCKET_COUNT)); + + /* only set `server_gone` + * user still can receive data & must still explicitly close the socket + */ + internal_id = _associated_interface.get_internal_id(spwf_id); + if(internal_id != SPWFSA_SOCKET_COUNT) { + _associated_interface._ids[internal_id].server_gone = true; + } + +_get_out: + /* force call of (external) callback */ + _call_callback(); +} + +#if MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1 +/* + * Handling oob (currently only for "+WIND:24:WiFi Up::") + */ +void SPWFSAxx::_skip_oob(void) +{ + if(_parser.recv("%255[^\n]\n", _msg_buffer) && _recv_delim_lf()) { + debug_if(_dbg_on, "AT^ +WIND:24:WiFi Up::%s\r\n", _msg_buffer); + } else { + debug_if(_dbg_on, "\r\nSPWF> Invalid string in SPWFSAxx::_skip_oob (%d)\r\n", __LINE__); + } +} +#endif + +void SPWFSAxx::setTimeout(uint32_t timeout_ms) +{ + _timeout = timeout_ms; + _parser.set_timeout(timeout_ms); +} + +void SPWFSAxx::attach(Callback<void()> func) +{ + _callback_func = func; /* do not call (external) callback in IRQ context during critical module operations */ +} + +/** + * Recv Function + */ +int32_t SPWFSAxx::recv(int spwf_id, void *data, uint32_t amount, bool datagram) +{ + BlockExecuter bh_handler(Callback<void()>(this, &SPWFSAxx::_execute_bottom_halves)); + + while (true) { + /* check if any packets are ready for us */ + for (struct packet **p = &_packets; *p; p = &(*p)->next) { + if ((*p)->id == spwf_id) { + debug_if(_dbg_on, "\r\nSPWF> Read done on ID %d and length of packet is %d\r\n",spwf_id,(*p)->len); + struct packet *q = *p; + + MBED_ASSERT(q->len > 0); + + if(datagram) { // UDP => always remove pkt size + // will always consume a whole pending size + uint32_t ret; + + debug_if(_dbg_on, "\r\nSPWF> %s():\t\t\t%d:%d (datagram)\r\n", __func__, spwf_id, q->len); + + ret = (amount < q->len) ? amount : q->len; + memcpy(data, q+1, ret); + + if (_packets_end == &(*p)->next) { + _packets_end = p; + } + *p = (*p)->next; + free(q); + + return ret; + } else { // TCP + if (q->len <= amount) { // return and remove full packet + memcpy(data, q+1, q->len); + + if (_packets_end == &(*p)->next) { + _packets_end = p; + } + *p = (*p)->next; + uint32_t len = q->len; + free(q); + + return len; + } else { // `q->len > amount`, return only partial packet + if(amount > 0) { + memcpy(data, q+1, amount); + q->len -= amount; + memmove(q+1, (uint8_t*)(q+1) + amount, q->len); + } + + return amount; + } + } + } + } + + /* check for pending data on module */ + { + int len; + + len = _read_in_pkt(spwf_id, false); + if(len <= 0) { /* SPWFXX error or no more data to be read */ + return -1; + } + } + } +} + +void SPWFSAxx::_process_winds(void) { + do { + if(_parser.process_oob()) { + /* nothing else to do! */; + } else { + debug_if(_dbg_on, "%s():\t\tNo (more) oob's found!\r\n", __func__); + return; // no (more) oob's found + } + } while(true); +} + +/* Note: returns + * '>=0' in case of success, amount of read in data (in bytes) + * 'SPWFXX_ERR_OOM' in case of "out of memory" + * 'SPWFXX_ERR_READ' in case of other `_read_in_packet()` error + * 'SPWFXX_ERR_LEN' in case of `_read_len()` error + */ +int SPWFSAxx::_read_in_pkt(int spwf_id, bool close) { + int pending; + uint32_t wind_pending; + BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback), + Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* do not call (external) callback in IRQ context while receiving */ + + _process_winds(); // perform async indication handling + + if(close) { // read in all data + wind_pending = pending = _read_len(spwf_id); // triggers also async indication handling! + + if(pending > 0) { + /* reset pending data sizes */ + _reset_pending_pkt_sizes(spwf_id); + /* create new entry for pending size */ + _add_pending_pkt_size(spwf_id, (uint32_t)pending); +#ifndef NDEBUG + wind_pending = _get_pending_pkt_size(spwf_id); + MBED_ASSERT(pending == (int)wind_pending); +#endif + } else if(pending < 0) { + debug_if(_dbg_on, "\r\nSPWF> %s(), #%d:`_read_len()` failed (%d)!\r\n", __func__, __LINE__, pending); + } + } else { // only read in already notified data + pending = wind_pending = _get_pending_pkt_size(spwf_id); + if(pending == 0) { // special handling for no packets pending (to WORK AROUND missing WINDs)! + pending = _read_len(spwf_id); // triggers also async indication handling! + + if(pending > 0) { + _process_winds(); // perform async indication handling (again) + wind_pending = _get_pending_pkt_size(spwf_id); + + if(wind_pending == 0) { + /* betzw - WORK AROUND module FW issues: create new entry for pending size */ + debug_if(_dbg_on, "%s():\t\tAdd packet w/o WIND (%d)!\r\n", __func__, pending); + _add_pending_packet_sz(spwf_id, (uint32_t)pending); + + pending = wind_pending = _get_pending_pkt_size(spwf_id); + MBED_ASSERT(wind_pending > 0); + } + } else if(pending < 0) { + debug_if(_dbg_on, "\r\nSPWF> %s(), #%d:`_read_len()` failed (%d)!\r\n", __func__, __LINE__, pending); + } + } + } + + if((pending > 0) && (wind_pending > 0)) { + int ret = _read_in_packet(spwf_id, wind_pending); + if(ret < 0) { /* "out of memory" or `_read_in_packet()` error */ + /* we do not know if data is still pending at this point + but leaving the pending data bit set might lead to an endless loop */ + _clear_pending_data(spwf_id); + /* also reset pending data sizes */ + _reset_pending_pkt_sizes(spwf_id); + + return ret; + } + + if((_get_cumulative_size(spwf_id) == 0) && (pending <= (int)wind_pending)) { + _clear_pending_data(spwf_id); + } + } else if(pending < 0) { /* 'SPWFXX_ERR_LEN' error */ + MBED_ASSERT(pending == SPWFXX_ERR_LEN); + /* we do not know if data is still pending at this point + but leaving the pending data bit set might lead to an endless loop */ + _clear_pending_data(spwf_id); + /* also reset pending data sizes */ + _reset_pending_pkt_sizes(spwf_id); + + return pending; + } else if(pending == 0) { + MBED_ASSERT(wind_pending == 0); + _clear_pending_data(spwf_id); + } else if(wind_pending == 0) { // `pending > 0` + /* betzw: should never happen! */ + MBED_ASSERT(false); + } + + return (int)wind_pending; +}