ublox-cellular-base_R4_PR
UbloxCellularBase.cpp
- Committer:
- mudassar0121
- Date:
- 2019-08-02
- Branch:
- reset_timeout
- Revision:
- 27:250eaef6232d
- Parent:
- 26:e4e444cc7b14
- Child:
- 29:8a38f91009ad
File content as of revision 27:250eaef6232d:
/* Copyright (c) 2019 ublox Limited * * 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 "UARTSerial.h" #include "APN_db.h" #include "UbloxCellularBase.h" #include "onboard_modem_api.h" #ifdef FEATURE_COMMON_PAL #include "mbed_trace.h" #define TRACE_GROUP "UCB" #else #define tr_debug(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__) #define tr_info(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__) #define tr_warn(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__) #define tr_error(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__) #endif /* Array to convert the 3G qual number into a median EC_NO_LEV number. */ /* 0 1 2 3 4 5 6 7 */ /* 44, 41, 35, 29, 23, 17, 11, 7*/ const int qualConvert3G[] = {-2, -4, -7, -10, -13, -16, -19, -21}; /* Array to convert the 3G "rssi" number into a dBm RSCP value rounded up to the * nearest whole number. */ const int rscpConvert3G[] = {-108, -105, -103, -100, -98, -96, -94, -93, /* 0 - 7 */ -91, -89, -88, -85, -83, -80, -78, -76, /* 8 - 15 */ -74, -73, -70, -68, -66, -64, -63, -60, /* 16 - 23 */ -58, -56, -54, -53, -51, -49, -48, -46}; /* 24 - 31 */ /* Array to convert the LTE rssi number into a dBm value rounded up to the * nearest whole number. */ const int rssiConvertLte[] = {-118, -115, -113, -110, -108, -105, -103, -100, /* 0 - 7 */ -98, -95, -93, -90, -88, -85, -83, -80, /* 8 - 15 */ -78, -76, -74, -73, -71, -69, -68, -65, /* 16 - 23 */ -63, -61, -60, -59, -58, -55, -53, -48}; /* 24 - 31 */ /********************************************************************** * PRIVATE METHODS **********************************************************************/ void UbloxCellularBase::set_nwk_reg_status_csd(int status) { switch (status) { case CSD_NOT_REGISTERED_NOT_SEARCHING: case CSD_NOT_REGISTERED_SEARCHING: tr_info("Not (yet) registered for circuit switched service"); break; case CSD_REGISTERED: case CSD_REGISTERED_ROAMING: tr_info("Registered for circuit switched service"); break; case CSD_REGISTRATION_DENIED: tr_info("Circuit switched service denied"); break; case CSD_UNKNOWN_COVERAGE: tr_info("Out of circuit switched service coverage"); break; case CSD_SMS_ONLY: tr_info("SMS service only"); break; case CSD_SMS_ONLY_ROAMING: tr_info("SMS service only"); break; case CSD_CSFB_NOT_PREFERRED: tr_info("Registered for circuit switched service with CSFB not preferred"); break; default: tr_info("Unknown circuit switched service registration status. %d", status); break; } _dev_info.reg_status_csd = static_cast<NetworkRegistrationStatusCsd>(status); } void UbloxCellularBase::set_nwk_reg_status_psd(int status) { switch (status) { case PSD_NOT_REGISTERED_NOT_SEARCHING: case PSD_NOT_REGISTERED_SEARCHING: tr_info("Not (yet) registered for packet switched service"); break; case PSD_REGISTERED: case PSD_REGISTERED_ROAMING: tr_info("Registered for packet switched service"); break; case PSD_REGISTRATION_DENIED: tr_info("Packet switched service denied"); break; case PSD_UNKNOWN_COVERAGE: tr_info("Out of packet switched service coverage"); break; case PSD_EMERGENCY_SERVICES_ONLY: tr_info("Limited access for packet switched service. Emergency use only."); break; default: tr_info("Unknown packet switched service registration status. %d", status); break; } _dev_info.reg_status_psd = static_cast<NetworkRegistrationStatusPsd>(status); } void UbloxCellularBase::set_nwk_reg_status_eps(int status) { switch (status) { case EPS_NOT_REGISTERED_NOT_SEARCHING: case EPS_NOT_REGISTERED_SEARCHING: tr_info("Not (yet) registered for EPS service"); break; case EPS_REGISTERED: case EPS_REGISTERED_ROAMING: tr_info("Registered for EPS service"); break; case EPS_REGISTRATION_DENIED: tr_info("EPS service denied"); break; case EPS_UNKNOWN_COVERAGE: tr_info("Out of EPS service coverage"); break; case EPS_EMERGENCY_SERVICES_ONLY: tr_info("Limited access for EPS service. Emergency use only."); break; default: tr_info("Unknown EPS service registration status. %d", status); break; } _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) { case GSM: case COMPACT_GSM: tr_info("Connected in GSM"); break; case UTRAN: tr_info("Connected to UTRAN"); break; case EDGE: tr_info("Connected to EDGE"); break; case HSDPA: tr_info("Connected to HSDPA"); break; case HSUPA: tr_info("Connected to HSPA"); break; case HSDPA_HSUPA: tr_info("Connected to HDPA/HSPA"); break; case LTE: tr_info("Connected to LTE"); break; case EC_GSM_IoT: tr_info("Connected to EC_GSM_IoT"); break; case E_UTRAN_NB_S1: tr_info("Connected to E_UTRAN NB1"); break; default: tr_info("Unknown RAT %d", acTStatus); break; } _dev_info.rat = static_cast<RadioAccessNetworkType>(acTStatus); } bool UbloxCellularBase::get_iccid() { bool success; LOCK(); MBED_ASSERT(_at != NULL); // Returns the ICCID (Integrated Circuit Card ID) of the SIM-card. // ICCID is a serial number identifying the SIM. // AT Command Manual UBX-13002752, section 4.12 success = _at->send("AT+CCID") && _at->recv("+CCID: %20[^\n]\nOK\n", _dev_info.iccid); tr_info("DevInfo: ICCID=%s", _dev_info.iccid); UNLOCK(); return success; } bool UbloxCellularBase::get_imsi() { bool success; LOCK(); MBED_ASSERT(_at != NULL); // International mobile subscriber identification // AT Command Manual UBX-13002752, section 4.11 success = _at->send("AT+CIMI") && _at->recv("%15[^\n]\nOK\n", _dev_info.imsi); tr_info("DevInfo: IMSI=%s", _dev_info.imsi); UNLOCK(); return success; } bool UbloxCellularBase::get_imei() { bool success; LOCK(); MBED_ASSERT(_at != NULL); // International mobile equipment identifier // AT Command Manual UBX-13002752, section 4.7 success = _at->send("AT+CGSN") && _at->recv("%15[^\n]\nOK\n", _dev_info.imei); tr_info("DevInfo: IMEI=%s", _dev_info.imei); UNLOCK(); return success; } bool UbloxCellularBase::get_meid() { bool success; LOCK(); MBED_ASSERT(_at != NULL); // Mobile equipment identifier // AT Command Manual UBX-13002752, section 4.8 success = _at->send("AT+GSN") && _at->recv("%18[^\n]\nOK\n", _dev_info.meid); tr_info("DevInfo: MEID=%s", _dev_info.meid); UNLOCK(); return success; } bool UbloxCellularBase::set_sms() { bool success = false; char buf[32]; LOCK(); MBED_ASSERT(_at != NULL); // Set up SMS format and enable URC // AT Command Manual UBX-13002752, section 11 if (_at->send("AT+CMGF=1") && _at->recv("OK")) { tr_debug("SMS in text mode"); if (_at->send("AT+CNMI=2,1") && _at->recv("OK")) { tr_debug("SMS URC enabled"); // Set to CS preferred since PS preferred doesn't work // on some networks if (_at->send("AT+CGSMS=1") && _at->recv("OK")) { tr_debug("SMS set to CS preferred"); success = true; memset (buf, 0, sizeof (buf)); if (_at->send("AT+CSCA?") && _at->recv("+CSCA: \"%31[^\"]\"", buf) && _at->recv("OK")) { tr_info("SMS Service Centre address is \"%s\"", buf); } } } } UNLOCK(); return success; } void UbloxCellularBase::parser_abort_cb() { _at->abort(); } // Callback for CME ERROR and CMS ERROR. void UbloxCellularBase::CMX_ERROR_URC() { char buf[48]; if (read_at_to_char(buf, sizeof (buf), '\n') > 0) { tr_debug("AT error %s", buf); } parser_abort_cb(); } // Callback for circuit switched registration URC. void UbloxCellularBase::CREG_URC() { char buf[10]; int status; int acTStatus; // If this is the URC it will be a single // digit followed by \n. If it is the // answer to a CREG query, it will be // a ": %d,%d\n" where the second digit // indicates the status // Note: not calling _at->recv() from here as we're // already in an _at->recv() if (read_at_to_char(buf, sizeof (buf), '\n') > 0) { if (sscanf(buf, ": %*d,%d,%*d,%*d,%d,", &status, &acTStatus) == 2) { set_nwk_reg_status_csd(status); set_rat(acTStatus); } else if (sscanf(buf, ": %*d,%d", &status) == 1) { set_nwk_reg_status_csd(status); } else if (sscanf(buf, ": %d", &status) == 1) { set_nwk_reg_status_csd(status); } } } // Callback for packet switched registration URC. void UbloxCellularBase::CGREG_URC() { char buf[10]; int status; int acTStatus; // If this is the URC it will be a single // digit followed by \n. If it is the // answer to a CGREG query, it will be // a ": %d,%d\n" where the second digit // indicates the status // Note: not calling _at->recv() from here as we're // already in an _at->recv() if (read_at_to_char(buf, sizeof (buf), '\n') > 0) { if (sscanf(buf, ": %*d,%d,%*d,%*d,%d,", &status, &acTStatus) == 2) { set_nwk_reg_status_csd(status); set_rat(acTStatus); } else if (sscanf(buf, ": %*d,%d", &status) == 1) { set_nwk_reg_status_psd(status); } else if (sscanf(buf, ": %d", &status) == 1) { set_nwk_reg_status_psd(status); } } } // Callback for EPS registration URC. void UbloxCellularBase::CEREG_URC() { char buf[10]; int status; int acTStatus; // If this is the URC it will be a single // digit followed by \n. If it is the // answer to a CEREG query, it will be // a ": %d,%d\n" where the second digit // indicates the status // Note: not calling _at->recv() from here as we're // already in an _at->recv() if (read_at_to_char(buf, sizeof (buf), '\n') > 0) { if (sscanf(buf, ": %*d,%d,%*d,%*d,%d,", &status, &acTStatus) == 2) { set_nwk_reg_status_csd(status); set_rat(acTStatus); } else if (sscanf(buf, ": %*d,%d", &status) == 1) { set_nwk_reg_status_eps(status); } else if (sscanf(buf, ": %d", &status) == 1) { set_nwk_reg_status_eps(status); } } } // Callback UMWI, just filtering it out. void UbloxCellularBase::UMWI_URC() { char buf[10]; // Note: not calling _at->recv() from here as we're // already in an _at->recv() 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 **********************************************************************/ #if MODEM_ON_BOARD void UbloxCellularBase::modem_init() { ::onboard_modem_init(); } void UbloxCellularBase::modem_deinit() { ::onboard_modem_deinit(); } void UbloxCellularBase::modem_power_up() { ::onboard_modem_power_up(); } void UbloxCellularBase::modem_power_down() { ::onboard_modem_power_down(); } #else void UbloxCellularBase::modem_init() { // Meant to be overridden } void UbloxCellularBase::modem_deinit() { // Meant to be overridden } void UbloxCellularBase::modem_power_up() { // Meant to be overridden } void UbloxCellularBase::modem_power_down() { // Mmeant to be overridden } #endif // Constructor. // Note: to allow this base class to be inherited as a virtual base class // by everyone, it takes no parameters. See also comment above classInit() // in the header file. UbloxCellularBase::UbloxCellularBase() { _pin = NULL; _at = NULL; _at_timeout = AT_PARSER_TIMEOUT; _fh = NULL; _modem_initialised = false; _sim_pin_check_enabled = false; _debug_trace_on = false; _dev_info.dev = DEV_TYPE_NONE; _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 #ifdef TARGET_UBLOX_C030_R41XM _edrx_configured = false; #endif } // Destructor. UbloxCellularBase::~UbloxCellularBase() { deinit(); delete _at; delete _fh; } // Initialise the portions of this class that are parameterised. void UbloxCellularBase::baseClassInit(PinName tx, PinName rx, int baud, bool debug_on) { // Only initialise ourselves if it's not already been done if (_at == NULL) { if (_debug_trace_on == false) { _debug_trace_on = debug_on; } _baud = baud; // Set up File Handle for buffered serial comms with cellular module // (which will be used by the AT parser) // Note: the UART is initialised to run no faster than 115200 because // the modems cannot reliably auto-baud at faster rates. The faster // rate is adopted later with a specific AT command and the // UARTSerial rate is adjusted at that time if (baud > 115200) { baud = 115200; } _fh = new UARTSerial(tx, rx, baud); // Set up the AT parser #ifdef TARGET_UBLOX_C030_R41XM _at = new UbloxATCmdParser(_fh, OUTPUT_ENTER_KEY, AT_PARSER_BUFFER_SIZE, _at_timeout, _debug_trace_on); #else _at = new ATCmdParser(_fh, OUTPUT_ENTER_KEY, AT_PARSER_BUFFER_SIZE, _at_timeout, _debug_trace_on); #endif // Error cases, out of band handling _at->oob("ERROR", callback(this, &UbloxCellularBase::parser_abort_cb)); _at->oob("+CME ERROR", callback(this, &UbloxCellularBase::CMX_ERROR_URC)); _at->oob("+CMS ERROR", callback(this, &UbloxCellularBase::CMX_ERROR_URC)); // Registration status, out of band handling _at->oob("+CREG", callback(this, &UbloxCellularBase::CREG_URC)); _at->oob("+CGREG", callback(this, &UbloxCellularBase::CGREG_URC)); _at->oob("+CEREG", callback(this, &UbloxCellularBase::CEREG_URC)); // 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 } } // Set the AT parser timeout. // Note: the AT interface should be locked before this is called. void UbloxCellularBase::at_set_timeout(int timeout) { MBED_ASSERT(_at != NULL); _at_timeout = timeout; _at->set_timeout(timeout); } // Read up to size bytes from the AT interface up to a "end". // Note: the AT interface should be locked before this is called. int UbloxCellularBase::read_at_to_char(char * buf, int size, char end) { int count = 0; int x = 0; if (size > 0) { for (count = 0; (count < size) && (x >= 0) && (x != end); count++) { x = _at->getc(); *(buf + count) = (char) x; } count--; *(buf + count) = 0; // Convert line endings: // If end was '\n' (0x0a) and the preceding character was 0x0d, then // overwrite that with null as well. if ((count > 0) && (end == '\n') && (*(buf + count - 1) == '\x0d')) { count--; *(buf + count) = 0; } } return count; } // Power up the modem. // Enables the GPIO lines to the modem and then wriggles the power line in short pulses. bool UbloxCellularBase::power_up() { bool success = false; int at_timeout; LOCK(); at_timeout = _at_timeout; // Has to be inside LOCK()s MBED_ASSERT(_at != NULL); /* Initialize GPIO lines */ tr_info("Powering up modem..."); modem_init(); /* Give modem a little time to settle down */ wait_ms(250); for (int retry_count = 0; !success && (retry_count < 20); retry_count++) { //In case of SARA-R4, modem takes a while to turn on, constantly toggling the power pin every ~2 secs causes the modem to never power up. if ( (retry_count % 5) == 0) { modem_power_up(); } wait_ms(500); // Modem tends to spit out noise during power up - don't confuse the parser _at->flush(); at_set_timeout(1000); if (_at->send("AT")) { // C027 needs a delay here wait_ms(100); if (_at->recv("OK")) { success = true; } } at_set_timeout(at_timeout); } if (success) { // Set the final baud rate if (_at->send("AT+IPR=%d", _baud) && _at->recv("OK")) { // Need to wait for things to be sorted out on the modem side wait_ms(100); ((UARTSerial *)_fh)->set_baud(_baud); } // Turn off modem echoing and turn on verbose responses success = _at->send("ATE0;+CMEE=2") && _at->recv("OK") && // The following commands are best sent separately _at->send("AT&K0") && _at->recv("OK") && // Turn off RTC/CTS handshaking _at->send("AT&C1") && _at->recv("OK") && // Set DCD circuit(109), changes in accordance with the carrier detect status _at->send("AT&D0") && _at->recv("OK"); // Set DTR circuit, we ignore the state change of DTR } if (!success) { tr_error("Preliminary modem setup failed."); } UNLOCK(); return success; } // Power down modem via AT interface. void UbloxCellularBase::power_down() { LOCK(); MBED_ASSERT(_at != NULL); // power-off modem modem_power_down(); modem_deinit(); 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->send("AT+CPWROFF") && _at->recv("OK"); } _at->set_timeout(at_timeout); } _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; UNLOCK(); } // Get the device ID. bool UbloxCellularBase::set_device_identity(DeviceType *dev) { char buf[20]; bool success; LOCK(); MBED_ASSERT(_at != NULL); success = _at->send("ATI") && _at->recv("%19[^\n]\nOK\n", buf); if (success) { if (strstr(buf, "SARA-G35")) *dev = DEV_SARA_G35; else if (strstr(buf, "LISA-U200-03S")) *dev = DEV_LISA_U2_03S; else if (strstr(buf, "LISA-U2")) *dev = DEV_LISA_U2; else if (strstr(buf, "SARA-U2")) *dev = DEV_SARA_U2; else if (strstr(buf, "SARA-R4")) *dev = DEV_SARA_R4; else if (strstr(buf, "LEON-G2")) *dev = DEV_LEON_G2; else if (strstr(buf, "TOBY-L2")) *dev = DEV_TOBY_L2; else if (strstr(buf, "MPCI-L2")) *dev = DEV_MPCI_L2; } UNLOCK(); return success; } // Send initialisation AT commands that are specific to the device. bool UbloxCellularBase::device_init(DeviceType dev) { bool success = false; LOCK(); MBED_ASSERT(_at != NULL); if ((dev == DEV_LISA_U2) || (dev == DEV_LEON_G2) || (dev == DEV_TOBY_L2)) { success = _at->send("AT+UGPIOC=20,2") && _at->recv("OK"); } else if ((dev == DEV_SARA_U2) || (dev == DEV_SARA_G35)) { success = _at->send("AT+UGPIOC=16,2") && _at->recv("OK"); } else { success = true; } UNLOCK(); return success; } // Get the SIM card going. bool UbloxCellularBase::initialise_sim_card() { bool success = false; int retry_count = 0; bool done = false; LOCK(); MBED_ASSERT(_at != NULL); /* SIM initialisation may take a significant amount, so an error is * kind of expected. We should retry 10 times until we succeed or timeout. */ for (retry_count = 0; !done && (retry_count < 10); retry_count++) { char pinstr[16]; if (_at->send("AT+CPIN?") && _at->recv("+CPIN: %15[^\n]\n", pinstr) && _at->recv("OK")) { done = true; if (strcmp(pinstr, "SIM PIN") == 0) { _sim_pin_check_enabled = true; if (_at->send("AT+CPIN=\"%s\"", _pin)) { if (_at->recv("OK")) { tr_info("PIN correct"); success = true; } else { tr_error("Incorrect PIN"); } } } else if (strcmp(pinstr, "READY") == 0) { _sim_pin_check_enabled = false; tr_info("No PIN required"); success = true; } else { tr_debug("Unexpected response from SIM: \"%s\"", pinstr); } } /* wait for a second before retry */ wait_ms(1000); } if (done) { tr_info("SIM Ready."); } else { tr_error("SIM not ready."); } UNLOCK(); return success; } /********************************************************************** * PUBLIC METHODS **********************************************************************/ // Initialise the modem. bool UbloxCellularBase::init(const char *pin) { int x; MBED_ASSERT(_at != NULL); if (!_modem_initialised) { if (power_up()) { tr_info("Modem Ready."); if (pin != NULL) { _pin = pin; } #ifdef TARGET_UBLOX_C027 if (set_functionality_mode(FUNC_MIN)) { #else if (set_functionality_mode(FUNC_AIRPLANE)) { #endif if (initialise_sim_card()) { int mno_profile; #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 #ifdef TARGET_UBLOX_C030_R41XM if (_at->is_idle_mode_enabled() == false) { set_idle_mode(false); //disable idle mode at start up } if(_edrx_configured == false) { // A special form of the command can be given as +CEDRXS=3. // In this form, eDRX will be disabled and data for all parameters in the command +CEDRXS will be removed or, // if available, set to the manufacturer specific default values. set_receive_period(3,UbloxCellularBase::EDRXGSM_A_Gb_mode); set_receive_period(3,UbloxCellularBase::EDRXEUTRAN_WB_S1_mode); set_receive_period(3,UbloxCellularBase::EDRXEUTRAN_NB_S1_mode); } get_receive_period(); if (get_mno_profile(&mno_profile)) tr_info("Current MNO profile is: %d", mno_profile); #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 if (get_iccid()) { // Try a few times to get the IMSI (since on some modems this can // take a while to be retrieved, especially if a SIM PIN // was set) for (x = 0; (x < 3) && !get_imsi(); x++) { wait_ms(1000); } if (x < 3) { // If we got the IMSI, can get the others if (get_imei() && // Get international mobile equipment identifier get_meid() && // Probably the same as the IMEI set_sms()) { // And set up SMS // The modem is initialised. _modem_initialised = true; tr_info("Modem initialized"); } } } } } } } } return _modem_initialised; } // Perform registration. bool UbloxCellularBase::nwk_registration() { bool atSuccess = false; bool registered = false; int status; int at_timeout; LOCK(); at_timeout = _at_timeout; // Has to be inside LOCK()s MBED_ASSERT(_at != NULL); if (!is_registered_psd() && !is_registered_csd() && !is_registered_eps()) { if (set_functionality_mode(FUNC_FULL)) { tr_info("Searching Network..."); // Enable the packet switched and network registration unsolicited result codes if (_at->send("AT+CREG=1") && _at->recv("OK") && _at->send("AT+CGREG=1") && _at->recv("OK")) { atSuccess = true; if (_at->send("AT+CEREG=1")) { _at->recv("OK"); // Don't check return value as this works for LTE only } if (atSuccess) { // See if we are already in automatic mode if (_at->send("AT+COPS?") && _at->recv("+COPS: %d", &status) && _at->recv("OK")) { // If not, set it if (status != 0) { // Don't check return code here as there's not much // we can do if this fails. _at->send("AT+COPS=0") && _at->recv("OK"); } } // Query the registration status directly as well, // just in case if (_at->send("AT+CREG?") && _at->recv("OK")) { // Answer will be processed by URC } if (_at->send("AT+CGREG?") && _at->recv("OK")) { // Answer will be processed by URC } if (_at->send("AT+CEREG?")) { _at->recv("OK"); // Don't check return value as this works for LTE only } } } // Wait for registration to succeed at_set_timeout(1000); for (int waitSeconds = 0; !registered && (waitSeconds < 180); waitSeconds++) { registered = is_registered_psd() || is_registered_csd() || is_registered_eps(); _at->recv(UNNATURAL_STRING); } at_set_timeout(at_timeout); if (registered) { // This should return quickly but sometimes the status field is not returned // so make the timeout short at_set_timeout(1000); if (_at->send("AT+COPS?") && _at->recv("+COPS: %*d,%*d,\"%*[^\"]\",%d\n", &status)) { set_rat(status); } at_set_timeout(at_timeout); } } } else { registered = true; } UNLOCK(); return registered; } bool UbloxCellularBase::is_registered_csd() { return (_dev_info.reg_status_csd == CSD_REGISTERED) || (_dev_info.reg_status_csd == CSD_REGISTERED_ROAMING) || (_dev_info.reg_status_csd == CSD_CSFB_NOT_PREFERRED); } bool UbloxCellularBase::is_registered_psd() { return (_dev_info.reg_status_psd == PSD_REGISTERED) || (_dev_info.reg_status_psd == PSD_REGISTERED_ROAMING); } bool UbloxCellularBase::is_registered_eps() { return (_dev_info.reg_status_eps == EPS_REGISTERED) || (_dev_info.reg_status_eps == EPS_REGISTERED_ROAMING); } // Perform deregistration. bool UbloxCellularBase::nwk_deregistration() { bool success = false; LOCK(); MBED_ASSERT(_at != NULL); int at_timeout = _at_timeout; // Has to be inside LOCK()s at_set_timeout(3*60*1000); //command has 3 minutes timeout if (_at->send("AT+COPS=2") && _at->recv("OK")) { _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; success = true; } at_set_timeout(at_timeout); UNLOCK(); return success; } // Put the modem into its lowest power state. void UbloxCellularBase::deinit() { power_down(); _modem_initialised = false; } // Set the PIN. void UbloxCellularBase::set_pin(const char *pin) { _pin = pin; } // Enable or disable SIM pin checking. bool UbloxCellularBase::sim_pin_check_enable(bool enableNotDisable) { bool success = false;; LOCK(); MBED_ASSERT(_at != NULL); if (_pin != NULL) { if (_sim_pin_check_enabled && !enableNotDisable) { // Disable the SIM lock if (_at->send("AT+CLCK=\"SC\",0,\"%s\"", _pin) && _at->recv("OK")) { _sim_pin_check_enabled = false; success = true; } } else if (!_sim_pin_check_enabled && enableNotDisable) { // Enable the SIM lock if (_at->send("AT+CLCK=\"SC\",1,\"%s\"", _pin) && _at->recv("OK")) { _sim_pin_check_enabled = true; success = true; } } else { success = true; } } UNLOCK(); return success; } // Change the pin code for the SIM card. bool UbloxCellularBase::change_sim_pin(const char *pin) { bool success = false;; LOCK(); MBED_ASSERT(_at != NULL); // Change the SIM pin if ((pin != NULL) && (_pin != NULL)) { if (_at->send("AT+CPWD=\"SC\",\"%s\",\"%s\"", _pin, pin) && _at->recv("OK")) { _pin = pin; success = true; } } UNLOCK(); return success; } // Get the IMEI. bool UbloxCellularBase::get_imei(char *imei_to_send, int size) { bool success; LOCK(); MBED_ASSERT(_at != NULL); // International mobile equipment identifier // AT Command Manual UBX-13002752, section 4.7 success = _at->send("AT+CGSN") && _at->recv("%15[^\n]\nOK\n", _dev_info.imei); tr_info("DevInfo: IMEI=%s", _dev_info.imei); if (success) { memcpy(imei_to_send,_dev_info.imei,size); imei_to_send[size-1] = '\0'; } UNLOCK(); return success; } // Get the IMEI of the module. const char *UbloxCellularBase::imei() { return _dev_info.imei; } // Get the Mobile Equipment ID (which may be the same as the IMEI). const char *UbloxCellularBase::meid() { return _dev_info.meid; } // Get the IMSI of the SIM. const char *UbloxCellularBase::imsi() { // (try) to update the IMSI, just in case the SIM has changed get_imsi(); return _dev_info.imsi; } // Get the ICCID of the SIM. const char *UbloxCellularBase::iccid() { // (try) to update the ICCID, just in case the SIM has changed get_iccid(); return _dev_info.iccid; } // Get the RSSI in dBm. int UbloxCellularBase::rssi() { char buf[7] = {0}; int rssi = 0; int qual = 0; int rssiRet = 0; bool success; LOCK(); MBED_ASSERT(_at != NULL); success = _at->send("AT+CSQ") && _at->recv("+CSQ: %6[^\n]\nOK\n", buf); if (success) { if (sscanf(buf, "%d,%d", &rssi, &qual) == 2) { // AT+CSQ returns a coded RSSI value and an RxQual value // For 2G an RSSI of 0 corresponds to -113 dBm or less, // an RSSI of 31 corresponds to -51 dBm or less and hence // each value is a 2 dB step. // For LTE the mapping is defined in the array rssiConvertLte[]. // For 3G the mapping to RSCP is defined in the array rscpConvert3G[] // and the RSSI value is then RSCP - the EC_NO_LEV number derived // by putting the qual number through qualConvert3G[]. if ((rssi >= 0) && (rssi <= 31)) { switch (_dev_info.rat) { case UTRAN: case HSDPA: case HSUPA: case HSDPA_HSUPA: // 3G if ((qual >= 0) && (qual <= 7)) { qual = qualConvert3G[qual]; rssiRet = rscpConvert3G[rssi]; rssiRet -= qual; } break; case LTE: // LTE rssiRet = rssiConvertLte[rssi]; break; case GSM: case COMPACT_GSM: case EDGE: default: // GSM or assumed GSM if the RAT is not known rssiRet = -(113 - (rssi << 2)); break; } } } } UNLOCK(); return rssiRet; } //RAT should be set in a detached state (AT+COPS=2) bool UbloxCellularBase::set_modem_rat(RAT selected_rat, RAT preferred_rat, RAT second_preferred_rat) { bool success = false; char command[16] = {0x00}; //check if modem is registered with network if (is_registered_csd() || is_registered_psd() || is_registered_eps()) { tr_error("RAT should only be set in detached state"); return false; } if (preferred_rat != NOT_USED && second_preferred_rat != NOT_USED) { sprintf(command, "AT+URAT=%d,%d,%d", selected_rat, preferred_rat, second_preferred_rat); } else if (preferred_rat != NOT_USED) { sprintf(command, "AT+URAT=%d,%d", selected_rat, preferred_rat); } else if (second_preferred_rat != NOT_USED) { sprintf(command, "AT+URAT=%d,%d", selected_rat, second_preferred_rat); } else { sprintf(command, "AT+URAT=%d", selected_rat); } LOCK(); if (_at->send(command) && _at->recv("OK")) { success = true; } else { tr_error("unable to set the specified RAT"); success = false; } UNLOCK(); return success; } bool UbloxCellularBase::get_modem_rat(int *selected_rat, int *preferred_rat, int *second_preferred_rat) { bool success = false; char buf[24] = {0x00}; if (selected_rat == NULL || preferred_rat == NULL || second_preferred_rat == NULL) { tr_info("invalid pointers"); return false; } MBED_ASSERT(_at != NULL); *selected_rat = NOT_USED; *preferred_rat = NOT_USED; *second_preferred_rat = NOT_USED; LOCK(); if (_at->send("AT+URAT?") && _at->recv("%23[^\n]\nOK\n", buf)) { if (sscanf(buf, "+URAT: %d,%d,%d", selected_rat, preferred_rat, second_preferred_rat) == 3) { success = true; } else if (sscanf(buf, "+URAT: %d,%d", selected_rat, preferred_rat) == 2) { success = true; } else if (sscanf(buf, "+URAT: %d", selected_rat) == 1) { success = true; } } UNLOCK(); return success; } //application should call init() or connect() in order to initialize the modem bool UbloxCellularBase::reboot_modem() { return (set_functionality_mode(FUNC_RESET)); } bool UbloxCellularBase::set_functionality_mode(FunctionalityMode mode) { bool return_val = false; int at_timeout; LOCK(); MBED_ASSERT(_at != NULL); at_timeout = _at_timeout; // Has to be inside LOCK()s at_set_timeout(3*60*1000); //command has 3 minutes timeout if (_at->send("AT+CFUN=%d", mode) && _at->recv("OK")) { return_val = true; } if (mode == FUNC_RESET || mode == FUNC_RESET_WITH_SIM) { _modem_initialised = false; } at_set_timeout(at_timeout); UNLOCK(); return return_val; } bool UbloxCellularBase::get_functionality_mode(int *mode) { bool return_val = false; if (mode == NULL) { return false; } LOCK(); MBED_ASSERT(_at != NULL); if ( (_at->send("AT+CFUN?") && _at->recv("+CFUN: %d", mode) && _at->recv("OK")) ) { return_val = true; } UNLOCK(); return return_val; } #ifdef TARGET_UBLOX_C030_R41XM bool UbloxCellularBase::set_mno_profile(MNOProfile profile) { bool return_val = false; int mno_profile; if (get_mno_profile(&mno_profile)) { tr_info("Current MNO profile is: %d", mno_profile); if (mno_profile != profile) { if (is_registered_csd() || is_registered_psd() || is_registered_eps()) { tr_error("MNO profile should only be set in detached state"); return false; } LOCK(); if (_at->send("AT+UMNOPROF=%d", profile) && _at->recv("OK")) { return_val = true; } else { tr_error("unable to set specified profile"); } UNLOCK(); } else { return_val = true; } } else { tr_error("could not read MNO profile"); } return return_val; } bool UbloxCellularBase::get_mno_profile(int *profile) { bool return_val = false; if (profile == NULL) { return false; } LOCK(); MBED_ASSERT(_at != NULL); if ( (_at->send("AT+UMNOPROF?") && _at->recv("+UMNOPROF: %d", profile) && _at->recv("OK")) ) { return_val = true; } UNLOCK(); return return_val; } // Enable or Disable the UPSV power saving mode bool UbloxCellularBase::set_idle_mode(bool enable) { #ifdef TARGET_UBLOX_C030_R412M if (_psm_status == true && enable == true) { return false; } #endif bool success = false; LOCK(); MBED_ASSERT(_at != NULL); if (_at->send("AT+UPSV=%d", enable ? 4 : 0) && _at->recv("OK")) { if (enable == true) { _at->idle_mode_enabled(); } else { _at->idle_mode_disabled(); } success = true; } UNLOCK(); return success; } bool UbloxCellularBase::get_idle_mode(int *status) { bool return_val = false; if (status == NULL) { return false; } LOCK(); MBED_ASSERT(_at != NULL); if ( (_at->send("AT+UPSV?") && _at->recv("+UPSV: %d", status) && _at->recv("OK")) ) { if (*status == 4) { *status = 1; } return_val = true; } UNLOCK(); 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; if (status == NULL || periodic_time == NULL || active_time == NULL) { return false; } 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) { if (_at->is_idle_mode_enabled() == true && periodic_time != 0 && active_time != 0 ) { return false; } 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++; } } } uint32_t UbloxCellularBase::binary_str_to_uint(const char *binary_string, int binary_string_length) { if (!binary_string || !binary_string_length) { return 0; } int integer_output = 0, base_exp = 1; for (int i = binary_string_length - 1; i >= 0; i--) { if (binary_string[i] == '1') { integer_output += (base_exp << (binary_string_length - (i+1))); } } return integer_output; } bool UbloxCellularBase::is_modem_awake() { return (_dev_info.modem_psm_state == AWAKE); } #ifdef TARGET_UBLOX_C030_R41XM int UbloxCellularBase::set_receive_period(int mode, tEDRXAccessTechnology act_type, uint8_t edrx_value) { char edrx[5]; uint_to_binary_str(edrx_value, edrx, 5, 4); edrx[4] = '\0'; int status = 1; LOCK(); if (_at->send("AT+CEDRXS=%d,%d,\"%s\"", mode, act_type, edrx) && _at->recv("OK")) { _edrx_configured = true; status = 0; } else { status = 1; } UNLOCK(); return status; } int UbloxCellularBase::set_receive_period(int mode, tEDRXAccessTechnology act_type) { int status = 1; LOCK(); if (_at->send("AT+CEDRXS=%d,%d", mode, act_type) && _at->recv("OK")) { status = 0; } else { status = 1; } UNLOCK(); return status; } int UbloxCellularBase::set_receive_period(int mode) { int status = 1; LOCK(); if (_at->send("AT+CEDRXS=%d", mode) && _at->recv("OK")) { status = 0; } else { status = 1; } UNLOCK(); return status; } uint32_t UbloxCellularBase::get_receive_period() { uint32_t edrx_value = 2; char buf[24] = {0x00}; char edrx_val[5]; tEDRXAccessTechnology act_type; LOCK(); if (_at->send("AT+CEDRXS?") && _at->recv("%23[^\n]\nOK\n", buf)) { if (sscanf(buf, "+CEDRXS: %d,\"%s\"", (int *)&act_type, edrx_val) == 2) { edrx_value = binary_str_to_uint(edrx_val,4); } } if (_at->send("AT+CEDRXRDP") && _at->recv("OK")) { } tr_info("edrx_value. %d", edrx_value); UNLOCK(); return edrx_value; } #endif //TARGET_UBLOX_C030_R41XM //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