diff --git a/features/cellular/easy_cellular/EasyCellularConnection.cpp b/features/cellular/easy_cellular/EasyCellularConnection.cpp index 1b8ce219f1..85cf88cf32 100644 --- a/features/cellular/easy_cellular/EasyCellularConnection.cpp +++ b/features/cellular/easy_cellular/EasyCellularConnection.cpp @@ -22,10 +22,8 @@ #include "nsapi_ppp.h" #endif -#include "CellularConnectionFSM.h" -#include "CellularDevice.h" - #include "EasyCellularConnection.h" +#include "CellularSIM.h" #include "CellularLog.h" #include "mbed_wait_api.h" @@ -35,63 +33,26 @@ namespace mbed { -bool EasyCellularConnection::cellular_status(int state, int next_state) -{ - tr_info("cellular_status: %s ==> %s", _cellularConnectionFSM->get_state_string((CellularConnectionFSM::CellularState)state), - _cellularConnectionFSM->get_state_string((CellularConnectionFSM::CellularState)next_state)); - - if (_target_state == state) { - tr_info("Target state reached: %s", _cellularConnectionFSM->get_state_string(_target_state)); - (void)_cellularSemaphore.release(); - return false; // return false -> state machine is halted - } - - // only in case of an error or when connected is reached state and next_state can be the same. - // Release semaphore to return application instead of waiting for semaphore to complete. - if (state == next_state) { - tr_error("cellular_status: state and next_state are same, release semaphore as this is an error in state machine"); - _stm_error = true; - (void)_cellularSemaphore.release(); - return false; // return false -> state machine is halted - } - - return true; -} - void EasyCellularConnection::network_callback(nsapi_event_t ev, intptr_t ptr) { - if (ev == NSAPI_EVENT_CONNECTION_STATUS_CHANGE) { - if (ptr == NSAPI_STATUS_GLOBAL_UP) { - _is_connected = true; - } else { - _is_connected = false; - } - } + // forward to application if (_status_cb) { _status_cb(ev, ptr); } } -EasyCellularConnection::EasyCellularConnection(bool debug) : - _is_connected(false), _is_initialized(false), _stm_error(false), - _target_state(CellularConnectionFSM::STATE_POWER_ON), - _cellularSerial(MDMTXD, MDMRXD, MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE), _cellularSemaphore(0), - _cellularConnectionFSM(0), _credentials_err(NSAPI_ERROR_OK), _status_cb(0) +EasyCellularConnection::EasyCellularConnection(CellularDevice *device) : _is_initialized(false), + _serial(MDMTXD, MDMRXD, MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE), _device(device), + _credentials_err(NSAPI_ERROR_OK), _status_cb(0) { - tr_info("EasyCellularConnection()"); #if USE_APN_LOOKUP _credentials_set = false; #endif // #if USE_APN_LOOKUP - modem_debug_on(debug); } EasyCellularConnection::~EasyCellularConnection() { - if (_cellularConnectionFSM) { - _cellularConnectionFSM->set_callback(NULL); - _cellularConnectionFSM->attach(NULL); - delete _cellularConnectionFSM; - } + } nsapi_error_t EasyCellularConnection::init() @@ -100,18 +61,20 @@ nsapi_error_t EasyCellularConnection::init() _stm_error = false; if (!_is_initialized) { #if defined (MDMRTS) && defined (MDMCTS) - _cellularSerial.set_flow_control(SerialBase::RTSCTS, MDMRTS, MDMCTS); + _serial.set_flow_control(SerialBase::RTSCTS, MDMRTS, MDMCTS); #endif - _cellularConnectionFSM = new CellularConnectionFSM(); - _cellularConnectionFSM->set_serial(&_cellularSerial); - _cellularConnectionFSM->set_callback(callback(this, &EasyCellularConnection::cellular_status)); - - err = _cellularConnectionFSM->init(); - - if (err == NSAPI_ERROR_OK) { - err = _cellularConnectionFSM->start_dispatch(); - _cellularConnectionFSM->attach(callback(this, &EasyCellularConnection::network_callback)); + err = _device->init_stm(&_serial); + if (err != NSAPI_ERROR_OK) { + return NSAPI_ERROR_NO_MEMORY; } + + err = _device->start_dispatch(); + if (err != NSAPI_ERROR_OK) { + return NSAPI_ERROR_NO_MEMORY; + } + + _network = _device->open_network(&_serial); + _device->attach(callback(this, &EasyCellularConnection::network_callback)); _is_initialized = true; } @@ -126,39 +89,29 @@ void EasyCellularConnection::set_credentials(const char *apn, const char *uname, if (_credentials_err) { return; } - CellularNetwork *network = _cellularConnectionFSM->get_network(); - if (network) { - _credentials_err = network->set_credentials(apn, uname, pwd); + _credentials_err = _device->set_credentials(apn, uname, pwd); #if USE_APN_LOOKUP - if (_credentials_err == NSAPI_ERROR_OK) { - _credentials_set = true; - } -#endif // #if USE_APN_LOOKUP - } else { - //if get_network() returns NULL it means there was not enough memory for - //an AT_CellularNetwork element during CellularConnectionFSM initialization - tr_error("There was not enough memory during CellularConnectionFSM initialization"); + if (_credentials_err == NSAPI_ERROR_OK) { + _credentials_set = true; } +#endif // #if USE_APN_LOOKUP } } void EasyCellularConnection::set_sim_pin(const char *sim_pin) { if (sim_pin && strlen(sim_pin) > 0) { - if (!_cellularConnectionFSM) { - _credentials_err = init(); - - if (_credentials_err) { - return; - } + _credentials_err = init(); + if (_credentials_err) { + return; } - _cellularConnectionFSM->set_sim_pin(sim_pin); + _device->set_sim_pin(sim_pin); } } nsapi_error_t EasyCellularConnection::connect(const char *sim_pin, const char *apn, const char *uname, const char *pwd) { - if (_is_connected) { + if (_device->is_connected()) { return NSAPI_ERROR_IS_CONNECTED; } @@ -176,7 +129,7 @@ nsapi_error_t EasyCellularConnection::connect(const char *sim_pin, const char *a nsapi_error_t EasyCellularConnection::check_connect() { - if (_is_connected) { + if (_device->is_connected()) { return NSAPI_ERROR_IS_CONNECTED; } @@ -199,30 +152,26 @@ nsapi_error_t EasyCellularConnection::connect() if (err) { return err; } + #if USE_APN_LOOKUP if (!_credentials_set) { - _target_state = CellularConnectionFSM::STATE_SIM_PIN; - err = _cellularConnectionFSM->continue_to_state(_target_state); + err = _device->set_sim_ready(); if (err == NSAPI_ERROR_OK) { - int sim_wait = _cellularSemaphore.wait(60 * 1000); // reserve 60 seconds to access to SIM - if (sim_wait != 1 || _stm_error) { - tr_error("NO SIM ACCESS"); - err = NSAPI_ERROR_NO_CONNECTION; - } else { - char imsi[MAX_IMSI_LENGTH + 1]; - wait(1); // need to wait to access SIM in some modems - err = _cellularConnectionFSM->get_sim()->get_imsi(imsi); - if (err == NSAPI_ERROR_OK) { - const char *apn_config = apnconfig(imsi); - if (apn_config) { - const char *apn = _APN_GET(apn_config); - const char *uname = _APN_GET(apn_config); - const char *pwd = _APN_GET(apn_config); - tr_info("Looked up APN %s", apn); - err = _cellularConnectionFSM->get_network()->set_credentials(apn, uname, pwd); - } + char imsi[MAX_IMSI_LENGTH + 1]; + wait(1); // need to wait to access SIM in some modems + CellularSIM* sim = _device->open_sim(&_serial); + err = sim->get_imsi(imsi); + if (err == NSAPI_ERROR_OK) { + const char *apn_config = apnconfig(imsi); + if (apn_config) { + const char *apn = _APN_GET(apn_config); + const char *uname = _APN_GET(apn_config); + const char *pwd = _APN_GET(apn_config); + tr_info("Looked up APN %s", apn); + err = _device->set_credentials(apn, uname, pwd); } } + _device->close_sim(); } if (err) { tr_error("APN lookup failed"); @@ -231,84 +180,53 @@ nsapi_error_t EasyCellularConnection::connect() } #endif // USE_APN_LOOKUP - _target_state = CellularConnectionFSM::STATE_CONNECTED; - err = _cellularConnectionFSM->continue_to_state(_target_state); - if (err == NSAPI_ERROR_OK) { - int ret_wait = _cellularSemaphore.wait(10 * 60 * 1000); // cellular network searching may take several minutes - if (ret_wait != 1 || _stm_error) { - tr_info("No cellular connection"); - err = NSAPI_ERROR_NO_CONNECTION; - } + err = _device->connect(); + if (err != NSAPI_ERROR_OK) { + tr_info("No cellular connection"); + err = NSAPI_ERROR_NO_CONNECTION; } - return err; } nsapi_error_t EasyCellularConnection::disconnect() { _credentials_err = NSAPI_ERROR_OK; - _is_connected = false; _is_initialized = false; _stm_error = false; #if USE_APN_LOOKUP _credentials_set = false; #endif // #if USE_APN_LOOKUP - nsapi_error_t err = NSAPI_ERROR_OK; - if (_cellularConnectionFSM && _cellularConnectionFSM->get_network()) { - err = _cellularConnectionFSM->get_network()->disconnect(); - } - - if (err == NSAPI_ERROR_OK) { - delete _cellularConnectionFSM; - _cellularConnectionFSM = NULL; - } - - return err; + return _device->disconnect(); } bool EasyCellularConnection::is_connected() { - return _is_connected; + return _device->is_connected(); } const char *EasyCellularConnection::get_ip_address() { - if (_cellularConnectionFSM) { - CellularNetwork *network = _cellularConnectionFSM->get_network(); - if (!network) { - return NULL; - } - return _cellularConnectionFSM->get_network()->get_ip_address(); - } else { + if (!_network) { return NULL; } + return _network->get_ip_address(); } const char *EasyCellularConnection::get_netmask() { - if (_cellularConnectionFSM) { - CellularNetwork *network = _cellularConnectionFSM->get_network(); - if (!network) { - return NULL; - } - return network->get_netmask(); - } else { + if (!_network) { return NULL; } + return _network->get_netmask(); } const char *EasyCellularConnection::get_gateway() { - if (_cellularConnectionFSM) { - CellularNetwork *network = _cellularConnectionFSM->get_network(); - if (!network) { - return NULL; - } - return network->get_gateway(); - } else { + if (!_network) { return NULL; } + return _network->get_gateway(); } void EasyCellularConnection::attach(mbed::Callback status_cb) @@ -318,45 +236,35 @@ void EasyCellularConnection::attach(mbed::Callbackget_device(); - if (dev) { - dev->modem_debug_on(on); - } - } + _device->modem_debug_on(on); } void EasyCellularConnection::set_plmn(const char *plmn) { if (plmn && strlen(plmn) > 0) { - if (!_cellularConnectionFSM) { + if (!_device) { _credentials_err = init(); - if (_credentials_err) { return; } } - _cellularConnectionFSM->set_plmn(plmn); + _device->set_plmn(plmn); } } NetworkStack *EasyCellularConnection::get_stack() { - if (_cellularConnectionFSM) { - return _cellularConnectionFSM->get_stack(); - } else { - return NULL; - } + return _device->get_stack(); } -CellularDevice *EasyCellularConnection::get_device() +CellularDevice *EasyCellularConnection::get_device() const { - return _cellularConnectionFSM->get_device(); + return _device; } UARTSerial *EasyCellularConnection::get_serial() { - return &_cellularSerial; + return &_serial; } } // namespace diff --git a/features/cellular/easy_cellular/EasyCellularConnection.h b/features/cellular/easy_cellular/EasyCellularConnection.h index fd8d8f07e4..672a6c0abe 100644 --- a/features/cellular/easy_cellular/EasyCellularConnection.h +++ b/features/cellular/easy_cellular/EasyCellularConnection.h @@ -18,10 +18,13 @@ #ifndef EASY_CELLULAR_CONNECTION_H #define EASY_CELLULAR_CONNECTION_H -#include "CellularConnectionFSM.h" +#include "CellularStateMachine.h" + #if defined(CELLULAR_DEVICE) || defined(DOXYGEN_ONLY) -#include "netsocket/CellularBase.h" +#include "CellularDevice.h" +#include "UARTSerial.h" +#include "CellularBase.h" #define USE_APN_LOOKUP (MBED_CONF_CELLULAR_USE_APN_LOOKUP || (NSAPI_PPP_AVAILABLE && MBED_CONF_PPP_CELL_IFACE_APN_LOOKUP)) @@ -34,7 +37,7 @@ namespace mbed { class EasyCellularConnection: public CellularBase { public: - EasyCellularConnection(bool debug = false); + EasyCellularConnection(CellularDevice *device = CellularDevice::get_default_instance()); virtual ~EasyCellularConnection(); public: @@ -140,7 +143,7 @@ public: * * @return cellular device */ - CellularDevice *get_device(); + CellularDevice *get_device() const; /** Get the UART serial file handle used by cellular subsystem * @@ -157,26 +160,19 @@ protected: virtual NetworkStack *get_stack(); private: - /** Callback for cellular status changes - * - * @return true to continue state machine - */ - bool cellular_status(int state, int next_state); void network_callback(nsapi_event_t ev, intptr_t ptr); nsapi_error_t init(); nsapi_error_t check_connect(); - bool _is_connected; bool _is_initialized; bool _stm_error; #if USE_APN_LOOKUP bool _credentials_set; #endif // #if USE_APN_LOOKUP - CellularConnectionFSM::CellularState _target_state; - UARTSerial _cellularSerial; - rtos::Semaphore _cellularSemaphore; - CellularConnectionFSM *_cellularConnectionFSM; + UARTSerial _serial; + CellularDevice *_device; + CellularNetwork* _network; nsapi_error_t _credentials_err; Callback _status_cb; }; diff --git a/features/cellular/framework/API/CellularDevice.h b/features/cellular/framework/API/CellularDevice.h index d065feb123..c55d4c3130 100644 --- a/features/cellular/framework/API/CellularDevice.h +++ b/features/cellular/framework/API/CellularDevice.h @@ -18,10 +18,7 @@ #ifndef CELLULAR_DEVICE_H_ #define CELLULAR_DEVICE_H_ -#include "CellularTargets.h" -#include "EventQueue.h" -#include "nsapi_types.h" -#include "PlatformMutex.h" +#include "CellularStateMachine.h" class NetworkStack; @@ -51,7 +48,7 @@ public: static CellularDevice *get_default_instance(); /** Get event queue that can be chained to main event queue. EventQueue is created in get_default_instance() or - * given to CELLULAR_DEVICE (for example TELIT_HE910 class). + * given to CELLULAR_DEVICE (for example TELIT_HE910 class) if get_default_instance() is not used to create CellularDevice. * @return event queue */ virtual events::EventQueue *get_queue() const; @@ -62,7 +59,7 @@ public: /** virtual Destructor */ - virtual ~CellularDevice() {} + virtual ~CellularDevice(); public: /** Create new CellularNetwork interface. @@ -148,12 +145,185 @@ public: */ virtual nsapi_error_t init_module(FileHandle *fh) = 0; +public: + + /** Inits the internal state machine. + * + * @remark MUST be called if internal state machine is to be used. All the following public methods place in to this + * category. + * + * @param fh file handle to be used as serial. Can be for example UARTSerial + */ + nsapi_error_t init_stm(FileHandle *fh); + + /** Check if the connection is currently established or not + * + * @return true/false If the cellular module have successfully acquired a carrier and is + * connected to an external packet data network using PPP, isConnected() + * API returns true and false otherwise. + */ + bool is_connected() const; + + /** Register callback for status reporting + * + * The specified status callback function will be called on status changes + * on the network and modem. The parameters on the callback are the event type and struct cell_callback_data_t + * giving more information about the event. + * + * @param status_cb The callback for status changes + */ + void attach(Callback status_cb); + + /** Start event queue dispatching in internal state machine by creating a new thread. + * + * @return NSAPI_ERROR_OK on success + * NSAPI_ERROR_NO_MEMORY on memory failure + */ + nsapi_error_t start_dispatch(); + + /** Stop the current operation. Operations: set_device_ready, set_sim_ready, register_to_network, attach_to_network + * + */ + void stop(); + + /** By default operations are synchronous. This method can toggle between sync/async. + * + */ + void set_blocking(bool blocking); + + /** Set the Cellular network credentials + * + * Please check documentation of connect() for default behaviour of APN settings. + * + * @param apn Access point name + * @param uname optionally, Username + * @param pwd optionally, password + */ + nsapi_error_t set_credentials(const char *apn, const char *uname = 0, const char *pwd = 0); + + /** Set the pin code for SIM card + * + * @param sim_pin PIN for the SIM card + */ + void set_sim_pin(const char *sim_pin); + + /** Plmn to use when registering to cellular network. + * If plmn is set then registering is forced to this plmn. If plmn is not set then automatic + * registering is used when registering to a cellular network. Does not start any operations. + * + * @param plmn plmn used when registering to cellular network + */ + void set_plmn(const char* plmn); + +public: // Operations that can be sync/async + + /** Start the interface + * + * Power on the device and does the initializations for communication with the modem.. + * By default this API is synchronous. API can be set to asynchronous with method set_blocking(...). + * In synchronous and asynchronous mode application can get result in from callback which is set with + * attach(...) + * + * @return NSAPI_ERROR_OK on success + * NSAPI_ERROR_NO_MEMORY on case of memory failure + */ + nsapi_error_t set_device_ready(); + + /** Start the interface + * + * Attempts to open the sim. + * By default this API is synchronous. API can be set to asynchronous with method set_blocking(...). + * In synchronous and asynchronous mode application can get result in from callback which is set with + * attach(...) + * + * @return NSAPI_ERROR_OK on success + * NSAPI_ERROR_NO_MEMORY on case of memory failure + */ + nsapi_error_t set_sim_ready(); + + /** Start the interface + * + * Attempts to register the device to cellular network. + * By default this API is synchronous. API can be set to asynchronous with method set_blocking(...). + * In synchronous and asynchronous mode application can get result in from callback which is set with + * attach(...) + * + * @return NSAPI_ERROR_OK on success + * NSAPI_ERROR_NO_MEMORY on case of memory failure + */ + nsapi_error_t register_to_network(); + + /** Start the interface + * + * Attempts to attach the device to cellular network. + * By default this API is synchronous. API can be set to asynchronous with method set_blocking(...). + * In synchronous and asynchronous mode application can get result in from callback which is set with + * attach(...) + * + * @return NSAPI_ERROR_OK on success + * NSAPI_ERROR_NO_MEMORY on case of memory failure + */ + nsapi_error_t attach_to_network(); + + /** Start the interface + * + * Attempts to activate PDP context. + * By default this API is synchronous. API can be set to asynchronous with method set_blocking(...). + * In synchronous and asynchronous mode application can get result in from callback which is set with + * attach(...) + * + * @return NSAPI_ERROR_OK on success + * NSAPI_ERROR_NO_MEMORY on case of memory failure + */ + nsapi_error_t activate_context(); + + /** Start the interface + * + * Attempts to connect to a Cellular network. + * By default this API is synchronous. API can be set to asynchronous with method set_blocking(...). + * In synchronous and asynchronous mode application can get result in from callback which is set with + * attach(...) + * + * @param sim_pin PIN for the SIM card + * @param apn optionally, access point name + * @param uname optionally, Username + * @param pwd optionally, password + * @return NSAPI_ERROR_OK on success, or negative error code on failure + */ + nsapi_error_t connect(const char *sim_pin, const char *apn = 0, const char *uname = 0, const char *pwd = 0); + + /** Start the interface + * + * Attempts to connect to a Cellular network. + * By default this API is synchronous. API can be set to asynchronous with method set_blocking(...). + * In synchronous and asynchronous mode application can get result in from callback which is set with + * attach(...) + * If the SIM requires a PIN, and it is not set/invalid, NSAPI_ERROR_AUTH_ERROR is returned. + * + * @return NSAPI_ERROR_OK on success, or negative error code on failure + */ + nsapi_error_t connect(); + + /** Stop the interface + * + * @return 0 on success, or error code on failure + */ + nsapi_error_t disconnect(); + +private: + void network_callback(nsapi_event_t ev, intptr_t ptr); + Callback _nw_status_cb; + protected: + nsapi_error_t _error; int _network_ref_count; int _sms_ref_count; int _power_ref_count; int _sim_ref_count; int _info_ref_count; + bool _is_connected; + CellularStateMachine *_state_machine; + FileHandle *_fh; }; } // namespace mbed diff --git a/features/cellular/framework/AT/AT_CellularDevice.cpp b/features/cellular/framework/AT/AT_CellularDevice.cpp index 13fcd7f436..1b9c7b764e 100644 --- a/features/cellular/framework/AT/AT_CellularDevice.cpp +++ b/features/cellular/framework/AT/AT_CellularDevice.cpp @@ -29,13 +29,20 @@ using namespace mbed; #define DEFAULT_AT_TIMEOUT 1000 // at default timeout in milliseconds AT_CellularDevice::AT_CellularDevice(EventQueue &queue) : - _atHandlers(0), _network(0), _sms(0), _sim(0), _power(0), _information(0), _queue(queue), + _atHandlers(0), _network(0), _sms(0), _sim(0), _power(0), _information(0), _at_queue(queue), _default_timeout(DEFAULT_AT_TIMEOUT), _modem_debug_on(false) { } AT_CellularDevice::~AT_CellularDevice() { + // make sure that all is deleted even if somewhere close was not called and reference counting is messed up. + _network_ref_count = 1; + _sms_ref_count = 1; + _power_ref_count = 1; + _sim_ref_count = 1; + _info_ref_count = 1; + close_network(); close_sms(); close_power(); @@ -52,7 +59,7 @@ AT_CellularDevice::~AT_CellularDevice() events::EventQueue *AT_CellularDevice::get_queue() const { - return &_queue; + return &_at_queue; } // each parser is associated with one filehandle (that is UART) @@ -70,7 +77,7 @@ ATHandler *AT_CellularDevice::get_at_handler(FileHandle *fileHandle) atHandler = atHandler->_nextATHandler; } - atHandler = new ATHandler(fileHandle, _queue, _default_timeout, "\r", get_send_delay()); + atHandler = new ATHandler(fileHandle, _at_queue, _default_timeout, "\r", get_send_delay()); if (_modem_debug_on) { atHandler->set_debug(_modem_debug_on); } @@ -278,7 +285,7 @@ void AT_CellularDevice::set_timeout(int timeout) } } -uint16_t AT_CellularDevice::get_send_delay() +uint16_t AT_CellularDevice::get_send_delay() const { return 0; } diff --git a/features/cellular/framework/AT/AT_CellularDevice.h b/features/cellular/framework/AT/AT_CellularDevice.h index 21c373ee73..1f56e9be90 100644 --- a/features/cellular/framework/AT/AT_CellularDevice.h +++ b/features/cellular/framework/AT/AT_CellularDevice.h @@ -40,7 +40,6 @@ public: AT_CellularDevice(events::EventQueue &queue); virtual ~AT_CellularDevice(); -protected: ATHandler *_atHandlers; ATHandler *get_at_handler(FileHandle *fh); @@ -77,7 +76,7 @@ public: // CellularDevice virtual void set_timeout(int timeout); - virtual uint16_t get_send_delay(); + virtual uint16_t get_send_delay() const; virtual void modem_debug_on(bool on); @@ -128,7 +127,7 @@ protected: AT_CellularInformation *_information; protected: - events::EventQueue &_queue; + events::EventQueue &_at_queue; int _default_timeout; bool _modem_debug_on; }; diff --git a/features/cellular/framework/AT/AT_CellularNetwork.cpp b/features/cellular/framework/AT/AT_CellularNetwork.cpp index 527589a04d..c5e59b557c 100644 --- a/features/cellular/framework/AT/AT_CellularNetwork.cpp +++ b/features/cellular/framework/AT/AT_CellularNetwork.cpp @@ -160,17 +160,22 @@ void AT_CellularNetwork::read_reg_params_and_compare(RegistrationType type) if (_at.get_last_error() == NSAPI_ERROR_OK && _connection_status_cb) { tr_debug("type: %d, status: %d, lac: %d, cellID: %d, act: %d", type, reg_params._status, reg_params._lac, reg_params._cell_id, reg_params._act); _reg_params._type = type; + cell_callback_data_t data; + data.error = NSAPI_ERROR_OK; if (reg_params._act != _reg_params._act) { _reg_params._act = reg_params._act; - _connection_status_cb((nsapi_event_t)CellularRadioAccessTechnologyChanged, _reg_params._act); + data.status_data = reg_params._act; + _connection_status_cb((nsapi_event_t)CellularRadioAccessTechnologyChanged, (intptr_t)&data); } if (reg_params._status != _reg_params._status) { _reg_params._status = reg_params._status; - _connection_status_cb((nsapi_event_t)CellularRegistrationStatusChanged, _reg_params._status); + data.status_data = reg_params._status; + _connection_status_cb((nsapi_event_t)CellularRegistrationStatusChanged, (intptr_t)&data); } if (reg_params._cell_id != -1 && reg_params._cell_id != _reg_params._cell_id) { _reg_params._cell_id = reg_params._cell_id; - _connection_status_cb((nsapi_event_t)CellularCellIDChanged, _reg_params._cell_id); + data.status_data = reg_params._cell_id; + _connection_status_cb((nsapi_event_t)CellularCellIDChanged, (intptr_t)&data); } } } diff --git a/features/cellular/framework/common/CellularCommon.h b/features/cellular/framework/common/CellularCommon.h index 5b98cdb862..8a67832b98 100644 --- a/features/cellular/framework/common/CellularCommon.h +++ b/features/cellular/framework/common/CellularCommon.h @@ -21,17 +21,31 @@ #include #include "nsapi_types.h" +struct cell_callback_data_t { + nsapi_error_t error; /* possible error code */ + int status_data; /* cellular_event_status related enum or other info in int format. Check cellular_event_status comments.*/ + + cell_callback_data_t() { + error = NSAPI_ERROR_OK; + status_data = -1; + } + }; + /** * Cellular specific event changes. * Connect and disconnect are handled via NSAPI_EVENT_CONNECTION_STATUS_CHANGE + * All enum types have struct *cell_callback_data_t in intptr_t with possible error code in cell_callback_data_t.error. + * Most enum values also have some enum in cell_callback_data_t.enumeration, check comments below. */ typedef enum cellular_event_status { - CellularDeviceReady = NSAPI_EVENT_CELLULAR_STATUS_BASE, /* Modem is powered and ready to receive commands. No additional info in callback intptr_t. */ - CellularSIMStatusChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 1, /* SIM state changed, call SIM state. enum SimState as additional info callback intptr_t. See enum SimState in ../API/CellularSIM.h */ - CellularRegistrationStatusChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 2, /* Registering status changed. enum RegistrationStatus as additional info callback intptr_t. See enum RegistrationStatus in ../API/CellularNetwork.h */ - CellularRegistrationTypeChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 3, /* Registration type changed. enum RegistrationType as additional info callback intptr_t. See enum RegistrationType in ../API/CellularNetwork.h */ - CellularCellIDChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 4, /* Network Cell ID have changed. int cellid as additional info callback intptr_t. */ - CellularRadioAccessTechnologyChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 5, /* Network roaming status have changed. enum RadioAccessTechnology as additional info callback intptr_t. See enum RadioAccessTechnology in ../API/CellularNetwork.h */ + CellularDeviceReady = NSAPI_EVENT_CELLULAR_STATUS_BASE, /* Modem is powered and ready to receive commands. cell_callback_data_t.status_data will be -1 */ + CellularSIMStatusChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 1, /* SIM state changed. cell_callback_data_t.status_data will be enum SimState. See enum SimState in ../API/CellularSIM.h*/ + CellularRegistrationStatusChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 2, /* Registering status changed. cell_callback_data_t.status_data will be enum RegistrationStatus. See enum RegistrationStatus in ../API/CellularNetwork.h*/ + CellularRegistrationTypeChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 3, /* Registration type changed. cell_callback_data_t.status_data will be enum RegistrationType. See enum RegistrationType in ../API/CellularNetwork.h*/ + CellularCellIDChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 4, /* Network Cell ID have changed. cell_callback_data_t.status_data will be int cellid*/ + CellularRadioAccessTechnologyChanged = NSAPI_EVENT_CELLULAR_STATUS_BASE + 5, /* Network roaming status have changed. cell_callback_data_t.status_data will be enum RadioAccessTechnology See enum RadioAccessTechnology in ../API/CellularNetwork.h*/ + CellularAttachNetwork = NSAPI_EVENT_CELLULAR_STATUS_BASE + 6, /* cell_callback_data_t.status_data will be enum AttachStatus. See enum AttachStatus in ../API/CellularNetwork.h */ + CellularActivatePDPContext = NSAPI_EVENT_CELLULAR_STATUS_BASE + 7, /* NSAPI_ERROR_OK in cell_callback_data_t.error on successfully PDP Context activated or negative error */ } cellular_connection_status_t; #endif // CELLULAR_COMMON_ diff --git a/features/cellular/framework/device/CellularDevice.cpp b/features/cellular/framework/device/CellularDevice.cpp index 1f7a53de06..05b4614bae 100644 --- a/features/cellular/framework/device/CellularDevice.cpp +++ b/features/cellular/framework/device/CellularDevice.cpp @@ -16,8 +16,11 @@ */ #include "CellularDevice.h" -#include "EventQueue.h" #include "CellularUtil.h" +#include "CellularLog.h" +#include "CellularTargets.h" +#include "EventQueue.h" +#include "UARTSerial.h" #ifdef CELLULAR_DEVICE #include CELLULAR_STRINGIFY(CELLULAR_DEVICE.h) @@ -39,14 +42,155 @@ MBED_WEAK CellularDevice *CellularDevice::get_default_instance() } #endif // CELLULAR_DEVICE -CellularDevice::CellularDevice() : _network_ref_count(0), _sms_ref_count(0), _power_ref_count(0), _sim_ref_count(0), - _info_ref_count(0) +CellularDevice::CellularDevice() : _error(NSAPI_ERROR_OK), _network_ref_count(0), _sms_ref_count(0), + _power_ref_count(0), _sim_ref_count(0), _info_ref_count(0), _is_connected(false), + _state_machine(0), _fh(0) { } +CellularDevice::~CellularDevice() +{ + delete _state_machine; +} + +void CellularDevice::stop() +{ + MBED_ASSERT(_state_machine); + _state_machine->stop(); +} + +bool CellularDevice::is_connected() const +{ + return _is_connected; +} + events::EventQueue *CellularDevice::get_queue() const { return NULL; } +nsapi_error_t CellularDevice::set_credentials(const char *apn, const char *uname, const char *pwd) +{ + MBED_ASSERT(_state_machine); + return _state_machine->set_credentials(apn, uname, pwd); } + +void CellularDevice::set_sim_pin(const char *sim_pin) +{ + MBED_ASSERT(_state_machine); + _state_machine->set_sim_pin(sim_pin); +} + +nsapi_error_t CellularDevice::init_stm(FileHandle *fh) +{ + MBED_ASSERT(!_state_machine); + + if (fh == NULL) { + return NSAPI_ERROR_PARAMETER; + } + _fh = fh; + + _state_machine = new CellularStateMachine(*this, *get_queue(), open_power(_fh)); + _state_machine->attach(callback(this, &CellularDevice::network_callback)); + _state_machine->set_sim(open_sim(_fh)); + CellularNetwork *nw = open_network(_fh); + _state_machine->set_network(nw); + + nsapi_error_t err = nw->init(); + if (err != NSAPI_ERROR_OK) { + delete _state_machine; + _state_machine = NULL; + } + + return err; +} + +nsapi_error_t CellularDevice::start_dispatch() { + MBED_ASSERT(_state_machine); + return _state_machine->start_dispatch(); +} + +nsapi_error_t CellularDevice::set_device_ready() +{ + return _state_machine->run_to_state(CellularStateMachine::STATE_DEVICE_READY); +} + +nsapi_error_t CellularDevice::set_sim_ready() +{ + return _state_machine->run_to_state(CellularStateMachine::STATE_SIM_PIN); +} + +nsapi_error_t CellularDevice::register_to_network() +{ + return _state_machine->run_to_state(CellularStateMachine::STATE_REGISTERING_NETWORK); +} + +nsapi_error_t CellularDevice::attach_to_network() +{ + return _state_machine->run_to_state(CellularStateMachine::STATE_ATTACHING_NETWORK); +} + +nsapi_error_t CellularDevice::activate_context() +{ + return _state_machine->run_to_state(CellularStateMachine::STATE_ACTIVATING_PDP_CONTEXT); +} + +nsapi_error_t CellularDevice::connect(const char *sim_pin, const char *apn, + const char *uname, const char *pwd) +{ + set_sim_pin(sim_pin); + set_credentials(apn, uname, pwd); + return connect(); +} + +nsapi_error_t CellularDevice::connect() +{ + return _state_machine->run_to_state(CellularStateMachine::STATE_CONNECTED); +} + +nsapi_error_t CellularDevice::disconnect() +{ + MBED_ASSERT(_state_machine); + return _state_machine->disconnect(); +} + +void CellularDevice::set_plmn(const char* plmn) +{ + MBED_ASSERT(_state_machine); + _state_machine->set_plmn(plmn); +} + +void CellularDevice::set_blocking(bool blocking) +{ + MBED_ASSERT(_state_machine); + _state_machine->set_blocking(blocking); +} + +void CellularDevice::attach(mbed::Callback status_cb) +{ + _nw_status_cb = status_cb; +} + +void CellularDevice::network_callback(nsapi_event_t ev, intptr_t ptr) +{ + if (ev == NSAPI_EVENT_CONNECTION_STATUS_CHANGE) { + if (ptr == NSAPI_STATUS_GLOBAL_UP) { + _is_connected = true; + } else { + _is_connected = false; + } + } + + if (ev >= NSAPI_EVENT_CELLULAR_STATUS_BASE && ev <= NSAPI_EVENT_CELLULAR_STATUS_END) { + tr_debug("Device: network_callback called with event: %d, err: %d, data: %d", ev, ((cell_callback_data_t*)ptr)->error, ((cell_callback_data_t*)ptr)->status_data); + } else { + tr_debug("Device: network_callback called with event: %d, ptr: %d", ev, ptr); + } + // forward network callback to application is it has registered with attach + if (_nw_status_cb) { + _nw_status_cb(ev, ptr); + } +} + + +} // namespae mbed diff --git a/features/cellular/framework/device/CellularStateMachine.cpp b/features/cellular/framework/device/CellularStateMachine.cpp new file mode 100644 index 0000000000..76b7cec31a --- /dev/null +++ b/features/cellular/framework/device/CellularStateMachine.cpp @@ -0,0 +1,765 @@ +/* + * Copyright (c) 2018, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * 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 "CellularStateMachine.h" +#include "CellularDevice.h" +#include "CellularLog.h" +#include "CellularUtil.h" +#include "CellularPower.h" +#include "CellularSIM.h" +#include "Thread.h" +#include "UARTSerial.h" + +#ifndef MBED_TRACE_MAX_LEVEL +#define MBED_TRACE_MAX_LEVEL TRACE_LEVEL_INFO +#endif + +// timeout to wait for AT responses +#define TIMEOUT_POWER_ON (1*1000) +#define TIMEOUT_SIM_PIN (1*1000) +#define TIMEOUT_NETWORK (10*1000) +#define TIMEOUT_CONNECT (60*1000) +#define TIMEOUT_REGISTRATION (180*1000) + +// maximum time when retrying network register, attach and connect in seconds ( 20minutes ) +#define TIMEOUT_NETWORK_MAX (20*60) + +#define RETRY_COUNT_DEFAULT 3 + +namespace mbed { + +CellularStateMachine::CellularStateMachine(CellularDevice &device, events::EventQueue &queue, CellularPower *power) : + _cellularDevice(device), _state(STATE_INIT), _next_state(_state), _target_state(_state), + _event_status_cb(0), _network(0), _power(power), _sim(0), _queue(queue), _queue_thread(0), _retry_count(0), + _event_timeout(-1), _event_id(0), _plmn(0), _command_success(false), _plmn_network_found(false), _cb_data(), + _current_event(NSAPI_EVENT_CONNECTION_STATUS_CHANGE), _automatic_reconnect(true), _blocking(true), + _stm_semaphore(0) +{ + memset(_sim_pin, 0, sizeof(_sim_pin)); +#if MBED_CONF_CELLULAR_RANDOM_MAX_START_DELAY == 0 + _start_time = 0; +#else + // so that not every device don't start at the exact same time (for example after power outage) + _start_time = rand() % (MBED_CONF_CELLULAR_RANDOM_MAX_START_DELAY); +#endif // MBED_CONF_CELLULAR_RANDOM_MAX_START_DELAY + + // set initial retry values in seconds + _retry_timeout_array[0] = 1; // double time on each retry in order to keep network happy + _retry_timeout_array[1] = 2; + _retry_timeout_array[2] = 4; + _retry_timeout_array[3] = 8; + _retry_timeout_array[4] = 16; + _retry_timeout_array[5] = 32; + _retry_timeout_array[6] = 64; + _retry_timeout_array[7] = 128; // if around two minutes was not enough then let's wait much longer + _retry_timeout_array[8] = 600; + _retry_timeout_array[9] = TIMEOUT_NETWORK_MAX; + _retry_array_length = RETRY_ARRAY_SIZE; +} + +CellularStateMachine::~CellularStateMachine() +{ + stop(); +} + +void CellularStateMachine::stop() +{ + _queue.cancel(_event_id); + _queue.break_dispatch(); + + if (_queue_thread) { + _queue_thread->terminate(); + delete _queue_thread; + _queue_thread = NULL; + } + + _state = STATE_INIT; + _next_state = _state; + _target_state = _state; + _cb_data.error = NSAPI_ERROR_OK; + _cb_data.status_data = -1; + + if (_power) { + _cellularDevice.close_power(); + _power = NULL; + } + + if (_sim) { + _cellularDevice.close_sim(); + _sim = NULL; + } + + if (_network) { + _cellularDevice.close_network(); + _network = NULL; + } +} + +void CellularStateMachine::set_blocking(bool blocking) +{ + _blocking = blocking; +} + +void CellularStateMachine::set_automatic_reconnect(bool do_reconnect) +{ + _automatic_reconnect = do_reconnect; +} + +events::EventQueue *CellularStateMachine::get_queue() const +{ + return &_queue; +} + +nsapi_error_t CellularStateMachine::set_credentials(const char *apn, const char *uname, const char *pwd) +{ + return _network->set_credentials(apn, uname, pwd); +} + +void CellularStateMachine::set_sim(CellularSIM* sim) +{ + if (_sim) { + // we own this so we'll close it before accepting new one + _cellularDevice.close_sim(); + } + _sim = sim; +} + +void CellularStateMachine::set_network(CellularNetwork* nw) +{ + if (_network) { + _cellularDevice.close_network(); + } + _network = nw; + _network->attach(callback(this, &CellularStateMachine::network_callback)); +} + +void CellularStateMachine::set_power(CellularPower* pwr) +{ + if (_power) { + _cellularDevice.close_power(); + } + _power = pwr; +} + +bool CellularStateMachine::power_on() +{ + _cb_data.error = _power->on(); + if (_cb_data.error != NSAPI_ERROR_OK && _cb_data.error != NSAPI_ERROR_UNSUPPORTED) { + tr_warn("Cellular start failed. Power off/on."); + _cb_data.error = _power->off(); + if (_cb_data.error != NSAPI_ERROR_OK && _cb_data.error != NSAPI_ERROR_UNSUPPORTED) { + tr_error("Cellular power down failing after failed power up attempt!"); + } + return false; + } + return true; +} + +void CellularStateMachine::set_sim_pin(const char *sim_pin) +{ + strncpy(_sim_pin, sim_pin, sizeof(_sim_pin)); + _sim_pin[sizeof(_sim_pin) - 1] = '\0'; +} + +void CellularStateMachine::set_plmn(const char *plmn) +{ + _plmn = plmn; +} + +bool CellularStateMachine::open_sim() +{ + CellularSIM::SimState state = CellularSIM::SimStateUnknown; + // wait until SIM is readable + // here you could add wait(secs) if you know start delay of your SIM + _cb_data.error = _sim->get_sim_state(state); + if (_cb_data.error != NSAPI_ERROR_OK) { + tr_info("Waiting for SIM (err while reading)..."); + if (_event_status_cb) { + _cb_data.status_data = state; + _event_status_cb((nsapi_event_t)CellularSIMStatusChanged, (intptr_t )&_cb_data); + } + return false; + } + + // report current state so callback can set sim pin if needed + if (_event_status_cb) { + _cb_data.status_data = state; + _event_status_cb((nsapi_event_t)CellularSIMStatusChanged, (intptr_t )&_cb_data); + } + + if (state == CellularSIM::SimStatePinNeeded) { + if (strlen(_sim_pin)) { + tr_info("Entering PIN to open SIM"); + _cb_data.error = _sim->set_pin(_sim_pin); + if (_cb_data.error) { + tr_error("SIM pin set failed with: %d", _cb_data.error); + } + } else { + // No sim pin provided even it's needed, stop state machine + tr_error("PIN required but No SIM pin provided."); + _retry_count = RETRY_ARRAY_SIZE; + return false; + } + } + + return state == CellularSIM::SimStateReady; +} + +bool CellularStateMachine::is_registered() +{ + CellularNetwork::RegistrationStatus status; + bool is_registered = false; + + for (int type = 0; type < CellularNetwork::C_MAX; type++) { + if (get_network_registration((CellularNetwork::RegistrationType) type, status, is_registered)) { + tr_debug("get_network_registration: type=%d, status=%d", type, status); + if (is_registered) { + break; + } + } + } + + return is_registered; +} + +bool CellularStateMachine::get_network_registration(CellularNetwork::RegistrationType type, + CellularNetwork::RegistrationStatus &status, bool &is_registered) +{ + is_registered = false; + bool is_roaming = false; + _cb_data.error = _network->get_registration_status(type, status); + if (_cb_data.error != NSAPI_ERROR_OK) { + if (_cb_data.error != NSAPI_ERROR_UNSUPPORTED) { + tr_warn("Get network registration failed (type %d)!", type); + } + return false; + } + switch (status) { + case CellularNetwork::RegisteredRoaming: + is_roaming = true; + // fall-through + case CellularNetwork::RegisteredHomeNetwork: + is_registered = true; + break; + case CellularNetwork::RegisteredSMSOnlyRoaming: + is_roaming = true; + // fall-through + case CellularNetwork::RegisteredSMSOnlyHome: + tr_warn("SMS only network registration!"); + break; + case CellularNetwork::RegisteredCSFBNotPreferredRoaming: + is_roaming = true; + // fall-through + case CellularNetwork::RegisteredCSFBNotPreferredHome: + tr_warn("Not preferred network registration!"); + break; + case CellularNetwork::AttachedEmergencyOnly: + tr_warn("Emergency only network registration!"); + break; + case CellularNetwork::RegistrationDenied: + case CellularNetwork::NotRegistered: + case CellularNetwork::Unknown: + case CellularNetwork::SearchingNetwork: + default: + break; + } + + if (is_roaming) { + tr_warn("Roaming cellular network!"); + } + + return true; +} + +void CellularStateMachine::report_failure(const char *msg) +{ + tr_error("Cellular stm failed with: %s", msg); + + if (_event_status_cb) { + _event_status_cb(_current_event, (intptr_t )&_cb_data); + } + + tr_error("Target state %s was not reached. Returning from state: %s", get_state_string(_target_state), get_state_string(_state)); + if (_blocking) { + _stm_semaphore.release(); + } +} + +const char *CellularStateMachine::get_state_string(CellularState state) const +{ +#if MBED_CONF_MBED_TRACE_ENABLE + static const char *strings[STATE_MAX_FSM_STATE] = { "Init", "Power", "Device ready", "SIM pin", "Registering network", "Manual registering", "Attaching network", "Activating PDP Context", "Connecting network", "Connected", "Disconnecting"}; + return strings[state]; +#else + return NULL; +#endif // #if MBED_CONF_MBED_TRACE_ENABLE +} + +bool CellularStateMachine::is_registered_to_plmn() +{ + int format; + CellularNetwork::operator_t op; + + _cb_data.error = _network->get_operator_params(format, op); + if (_cb_data.error == NSAPI_ERROR_OK) { + if (format == 2) { + // great, numeric format we can do comparison for that + if (strcmp(op.op_num, _plmn) == 0) { + return true; + } + return false; + } + + // format was alpha, get operator names to do the comparing + CellularNetwork::operator_names_list names_list; + _cb_data.error = _network->get_operator_names(names_list); + if (_cb_data.error == NSAPI_ERROR_OK) { + CellularNetwork::operator_names_t *op_names = names_list.get_head(); + bool found_match = false; + while (op_names) { + if (format == 0) { + if (strcmp(op.op_long, op_names->alpha) == 0) { + found_match = true; + } + } else if (format == 1) { + if (strcmp(op.op_short, op_names->alpha) == 0) { + found_match = true; + } + } + + if (found_match) { + if (strcmp(_plmn, op_names->numeric)) { + names_list.delete_all(); + return true; + } + names_list.delete_all(); + return false; + } + } + } + names_list.delete_all(); + } + + return false; +} + +void CellularStateMachine::continue_from_state(CellularState state) +{ + tr_info("Continue state from %s to %s", get_state_string((CellularStateMachine::CellularState)_state), + get_state_string((CellularStateMachine::CellularState)state)); + _state = state; + enter_to_state(state); + _event_id = _queue.call_in(0, callback(this, &CellularStateMachine::event)); + if (!_event_id) { + _cb_data.error = NSAPI_ERROR_NO_MEMORY; + report_failure("Failed to call queue."); + stop(); + } +} + +nsapi_error_t CellularStateMachine::run_to_state(CellularStateMachine::CellularState state) +{ + // update next state so that we don't continue from previous state if state machine was paused and then started again. + _state = _next_state; + _target_state = state; + enter_to_state(_next_state); + + _event_id = _queue.call_in(0, callback(this, &CellularStateMachine::event)); + + if (!_event_id) { + stop(); + return NSAPI_ERROR_NO_MEMORY; + } + + if (_blocking) { + // TODO, should we adjust semaphore wait time according to state we are trying to achieve? + int ret_wait = _stm_semaphore.wait(10 * 60 * 1000); // cellular network searching may take several minutes + if (ret_wait != 1) { + tr_info("No cellular connection"); + return NSAPI_ERROR_NO_CONNECTION; + } + } + + return _cb_data.error; +} + +void CellularStateMachine::enter_to_state(CellularState state) +{ + _next_state = state; + _retry_count = 0; + _command_success = false; + _cb_data.error = NSAPI_ERROR_OK; + _cb_data.status_data = -1; +} + +void CellularStateMachine::retry_state_or_fail() +{ + if (++_retry_count < RETRY_ARRAY_SIZE) { + tr_debug("Retry State %s, retry %d/%d", get_state_string(_state), _retry_count, RETRY_ARRAY_SIZE); + _event_timeout = _retry_timeout_array[_retry_count]; + } else { + report_failure(get_state_string(_state)); + return; + } +} + +void CellularStateMachine::state_init() +{ + // we should check that if power is already on then we can jump to device ready state + _cellularDevice.set_timeout(TIMEOUT_POWER_ON); + tr_info("Cellular state init (timeout %d ms)", TIMEOUT_POWER_ON); + _cb_data.error = _power->is_device_ready(); + if (_cb_data.error != NSAPI_ERROR_OK) { + _event_timeout = _start_time; + tr_info("Init state, waiting %d ms before POWER state)", _start_time); + enter_to_state(STATE_POWER_ON); + } else { + tr_info("Device was ready to accept commands, jump to device ready"); + enter_to_state(STATE_DEVICE_READY); + } +} + +void CellularStateMachine::state_power_on() +{ + _cellularDevice.set_timeout(TIMEOUT_POWER_ON); + tr_info("Cellular power ON (timeout %d ms)", TIMEOUT_POWER_ON); + if (power_on()) { + enter_to_state(STATE_DEVICE_READY); + } else { + // retry to power on device + retry_state_or_fail(); + } +} + +void CellularStateMachine::device_ready() +{ + tr_info("Cellular device ready"); + if (_event_status_cb) { + _event_status_cb((nsapi_event_t)CellularDeviceReady, (intptr_t )&_cb_data); + } + _power->remove_device_ready_urc_cb(mbed::callback(this, &CellularStateMachine::ready_urc_cb)); + _cellularDevice.close_power(); + _power = NULL; +} + +void CellularStateMachine::state_device_ready() +{ + _cellularDevice.set_timeout(TIMEOUT_POWER_ON); + _cb_data.error = _power->set_at_mode(); + if (_cb_data.error == NSAPI_ERROR_OK) { + device_ready(); + enter_to_state(STATE_SIM_PIN); + } else { + if (_retry_count == 0) { + (void)_power->set_device_ready_urc_cb(mbed::callback(this, &CellularStateMachine::ready_urc_cb)); + } + retry_state_or_fail(); + } +} + +void CellularStateMachine::state_sim_pin() +{ + _cellularDevice.set_timeout(TIMEOUT_SIM_PIN); + tr_info("Sim state (timeout %d ms)", TIMEOUT_SIM_PIN); + if (open_sim()) { + bool success = false; + for (int type = 0; type < CellularNetwork::C_MAX; type++) { + _cb_data.error = _network->set_registration_urc((CellularNetwork::RegistrationType)type, true); + if (!_cb_data.error) { + success = true; + } + } + if (!success) { + tr_warn("Failed to set any URC's for registration"); + retry_state_or_fail(); + return; + } + if (_plmn) { + enter_to_state(STATE_MANUAL_REGISTERING_NETWORK); + } else { + enter_to_state(STATE_REGISTERING_NETWORK); + } + } else { + retry_state_or_fail(); + } +} + +void CellularStateMachine::state_registering() +{ + _cellularDevice.set_timeout(TIMEOUT_NETWORK); + if (is_registered()) { + // we are already registered, go to attach + enter_to_state(STATE_ATTACHING_NETWORK); + } else { + _cellularDevice.set_timeout(TIMEOUT_REGISTRATION); + if (!_command_success) { + _cb_data.error = _network->set_registration(); + _command_success = (_cb_data.error == NSAPI_ERROR_OK); + } + retry_state_or_fail(); + } +} + +// only used when _plmn is set +void CellularStateMachine::state_manual_registering_network() +{ + _cellularDevice.set_timeout(TIMEOUT_REGISTRATION); + tr_info("state_manual_registering_network"); + if (!_plmn_network_found) { + if (is_registered() && is_registered_to_plmn()) { + _plmn_network_found = true; + enter_to_state(STATE_ATTACHING_NETWORK); + } else { + if (!_command_success) { + _cb_data.error = _network->set_registration(_plmn); + _command_success = (_cb_data.error == NSAPI_ERROR_OK); + } + retry_state_or_fail(); + } + } +} + +void CellularStateMachine::state_attaching() +{ + _cellularDevice.set_timeout(TIMEOUT_CONNECT); + _cb_data.error = _network->set_attach(); + if (_cb_data.error == NSAPI_ERROR_OK) { + _cellularDevice.close_sim(); + _sim = NULL; + if (_event_status_cb) { + _cb_data.status_data = CellularNetwork::Attached; + _event_status_cb(_current_event, (intptr_t )&_cb_data); + } + enter_to_state(STATE_ACTIVATING_PDP_CONTEXT); + } else { + retry_state_or_fail(); + } +} + +void CellularStateMachine::state_activating_pdp_context() +{ + _cellularDevice.set_timeout(TIMEOUT_CONNECT); + tr_info("Activate PDP Context (timeout %d ms)", TIMEOUT_CONNECT); + _cb_data.error = _network->activate_context(); + if (_cb_data.error == NSAPI_ERROR_OK) { + if (_event_status_cb) { + _event_status_cb(_current_event, (intptr_t )&_cb_data); + } + enter_to_state(STATE_CONNECTING_NETWORK); + } else { + retry_state_or_fail(); + } +} + +void CellularStateMachine::state_connect_to_network() +{ + _cellularDevice.set_timeout(TIMEOUT_CONNECT); + tr_info("Connect to cellular network (timeout %d ms)", TIMEOUT_CONNECT); + _cb_data.error = _network->connect(); + if (_cb_data.error == NSAPI_ERROR_OK) { + _cellularDevice.set_timeout(TIMEOUT_NETWORK); + tr_debug("Connected to cellular network, set at timeout (timeout %d ms)", TIMEOUT_NETWORK); + // when using modems stack connect is synchronous + enter_to_state(STATE_CONNECTED); + } else { + retry_state_or_fail(); + } +} + +void CellularStateMachine::event() +{ + _event_timeout = -1; + switch (_state) { + case STATE_INIT: + _current_event = (nsapi_event_t)CellularDeviceReady; + state_init(); + break; + case STATE_POWER_ON: + _current_event = (nsapi_event_t)CellularDeviceReady; + state_power_on(); + break; + case STATE_DEVICE_READY: + _current_event = (nsapi_event_t)CellularDeviceReady; + state_device_ready(); + break; + case STATE_SIM_PIN: + _current_event = (nsapi_event_t)CellularSIMStatusChanged; + state_sim_pin(); + break; + case STATE_REGISTERING_NETWORK: + _current_event = (nsapi_event_t)CellularRegistrationStatusChanged; + state_registering(); + break; + case STATE_MANUAL_REGISTERING_NETWORK: + _current_event = (nsapi_event_t)CellularRegistrationStatusChanged; + state_manual_registering_network(); + break; + case STATE_ATTACHING_NETWORK: + _current_event = (nsapi_event_t)CellularAttachNetwork; + state_attaching(); + break; + case STATE_ACTIVATING_PDP_CONTEXT: + _current_event = (nsapi_event_t)CellularActivatePDPContext; + state_activating_pdp_context(); + break; + case STATE_CONNECTING_NETWORK: + _current_event = NSAPI_EVENT_CONNECTION_STATUS_CHANGE; + state_connect_to_network(); + break; + case STATE_CONNECTED: + _current_event = NSAPI_EVENT_CONNECTION_STATUS_CHANGE; + break; + case STATE_DISCONNECTING: + default: + MBED_ASSERT(0); + break; + } + + if (_blocking && _target_state == _state && _cb_data.error == NSAPI_ERROR_OK) { + tr_info("Target state reached: %s", get_state_string(_target_state)); + _stm_semaphore.release(); + return; + } + + if (_next_state != _state || _event_timeout >= 0) { + if (_next_state != _state) { // state exit condition + tr_info("Cellular state from %s to %s", get_state_string((CellularStateMachine::CellularState)_state), + get_state_string((CellularStateMachine::CellularState)_next_state)); + } else { + tr_info("Cellular event in %d seconds", _event_timeout); + } + _state = _next_state; + if (_event_timeout == -1) { + _event_timeout = 0; + } + _event_id = _queue.call_in(_event_timeout * 1000, callback(this, &CellularStateMachine::event)); + if (!_event_id) { + _cb_data.error = NSAPI_ERROR_NO_MEMORY; + report_failure("Cellular event failure!"); + return; + } + } +} + +nsapi_error_t CellularStateMachine::start_dispatch() +{ + MBED_ASSERT(!_queue_thread); + + _queue_thread = new rtos::Thread(osPriorityNormal, 2048); + if (!_queue_thread) { + stop(); + return NSAPI_ERROR_NO_MEMORY; + } + if (_queue_thread->start(callback(&_queue, &events::EventQueue::dispatch_forever)) != osOK) { + stop(); + return NSAPI_ERROR_NO_MEMORY; + } + + return NSAPI_ERROR_OK; +} + +void CellularStateMachine::attach(mbed::Callback status_cb) +{ + _event_status_cb = status_cb; +} + +nsapi_error_t CellularStateMachine::disconnect() +{ + nsapi_error_t err = NSAPI_ERROR_OK; + if (_network) { + // set state to disconnecting + _state = STATE_DISCONNECTING; + err = _network->disconnect(); + } + return err; +} + +void CellularStateMachine::network_callback(nsapi_event_t ev, intptr_t ptr) +{ + cell_callback_data_t *data = (cell_callback_data_t*)ptr; + if (ev >= NSAPI_EVENT_CELLULAR_STATUS_BASE && ev <= NSAPI_EVENT_CELLULAR_STATUS_END) { + tr_debug("FSM: network_callback called with event: %d, err: %d, data: %d _state: %s", ev, data->error, data->status_data, get_state_string(_state)); + } else { + tr_debug("FSM: network_callback called with event: %d, ptr: %d _state: %s", ev, ptr, get_state_string(_state)); + } + + if ((cellular_connection_status_t)ev == CellularRegistrationStatusChanged && + (_state == STATE_REGISTERING_NETWORK || _state == STATE_MANUAL_REGISTERING_NETWORK)) { + // expect packet data so only these states are valid + if (data->status_data == CellularNetwork::RegisteredHomeNetwork || data->status_data == CellularNetwork::RegisteredRoaming) { + if (_plmn) { + if (is_registered_to_plmn()) { + if (!_plmn_network_found) { + _plmn_network_found = true; + _queue.cancel(_event_id); + continue_from_state(STATE_ATTACHING_NETWORK); + } + } + } else { + _queue.cancel(_event_id); + continue_from_state(STATE_ATTACHING_NETWORK); + } + } + } + + if (_event_status_cb) { + _event_status_cb(ev, ptr); + } + + // try to reconnect if we think that we are connected, automatic reconnection is on and we get event disconnected + if (_automatic_reconnect && ev == NSAPI_EVENT_CONNECTION_STATUS_CHANGE && ptr == NSAPI_STATUS_DISCONNECTED && + _state == STATE_CONNECTED) { + + tr_info("FSM: start automatic reconnect!"); + // call disconnect to set filehandle irq back to us, don't really care about return value. + (void)_network->disconnect(); + + // start from registering phase as we might have been deregistered if there is no network + if (_plmn) { + continue_from_state(STATE_MANUAL_REGISTERING_NETWORK); + } else { + continue_from_state(STATE_REGISTERING_NETWORK); + } + + if (_event_status_cb) { + _event_status_cb(NSAPI_EVENT_CONNECTION_STATUS_CHANGE, NSAPI_STATUS_RECONNECTING); + } + } +} + +void CellularStateMachine::ready_urc_cb() +{ + tr_debug("Device ready URC func called"); + if (_state == STATE_DEVICE_READY && _power->set_at_mode() == NSAPI_ERROR_OK) { + tr_debug("State was STATE_DEVICE_READY and at mode ready, cancel state and move to next"); + _queue.cancel(_event_id); + device_ready(); + continue_from_state(STATE_SIM_PIN); + } +} + +void CellularStateMachine::set_retry_timeout_array(uint16_t timeout[], int array_len) +{ + _retry_array_length = array_len > RETRY_ARRAY_SIZE ? RETRY_ARRAY_SIZE : array_len; + + for (int i = 0; i < _retry_array_length; i++) { + _retry_timeout_array[i] = timeout[i]; + } +} + +} // namespace + diff --git a/features/cellular/framework/device/CellularStateMachine.h b/features/cellular/framework/device/CellularStateMachine.h new file mode 100644 index 0000000000..f386cfb19d --- /dev/null +++ b/features/cellular/framework/device/CellularStateMachine.h @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2018, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef _CELLULAR_STATEMACHINE_H_ +#define _CELLULAR_STATEMACHINE_H_ + +#include "EventQueue.h" +#include "CellularNetwork.h" +#include "CellularCommon.h" +#include "Semaphore.h" + +namespace rtos { + class Thread; +} + +namespace mbed { + +class UARTSerial; +class CellularPower; +class CellularSIM; +class CellularDevice; + +const int MAX_PIN_SIZE = 8; +const int RETRY_ARRAY_SIZE = 10; + +/** CellularStateMachine class + * + * Finite State Machine for connecting to cellular network. + * By default automatic reconnecting is on. This means that when FSM gets the disconnected callback + * it will try to connect automatically. Application can toggle this behavior with method set_automatic_reconnect(...) + */ +class CellularStateMachine { +public: + /** Constructor + * + * @param device reference to CellularDevice + * @param queue reference to queue used in state transitions + * @param power power needed in first state. Can be also given with set_power but must be given before + * calling run_to_state. Transfers ownership to this class. + */ + CellularStateMachine(CellularDevice &device, events::EventQueue &queue, CellularPower *power); + ~CellularStateMachine(); + +public: + /** Cellular connection states + */ + enum CellularState { + STATE_INIT = 0, + STATE_POWER_ON, + STATE_DEVICE_READY, + STATE_SIM_PIN, + STATE_REGISTERING_NETWORK, + STATE_MANUAL_REGISTERING_NETWORK, + STATE_ATTACHING_NETWORK, + STATE_ACTIVATING_PDP_CONTEXT, + STATE_CONNECTING_NETWORK, + STATE_CONNECTED, + STATE_DISCONNECTING, + STATE_MAX_FSM_STATE + }; + +public: + + /** Set the SIM interface. Transfers ownership to this class. + * + * @param sim sim interface to be used to access sim services + */ + void set_sim(CellularSIM* sim); + + /** Set the network interface. Transfers ownership to this class. + * + * @param nw network interface to be used for network services + */ + void set_network(CellularNetwork* nw); + + /** Set the power interface. Transfers ownership to this class. + * + * @param pwr power interface for power handling + */ + void set_power(CellularPower* pwr); + + /** By default run_to_state is synchronous. This method can toggle between sync/async. + * + */ + void set_blocking(bool blocking); + + /** Disconnects from the cellular network. + * + * @return NSAPI_ERROR_OK on success, negative code in case of failure + */ + nsapi_error_t disconnect(); + + /** By default automatic reconnecting is on. This means that when FSM gets the disconnected callback + * it will try to connect automatically. By this method application can toggle this behavior. + * + * @param do_reconnect true for automatic reconnect, false to not reconnect automatically + */ + void set_automatic_reconnect(bool do_reconnect); + + /** Register callback for status reporting + * + * The specified status callback function will be called on status changes + * on the network. The parameters on the callback are the event type and + * event-type dependent reason parameter. + * + * @param status_cb The callback for status changes + */ + void attach(mbed::Callback status_cb); + + /** Start event queue dispatching + * @return see nsapi_error_t, 0 on success + */ + nsapi_error_t start_dispatch(); + + /** Stop event queue dispatching and close cellular interfaces. + */ + void stop(); + + /** Runs state machine to connected state unless callback method set with set_state_callback return false to stop. + * + * @return see nsapi_error_t, 0 on success + */ + nsapi_error_t run_to_state(CellularState state); + + /** Set the Cellular network credentials + * + * Please check documentation of connect() for default behaviour of APN settings. + * + * @param apn Access point name + * @param uname optionally, Username + * @param pwd optionally, password + */ + nsapi_error_t set_credentials(const char *apn, const char *uname = 0, const char *pwd = 0); + + /** Set cellular device SIM PIN code + * @param sim_pin PIN code + */ + void set_sim_pin(const char *sim_pin); + + /** Sets the timeout array for network rejects. After reject next item is tried and after all items are waited and + * still fails then current network event will fail. + * + * @param timeout timeout array using seconds + * @param array_len length of the array + */ + void set_retry_timeout_array(uint16_t timeout[], int array_len); + + /** Sets the operator plmn which is used when registering to a network specified by plmn. If plmn is not set then automatic + * registering is used when registering to a cellular network. Does not start any operations. + * + * @param plmn operator in numeric format. See more from 3GPP TS 27.007 chapter 7.3. + */ + void set_plmn(const char *plmn); + + /** returns readable format of the given state. Used for printing states while debugging. + * + * @param state state which is returned in string format + * @return string format of the given state + */ + const char *get_state_string(CellularState state) const; + + /** Get event queue that can be chained to main event queue (or use start_dispatch) + * @return event queue + */ + events::EventQueue *get_queue() const; +private: + bool power_on(); + bool open_sim(); + bool get_network_registration(CellularNetwork::RegistrationType type, CellularNetwork::RegistrationStatus &status, bool &is_registered); + bool is_registered(); + void device_ready(); + + // state functions to keep state machine simple + void state_init(); + void state_power_on(); + void state_device_ready(); + void state_sim_pin(); + void state_registering(); + void state_manual_registering_network(); + void state_attaching(); + void state_activating_pdp_context(); + void state_connect_to_network(); + void enter_to_state(CellularState state); + void retry_state_or_fail(); + void network_callback(nsapi_event_t ev, intptr_t ptr); + void continue_from_state(CellularState state); + bool is_registered_to_plmn(); + +private: + void report_failure(const char *msg); + void event(); + void ready_urc_cb(); + + CellularDevice &_cellularDevice; + CellularState _state; + CellularState _next_state; + CellularState _target_state; + + Callback _event_status_cb; + + CellularNetwork *_network; + CellularPower *_power; + CellularSIM *_sim; + events::EventQueue &_queue; + rtos::Thread *_queue_thread; + + char _sim_pin[MAX_PIN_SIZE + 1]; + int _retry_count; + int _start_time; + int _event_timeout; + + uint16_t _retry_timeout_array[RETRY_ARRAY_SIZE]; + int _retry_array_length; + int _event_id; + const char *_plmn; + bool _command_success; + bool _plmn_network_found; + cell_callback_data_t _cb_data; + nsapi_event_t _current_event; + bool _automatic_reconnect; + bool _blocking; + rtos::Semaphore _stm_semaphore; +}; + +} // namespace + +#endif /* _CELLULAR_STATEMACHINE_H_ */ diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.cpp b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.cpp index d478b5161e..6b8d0af18f 100644 --- a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.cpp +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.cpp @@ -47,7 +47,7 @@ AT_CellularPower *TELIT_HE910::open_power_impl(ATHandler &at) return new TELIT_HE910_CellularPower(at); } -uint16_t TELIT_HE910::get_send_delay() +uint16_t TELIT_HE910::get_send_delay() const { return DEFAULT_DELAY_BETWEEN_AT_COMMANDS; } diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.h b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.h index 2830782876..d307019956 100644 --- a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.h +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.h @@ -35,7 +35,7 @@ protected: // AT_CellularDevice virtual AT_CellularPower *open_power_impl(ATHandler &at); public: // from CellularDevice - virtual uint16_t get_send_delay(); + virtual uint16_t get_send_delay() const; }; } // namespace mbed #endif /* CELLULAR_TARGETS_TELIT_HE910_TELIT_HE910_H_ */ diff --git a/features/netsocket/nsapi_types.h b/features/netsocket/nsapi_types.h index c819c471a8..7222fe0c04 100644 --- a/features/netsocket/nsapi_types.h +++ b/features/netsocket/nsapi_types.h @@ -69,6 +69,7 @@ typedef enum nsapi_connection_status { NSAPI_STATUS_GLOBAL_UP = 1, /*!< global IP address set */ NSAPI_STATUS_DISCONNECTED = 2, /*!< no connection to network */ NSAPI_STATUS_CONNECTING = 3, /*!< connecting to network */ + NSAPI_STATUS_RECONNECTING = 4, /*!< reconnecting to network */ NSAPI_STATUS_ERROR_UNSUPPORTED = NSAPI_ERROR_UNSUPPORTED } nsapi_connection_status_t;