diff --git a/UNITTESTS/features/cellular/framework/AT/at_cellulardevice/unittest.cmake b/UNITTESTS/features/cellular/framework/AT/at_cellulardevice/unittest.cmake index f7ead9eaf2..ac50a142f6 100644 --- a/UNITTESTS/features/cellular/framework/AT/at_cellulardevice/unittest.cmake +++ b/UNITTESTS/features/cellular/framework/AT/at_cellulardevice/unittest.cmake @@ -43,6 +43,7 @@ set(unittest-test-sources stubs/SerialBase_stub.cpp stubs/CellularStateMachine_stub.cpp stubs/CellularContext_stub.cpp + stubs/ThisThread_stub.cpp stubs/ConditionVariable_stub.cpp stubs/Mutex_stub.cpp ) diff --git a/features/cellular/framework/AT/ATHandler.cpp b/features/cellular/framework/AT/ATHandler.cpp index 88334167ed..e1123e6166 100644 --- a/features/cellular/framework/AT/ATHandler.cpp +++ b/features/cellular/framework/AT/ATHandler.cpp @@ -926,7 +926,7 @@ void ATHandler::set_3gpp_error(int err, DeviceErrorType error_type) for (size_t i = 0; i < sizeof(map_3gpp_errors) / sizeof(map_3gpp_errors[0]); i++) { if (map_3gpp_errors[i][0] == err) { _last_3gpp_error = map_3gpp_errors[i][1]; - tr_debug("AT3GPP error code %d", get_3gpp_error()); + tr_error("AT3GPP error code %d", get_3gpp_error()); break; } } @@ -943,7 +943,7 @@ void ATHandler::at_error(bool error_code_expected, DeviceErrorType error_type) set_3gpp_error(err, error_type); _last_at_err.errCode = err; _last_at_err.errType = error_type; - tr_error("AT error code %ld", err); + tr_warn("AT error code %ld", err); } else { tr_warn("ATHandler ERROR reading failed"); } diff --git a/features/cellular/framework/AT/AT_CellularContext.cpp b/features/cellular/framework/AT/AT_CellularContext.cpp index c1fbbc3b8d..7bfd6e666f 100644 --- a/features/cellular/framework/AT/AT_CellularContext.cpp +++ b/features/cellular/framework/AT/AT_CellularContext.cpp @@ -608,6 +608,7 @@ nsapi_error_t AT_CellularContext::open_data_channel() connected, or timeout after 30 seconds*/ nsapi_error_t err = nsapi_ppp_connect(_at.get_file_handle(), callback(this, &AT_CellularContext::ppp_status_cb), _uname, _pwd, (nsapi_ip_stack_t)_pdp_type); if (err) { + tr_error("nsapi_ppp_connect failed"); ppp_disconnected(); } @@ -993,6 +994,7 @@ void AT_CellularContext::cellular_callback(nsapi_event_t ev, intptr_t ptr) tr_info("cellular_callback: PPP mode and NSAPI_STATUS_DISCONNECTED"); _cb_data.error = NSAPI_ERROR_NO_CONNECTION; _is_connected = false; + ppp_disconnected(); } } #else diff --git a/features/cellular/framework/AT/AT_CellularDevice.cpp b/features/cellular/framework/AT/AT_CellularDevice.cpp index 33a0d131d7..8835d76a1e 100644 --- a/features/cellular/framework/AT/AT_CellularDevice.cpp +++ b/features/cellular/framework/AT/AT_CellularDevice.cpp @@ -15,6 +15,7 @@ * limitations under the License. */ +#include "rtos/ThisThread.h" #include "CellularUtil.h" #include "AT_CellularDevice.h" #include "AT_CellularInformation.h" @@ -202,6 +203,7 @@ nsapi_error_t AT_CellularDevice::get_sim_state(SimState &state) _at->flush(); nsapi_error_t error = _at->at_cmd_str("+CPIN", "?", simstr, sizeof(simstr)); ssize_t len = strlen(simstr); + device_err_t err = _at->get_last_device_error(); _at->unlock(); if (len != -1) { @@ -213,7 +215,6 @@ nsapi_error_t AT_CellularDevice::get_sim_state(SimState &state) state = SimStatePukNeeded; } else { simstr[len] = '\0'; - tr_error("Unknown SIM state %s", simstr); state = SimStateUnknown; } } else { @@ -229,7 +230,11 @@ nsapi_error_t AT_CellularDevice::get_sim_state(SimState &state) tr_error("SIM PUK required"); break; case SimStateUnknown: - tr_warn("SIM state unknown"); + if (err.errType == DeviceErrorTypeErrorCME && err.errCode == 14) { + tr_info("SIM busy"); + } else { + tr_warn("SIM state unknown"); + } break; default: tr_info("SIM is ready"); @@ -443,12 +448,18 @@ nsapi_error_t AT_CellularDevice::init() setup_at_handler(); _at->lock(); - _at->flush(); - _at->at_cmd_discard("E0", ""); - - _at->at_cmd_discard("+CMEE", "=1"); - - _at->at_cmd_discard("+CFUN", "=1"); + for (int retry = 1; retry <= 3; retry++) { + _at->clear_error(); + _at->flush(); + _at->at_cmd_discard("E0", ""); + _at->at_cmd_discard("+CMEE", "=1"); + _at->at_cmd_discard("+CFUN", "=1"); + if (_at->get_last_error() == NSAPI_ERROR_OK) { + break; + } + tr_debug("Wait 100ms to init modem"); + rtos::ThisThread::sleep_for(100); // let modem have time to get ready + } return _at->unlock_return_error(); } diff --git a/features/cellular/framework/AT/AT_CellularNetwork.cpp b/features/cellular/framework/AT/AT_CellularNetwork.cpp index 0fb1c5b499..5d3b4f7020 100644 --- a/features/cellular/framework/AT/AT_CellularNetwork.cpp +++ b/features/cellular/framework/AT/AT_CellularNetwork.cpp @@ -211,8 +211,9 @@ nsapi_error_t AT_CellularNetwork::set_registration(const char *plmn) if (!plmn) { tr_debug("Automatic network registration"); NWRegisteringMode mode; - get_network_registering_mode(mode); - + if (get_network_registering_mode(mode) != NSAPI_ERROR_OK) { + return NSAPI_ERROR_DEVICE_ERROR; + } if (mode != NWModeAutomatic) { return _at.at_cmd_discard("+COPS", "=0"); } diff --git a/features/cellular/framework/device/CellularDevice.cpp b/features/cellular/framework/device/CellularDevice.cpp index 5115245996..aea7437b09 100644 --- a/features/cellular/framework/device/CellularDevice.cpp +++ b/features/cellular/framework/device/CellularDevice.cpp @@ -234,6 +234,9 @@ nsapi_error_t CellularDevice::shutdown() } CellularContext *curr = get_context_list(); while (curr) { + if (curr->is_connected()) { + curr->disconnect(); + } curr->cellular_callback(NSAPI_EVENT_CONNECTION_STATUS_CHANGE, NSAPI_STATUS_DISCONNECTED); curr = (CellularContext *)curr->_next; } diff --git a/features/cellular/framework/device/CellularStateMachine.cpp b/features/cellular/framework/device/CellularStateMachine.cpp index 3af35d48c2..b7e378ea6f 100644 --- a/features/cellular/framework/device/CellularStateMachine.cpp +++ b/features/cellular/framework/device/CellularStateMachine.cpp @@ -367,6 +367,7 @@ void CellularStateMachine::state_device_ready() } } else { _status = 0; + _is_retry = true; enter_to_state(STATE_INIT); } } diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.cpp b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.cpp index 083bafa4a1..6136863526 100644 --- a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.cpp +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.cpp @@ -97,92 +97,35 @@ void QUECTEL_BG96::set_ready_cb(Callback callback) _at->set_urc_handler(DEVICE_READY_URC, callback); } -nsapi_error_t QUECTEL_BG96::hard_power_on() -{ - if (_pwr.is_connected()) { - tr_info("Modem power on"); - ThisThread::sleep_for(250); - _pwr = !_active_high; - ThisThread::sleep_for(250); // BG96_Hardware_Design_V1.1 says 100 ms, but 250 ms seems to be more robust - _pwr = _active_high; - ThisThread::sleep_for(500); - } - - return NSAPI_ERROR_OK; -} - nsapi_error_t QUECTEL_BG96::soft_power_on() -{ - if (!_rst.is_connected()) { - return NSAPI_ERROR_OK; - } - - tr_info("Reset modem"); - _rst = !_active_high; - ThisThread::sleep_for(100); - _rst = _active_high; - ThisThread::sleep_for(150 + 460); // RESET_N timeout from BG96_Hardware_Design_V1.1 - _rst = !_active_high; - ThisThread::sleep_for(500); - - // wait for RDY - _at->lock(); - _at->set_at_timeout(10 * 1000); - _at->resp_start(); - _at->set_stop_tag("RDY"); - bool rdy = _at->consume_to_stop_tag(); - _at->set_stop_tag(OK); - _at->restore_at_timeout(); - - if (!rdy) { - // check if modem was silently powered on - _at->clear_error(); - _at->set_at_timeout(100); - _at->at_cmd_discard("", ""); //Send AT - _at->restore_at_timeout(); - } - return _at->unlock_return_error(); -} - -nsapi_error_t QUECTEL_BG96::hard_power_off() { if (_pwr.is_connected()) { - tr_info("Modem power off"); - _pwr = _active_high; - ThisThread::sleep_for(650); // from BG96_Hardware_Design_V1.1 - _pwr = !_active_high; + tr_info("QUECTEL_BG96::soft_power_on"); + // check if modem was powered on already + if (wake_up()) { + return NSAPI_ERROR_OK; + } + if (!wake_up(true)) { + tr_error("Modem not responding"); + soft_power_off(); + return NSAPI_ERROR_DEVICE_ERROR; + } } return NSAPI_ERROR_OK; } -nsapi_error_t QUECTEL_BG96::init() +nsapi_error_t QUECTEL_BG96::soft_power_off() { - setup_at_handler(); - - int retry = 0; - _at->lock(); - _at->flush(); - _at->at_cmd_discard("E0", ""); // echo off - - _at->at_cmd_discard("+CMEE", "=1"); // verbose responses - + _at->cmd_start("AT+QPOWD"); + _at->cmd_stop_read_resp(); if (_at->get_last_error() != NSAPI_ERROR_OK) { - return _at->unlock_return_error(); - } - - do { - _at->clear_error(); - _at->at_cmd_discard("+CFUN", "=1"); // set full functionality - if (_at->get_last_error() == NSAPI_ERROR_OK) { - break; + tr_warn("Force modem off"); + if (_pwr.is_connected()) { + press_button(_pwr, 650); // BG96_Hardware_Design_V1.1: Power off signal at least 650 ms } - // wait some time that modem gets ready for CFUN command, and try again - retry++; - ThisThread::sleep_for(64); // experimental value - } while (retry < 3); - + } return _at->unlock_return_error(); } @@ -215,3 +158,52 @@ void QUECTEL_BG96::urc_pdpdeact() } send_disconnect_to_context(cid); } + +void QUECTEL_BG96::press_button(DigitalOut &button, uint32_t timeout) +{ + if (!button.is_connected()) { + return; + } + button = _active_high; + ThisThread::sleep_for(timeout); + button = !_active_high; +} + +bool QUECTEL_BG96::wake_up(bool reset) +{ + // check if modem is already ready + _at->lock(); + _at->flush(); + _at->set_at_timeout(30); + _at->cmd_start("AT"); + _at->cmd_stop_read_resp(); + nsapi_error_t err = _at->get_last_error(); + _at->restore_at_timeout(); + _at->unlock(); + // modem is not responding, power it on + if (err != NSAPI_ERROR_OK) { + if (!reset) { + // BG96_Hardware_Design_V1.1 requires VBAT to be stable over 30 ms, that's handled above + tr_info("Power on modem"); + press_button(_pwr, 250); // BG96_Hardware_Design_V1.1 requires time 100 ms, but 250 ms seems to be more robust + } else { + tr_warn("Reset modem"); + press_button(_rst, 150); // BG96_Hardware_Design_V1.1 requires RESET_N timeout at least 150 ms + } + _at->lock(); + // According to BG96_Hardware_Design_V1.1 USB is active after 4.2s, but it seems to take over 5s + _at->set_at_timeout(6000); + _at->resp_start(); + _at->set_stop_tag("RDY"); + bool rdy = _at->consume_to_stop_tag(); + _at->set_stop_tag(OK); + _at->restore_at_timeout(); + _at->unlock(); + if (!rdy) { + return false; + } + } + + // sync to check that AT is really responsive and to clear garbage + return _at->sync(500); +} diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.h b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.h index 60a02cea4b..592ca3e8c7 100644 --- a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.h +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.h @@ -41,17 +41,16 @@ protected: // AT_CellularDevice virtual AT_CellularContext *create_context_impl(ATHandler &at, const char *apn, bool cp_req = false, bool nonip_req = false); virtual AT_CellularInformation *open_information_impl(ATHandler &at); virtual void set_ready_cb(Callback callback); - virtual nsapi_error_t hard_power_on(); - virtual nsapi_error_t hard_power_off(); virtual nsapi_error_t soft_power_on(); - virtual nsapi_error_t init(); + virtual nsapi_error_t soft_power_off(); virtual void set_at_urcs_impl(); public: void handle_urc(FileHandle *fh); private: - nsapi_error_t press_power_button(uint32_t timeout); + void press_button(DigitalOut &button, uint32_t timeout); + bool wake_up(bool reset = false); bool _active_high; DigitalOut _pwr; DigitalOut _rst;