Cellular: Introduced new state machine to replace old CellularConnectionFSM

CellularDevice class own new state machine. Now we don't have to expose state machine
which may change and so we don't have to make API changes if that happens.
EasyCellularConnection uses now CellularDevice instead of old state machine.
pull/8579/head
Teppo Järvelin 2018-08-29 09:31:35 +03:00
parent 0404701b5f
commit 8880538eba
13 changed files with 1444 additions and 195 deletions

View File

@ -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<void(nsapi_event_t, intptr_t)> status_cb)
@ -318,45 +236,35 @@ void EasyCellularConnection::attach(mbed::Callback<void(nsapi_event_t, intptr_t)
void EasyCellularConnection::modem_debug_on(bool on)
{
if (_cellularConnectionFSM) {
CellularDevice *dev = _cellularConnectionFSM->get_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

View File

@ -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<void(nsapi_event_t, intptr_t)> _status_cb;
};

View File

@ -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<void(nsapi_event_t, intptr_t)> 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<void(nsapi_event_t, intptr_t)> _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

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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);
}
}
}

View File

@ -21,17 +21,31 @@
#include <stdint.h>
#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_

View File

@ -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<void(nsapi_event_t, intptr_t)> 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

View File

@ -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<void(nsapi_event_t, intptr_t)> 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

View File

@ -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<void(nsapi_event_t, intptr_t)> 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<void(nsapi_event_t, intptr_t)> _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_ */

View File

@ -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;
}

View File

@ -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_ */

View File

@ -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;