ublox-cellular-base_PSM
Revision 23:eaab8e812a5d, committed 2019-05-03
- Comitter:
- wajahat.abbas@u-blox.com
- Date:
- Fri May 03 13:43:49 2019 +0500
- Parent:
- 22:779971811c46
- Commit message:
- Added PSM support for R412M
Changed in this revision
UbloxCellularBase.cpp | Show annotated file Show diff for this revision Revisions of this file |
UbloxCellularBase.h | Show annotated file Show diff for this revision Revisions of this file |
--- a/UbloxCellularBase.cpp Wed Apr 10 12:05:55 2019 +0500 +++ b/UbloxCellularBase.cpp Fri May 03 13:43:49 2019 +0500 @@ -143,6 +143,25 @@ _dev_info.reg_status_eps = static_cast<NetworkRegistrationStatusEps>(status); } +#ifdef TARGET_UBLOX_C030_R412M +void UbloxCellularBase::set_modem_psm_state(int status) +{ + switch (status) { + case ASLEEP: + tr_info("Modem is going in PSM sleep"); + break; + case AWAKE: + tr_info("Modem is awake from PSM sleep"); + break; + default: + tr_info("Unknown PSM state. %d", status); + break; + } + + _dev_info.modem_psm_state = static_cast<ModemPSMState>(status); +} +#endif + void UbloxCellularBase::set_rat(int acTStatus) { switch (acTStatus) { @@ -384,6 +403,30 @@ read_at_to_char(buf, sizeof (buf), '\n'); } +#ifdef TARGET_UBLOX_C030_R412M +// Callback UUPSMR, set/clear flag for modem psm state. +void UbloxCellularBase::UUPSMR_URC() +{ + int status; + char buf[10]; + + if (read_at_to_char(buf, sizeof (buf), '\n') > 0) { + if (sscanf(buf, ": %d", &status) == 1) { + set_modem_psm_state(status); + //call application registered callbacks + if (status == AWAKE) { //modem coming out of sleep + if (_func_psm_coming_out) { + _func_psm_coming_out(_cb_param_psm_coming_out); + } + } else if(status == ASLEEP) { //modem going into sleep + if (_func_psm_going_in) { + _func_psm_going_in(_cb_param_psm_going_in); + } + } + } + } +} +#endif /********************************************************************** * PROTECTED METHODS **********************************************************************/ @@ -448,6 +491,14 @@ _dev_info.reg_status_csd = CSD_NOT_REGISTERED_NOT_SEARCHING; _dev_info.reg_status_psd = PSD_NOT_REGISTERED_NOT_SEARCHING; _dev_info.reg_status_eps = EPS_NOT_REGISTERED_NOT_SEARCHING; +#ifdef TARGET_UBLOX_C030_R412M + _dev_info.modem_psm_state = AWAKE; + _psm_status = false; + _cb_param_psm_going_in = NULL; + _func_psm_going_in = NULL; + _cb_param_psm_coming_out = NULL; + _func_psm_coming_out = NULL; +#endif } // Destructor. @@ -496,6 +547,10 @@ // Capture the UMWI, just to stop it getting in the way _at->oob("+UMWI", callback(this, &UbloxCellularBase::UMWI_URC)); +#ifdef TARGET_UBLOX_C030_R412M + // Handle PSM URC for going in and coming out of PSM + _at->oob("+UUPSMR", callback(this, &UbloxCellularBase::UUPSMR_URC)); +#endif } } @@ -611,12 +666,12 @@ if (_modem_initialised && (_at != NULL)) { int at_timeout = _at_timeout; // Save previous timeout - _at->set_timeout(1000); - // Check modem is powered off - if(_at->send("AT") && _at->recv("OK")) { + _at->set_timeout(1000); + // Check modem is powered off + if(_at->send("AT") && _at->recv("OK")) { _at->send("AT+CPWROFF") && _at->recv("OK"); - } - _at->set_timeout(at_timeout); + } + _at->set_timeout(at_timeout); } _dev_info.reg_status_csd = CSD_NOT_REGISTERED_NOT_SEARCHING; @@ -748,6 +803,11 @@ _pin = pin; } if (initialise_sim_card()) { +#ifdef TARGET_UBLOX_C030_R412M + if (_psm_status == false) { //psm is not enabled by application yet so disable it at start-up + set_power_saving_mode(0, 0); + } +#endif if (set_device_identity(&_dev_info.dev) && // Set up device identity device_init(_dev_info.dev)) {// Initialise this device // Get the integrated circuit ID of the SIM @@ -1127,7 +1187,7 @@ return success; } -// Power down modem via AT interface. +//application should call init() or connect() in order to initialize the modem bool UbloxCellularBase::reboot_modem() { bool return_val = false; @@ -1207,6 +1267,295 @@ return return_val; } #endif +#ifdef TARGET_UBLOX_C030_R412M +bool UbloxCellularBase::get_power_saving_mode(int *status, int *periodic_time, int *active_time) +{ + char pt_encoded[8+1];// timer value encoded as 3GPP IE + char at_encoded[8+1];// timer value encoded as 3GPP IE + int value, multiplier; + bool return_val; + LOCK(); + //+UCPSMS:1,,,"01000011","01000011" + if (_at->send("AT+UCPSMS?") && _at->recv("+UCPSMS:%d,,,\"%8c\",\"%8c\"\n", status, pt_encoded, at_encoded)) { + if (*status == true) { + //PSM is enabled, decode the timer values, periodic TAU first + value = (pt_encoded[7]- '0'); + value += (pt_encoded[6]- '0') << 1; + value += (pt_encoded[5]- '0') << 2; + value += (pt_encoded[4]- '0') << 3; + value += (pt_encoded[3]- '0') << 4; + + multiplier = (pt_encoded[2]- '0'); + multiplier += (pt_encoded[1]- '0') << 1; + multiplier += (pt_encoded[0]- '0') << 2; + + switch(multiplier) { + //10 minutes + case 0: + value = value*10*60; + break; + + //1 hour + case 1: + value = value*60*60; + break; + + //10 hours + case 2: + value = value*10*60*60; + break; + + //2 seconds + case 3: + value = value*2; + break; + + //30 seconds + case 4: + value = value*30; + break; + + //1 minute + case 5: + value = value*60; + break; + + //320 hours + case 6: + value = value*320*60*60; + break; + + default: + value = 0; + break; + } + *periodic_time = value; + + //decode the active time + value = (at_encoded[7]- '0'); + value += (at_encoded[6]- '0') << 1; + value += (at_encoded[5]- '0') << 2; + value += (at_encoded[4]- '0') << 3; + value += (at_encoded[3]- '0') << 4; + + multiplier = (at_encoded[2]- '0'); + multiplier += (at_encoded[1]- '0') << 1; + multiplier += (at_encoded[0]- '0') << 2; + + switch(multiplier) { + //2 seconds + case 0: + value = value*2; + break; + + //1 minute + case 1: + value = value*60; + break; + + //decihours (6minutes) + case 2: + value = value*6*60; + break; + + default: + value = 0; + break; + } + *active_time = value; + } + return_val = true; + } else { + return_val = false; + } + UNLOCK(); + return return_val; +} + +bool UbloxCellularBase::set_power_saving_mode(int periodic_time, int active_time) +{ + bool return_val = false; + + LOCK(); + int at_timeout = _at_timeout; + at_set_timeout(10000); //AT+CPSMS has response time of < 10s + + //check if modem supports PSM URCs + if (_at->send("AT+UPSMR?") && _at->recv("OK")) { + if (periodic_time == 0 && active_time == 0) { + // disable PSM + if (_at->send("AT+CPSMS=0") && _at->recv("OK")) { + if (_at->send("AT+UPSMR=0") && _at->recv("OK")) {//disable the URC + //de-register the callback + detach_cb_psm_going_in(); + detach_cb_psm_coming_out(); + _psm_status = false; + return_val = true; + } + } + } else { //PSM string encoding code borrowed from AT_CellularPower.cpp + /** + Table 10.5.163a/3GPP TS 24.008: GPRS Timer 3 information element + + Bits 5 to 1 represent the binary coded timer value. + + Bits 6 to 8 defines the timer value unit for the GPRS timer as follows: + 8 7 6 + 0 0 0 value is incremented in multiples of 10 minutes + 0 0 1 value is incremented in multiples of 1 hour + 0 1 0 value is incremented in multiples of 10 hours + 0 1 1 value is incremented in multiples of 2 seconds + 1 0 0 value is incremented in multiples of 30 seconds + 1 0 1 value is incremented in multiples of 1 minute + 1 1 0 value is incremented in multiples of 320 hours (NOTE 1) + 1 1 1 value indicates that the timer is deactivated (NOTE 2). + */ + char pt[8+1];// timer value encoded as 3GPP IE + const int ie_value_max = 0x1f; + uint32_t periodic_timer = 0; + if (periodic_time <= 2*ie_value_max) { // multiples of 2 seconds + periodic_timer = periodic_time/2; + strcpy(pt, "01100000"); + } else { + if (periodic_time <= 30*ie_value_max) { // multiples of 30 seconds + periodic_timer = periodic_time/30; + strcpy(pt, "10000000"); + } else { + if (periodic_time <= 60*ie_value_max) { // multiples of 1 minute + periodic_timer = periodic_time/60; + strcpy(pt, "10100000"); + } else { + if (periodic_time <= 10*60*ie_value_max) { // multiples of 10 minutes + periodic_timer = periodic_time/(10*60); + strcpy(pt, "00000000"); + } else { + if (periodic_time <= 60*60*ie_value_max) { // multiples of 1 hour + periodic_timer = periodic_time/(60*60); + strcpy(pt, "00100000"); + } else { + if (periodic_time <= 10*60*60*ie_value_max) { // multiples of 10 hours + periodic_timer = periodic_time/(10*60*60); + strcpy(pt, "01000000"); + } else { // multiples of 320 hours + int t = periodic_time / (320*60*60); + if (t > ie_value_max) { + t = ie_value_max; + } + periodic_timer = t; + strcpy(pt, "11000000"); + } + } + } + } + } + } + + uint_to_binary_str(periodic_timer, &pt[3], sizeof(pt)-3, 5); + pt[8] = '\0'; + + /** + Table 10.5.172/3GPP TS 24.008: GPRS Timer information element + + Bits 5 to 1 represent the binary coded timer value. + + Bits 6 to 8 defines the timer value unit for the GPRS timer as follows: + + 8 7 6 + 0 0 0 value is incremented in multiples of 2 seconds + 0 0 1 value is incremented in multiples of 1 minute + 0 1 0 value is incremented in multiples of decihours + 1 1 1 value indicates that the timer is deactivated. + + Other values shall be interpreted as multiples of 1 minute in this version of the protocol. + */ + char at[8+1]; + uint32_t active_timer; // timer value encoded as 3GPP IE + if (active_time <= 2*ie_value_max) { // multiples of 2 seconds + active_timer = active_time/2; + strcpy(at, "00000000"); + } else { + if (active_time <= 60*ie_value_max) { // multiples of 1 minute + active_timer = (1<<5) | (active_time/60); + strcpy(at, "00100000"); + } else { // multiples of decihours + int t = active_time / (6*60); + if (t > ie_value_max) { + t = ie_value_max; + } + active_timer = t; + strcpy(at, "01000000"); + } + } + + uint_to_binary_str(active_timer, &at[3], sizeof(at)-3, 5); + at[8] = '\0'; + + if (_at->send("AT+CPSMS=1,,,\"%s\",\"%s\"", pt, at) && _at->recv("OK")) { + if (_at->send("AT+UPSMR=1") && _at->recv("OK")) {//enable the PSM URC + tr_info("PSM enabled successfully!"); + _psm_status = true; + return_val = true; + } else { + tr_error("PSM URCs not supported"); + return_val = false; + } + } else { + tr_error("+CPSMS command failed"); + return_val = false; + } + } + } else { + tr_error("PSM URCs not supported by this version of modem"); + } + at_set_timeout(at_timeout); + UNLOCK(); + return return_val; +} + +void UbloxCellularBase::uint_to_binary_str(uint32_t num, char* str, int str_size, int bit_cnt) +{ + if (!str || str_size < bit_cnt) { + return; + } + int tmp, pos = 0; + + for (int i = 31; i >= 0; i--) { + tmp = num >> i; + if (i < bit_cnt) { + if (tmp&1) { + str[pos] = 1 + '0'; + } else { + str[pos] = 0 + '0'; + } + pos++; + } + } +} + +bool UbloxCellularBase::is_modem_awake() +{ + return (_dev_info.modem_psm_state == AWAKE); +} + +//application should call init() or connect() in order to initialize the modem +void UbloxCellularBase::wakeup_modem() +{ + LOCK(); + + MBED_ASSERT(_at != NULL); + + tr_info("Waking up modem..."); + + modem_power_up(); + + _dev_info.reg_status_csd = CSD_NOT_REGISTERED_NOT_SEARCHING; + _dev_info.reg_status_psd = PSD_NOT_REGISTERED_NOT_SEARCHING; + _dev_info.reg_status_eps = EPS_NOT_REGISTERED_NOT_SEARCHING; + _modem_initialised = false; + + UNLOCK(); +} +#endif // End of File
--- a/UbloxCellularBase.h Wed Apr 10 12:05:55 2019 +0500 +++ b/UbloxCellularBase.h Fri May 03 13:43:49 2019 +0500 @@ -75,6 +75,14 @@ EPS_EMERGENCY_SERVICES_ONLY = 8 } NetworkRegistrationStatusEps; + /** modem PSM states. + * + */ + typedef enum { + AWAKE = 0, + ASLEEP = 1 + } ModemPSMState; + /** Initialise the modem, ready for use. * * @param pin PIN for the SIM card. @@ -256,11 +264,98 @@ */ bool get_modem_rat(int *selected_rat, int *preferred_rat, int *second_preferred_rat); - /** reboot the modem using AT+CFUN=15. + /** reboot the modem using AT+CFUN=15. Application should call init() or connect() before making any other API calls. * * @return true if successful, otherwise false. */ bool reboot_modem(); + +#ifdef TARGET_UBLOX_C030_R412M + /** Important: Callback function is executed in context of AT parser so a user should not issue any AT commands from inside the callback. + * It is recommended to set a flag/event/signal in callback and application can use that to wake up the modem and re-initialize it + * + * application callback for modem going in to PSM sleep + * + * @param func callback function to be executed when modem is going in to PSM sleep + * @param param parameter to be passed to callback function. + */ + void attach_cb_psm_going_in(Callback<void(void*)> func, void *param) + { + _func_psm_going_in = func; + _cb_param_psm_going_in = param; + } + + /** Important: Callback function is executed in context of AT parser so a user should not issue any AT commands from inside the callback. + * It is recommended to set a flag/event/signal in callback and application can use that to wake up the modem and re-initialize it + * + * application callback for modem coming out of PSM sleep + * + * @param func callback function to be executed when modem is coming out of PSM sleep. + * @param param parameter to be passed to callback function. + */ + void attach_cb_psm_coming_out(Callback<void(void*)> func, void *param) + { + _func_psm_coming_out = func; + _cb_param_psm_coming_out = param; + } + + /** de-register the application callback for modem going in to PSM sleep + * + */ + void detach_cb_psm_going_in() + { + _func_psm_going_in = NULL; + _cb_param_psm_going_in = NULL; + } + + /** de-register application callback for modem coming out of PSM sleep + * + */ + void detach_cb_psm_coming_out() + { + _func_psm_coming_out = NULL; + _cb_param_psm_coming_out = NULL; + } + + /** Enable or disable the 3GPP PSM. + * + * Note: Application should reboot the module after enabling PSM in order to enter PSM state. (reboot_modem()) + * Note: Modem can be woken up by toggling the power-on signal. (wakeup_modem()) + * Note: When device enters PSM, all connections(PPP, sockets) and settings that are not saved in NV memory(ATE0, CREG etc) are lost. + * host application should be prepared to re-initialize the modem and re-establish the connections. + * Note: PSM is disabled if both periodic_time and active_time are 0. + * Note: Not all variants/firmware versions support PSM URCs and in that case function will return false. + * + * PSM string encoding code is borrowed from AT_CellularPower.cpp + * + * @param periodic_time requested periodic TAU in seconds. + * @param active_time requested active time in seconds. + * @param func callback function to execute when modem goes to sleep + * @param ptr parameter to callback function + * @return True if successful, otherwise false. + */ + bool set_power_saving_mode(int periodic_tau, int active_time); + + /** Reads the 3GPP PSM status (enabled or disabled) and returns assigned periodic tau and active time values. + * + * @param status 0: PSM disabled, 1: PSM enabled + * @param periodic_tau assigned periodic TAU in seconds. + * @param active_time assigned active time in seconds + * @return True if command successful, otherwise false. + */ + bool get_power_saving_mode(int *status, int *periodic_tau, int *active_time); + + /** Wake up the modem from PSM. Ref to comment on set_power_saving_mode, application should call init() or connect() + * before making any other API calls. + */ + void wakeup_modem(); + + /** True if the modem is not in PSM sleep + * otherwise false. + */ + bool is_modem_awake(); +#endif + protected: #define OUTPUT_ENTER_KEY "\r" @@ -323,6 +418,9 @@ volatile NetworkRegistrationStatusCsd reg_status_csd; //!< Circuit switched attach status. volatile NetworkRegistrationStatusPsd reg_status_psd; //!< Packet switched attach status. volatile NetworkRegistrationStatusEps reg_status_eps; //!< Evolved Packet Switched (e.g. LTE) attach status. +#ifdef TARGET_UBLOX_C030_R412M + volatile ModemPSMState modem_psm_state; //!< last known modem PSM state +#endif } DeviceInfo; /* IMPORTANT: the variables below are available to @@ -492,6 +590,19 @@ */ bool initialise_sim_card(); +#ifdef TARGET_UBLOX_C030_R412M + /** Converts the given uint to binary string. Fills the given str starting from [0] with the number of bits defined by bit_cnt + * For example uint_to_binary_string(9, str, 10) would fill str "0000001001" + * For example uint_to_binary_string(9, str, 3) would fill str "001" + * + * @param num uint to converts to binary string + * @param str buffer for converted binary string + * @param str_size size of the str buffer + * @param bit_cnt defines how many bits are filled to buffer started from lsb + */ + void uint_to_binary_str(uint32_t num, char* str, int str_size, int bit_cnt); +#endif + private: void set_nwk_reg_status_csd(int status); @@ -509,6 +620,15 @@ void CGREG_URC(); void CEREG_URC(); void UMWI_URC(); +#ifdef TARGET_UBLOX_C030_R412M + void UUPSMR_URC(); + bool _psm_status; + void *_cb_param_psm_going_in; + Callback<void(void*)> _func_psm_going_in; /**< Callback. */ + void *_cb_param_psm_coming_out; + Callback<void(void*)> _func_psm_coming_out; /**< Callback. */ + void set_modem_psm_state(int state); +#endif }; #endif // _UBLOX_CELLULAR_BASE_