ublox-cellular-base_PSM

Files at this revision

API Documentation at this revision

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_