ublox-cellular-base-n2xx
UbloxCellularBaseN2xx.cpp
- Committer:
- philware
- Date:
- 2017-06-26
- Revision:
- 1:d4ff95ab40ae
- Child:
- 3:39eadc84c5ac
File content as of revision 1:d4ff95ab40ae:
/* Copyright (c) 2017 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 "UbloxCellularBaseN2xx.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 #define ATOK _at->recv("OK") /********************************************************************** * PRIVATE METHODS **********************************************************************/ void UbloxCellularBaseN2xx::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 UbloxCellularBaseN2xx::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 UbloxCellularBaseN2xx::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); } void UbloxCellularBaseN2xx::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; default: tr_info("Unknown RAT %d", AcTStatus); break; } _dev_info.rat = static_cast<RadioAccessNetworkType>(AcTStatus); } bool UbloxCellularBaseN2xx::get_sara_n2xx_info() { return ( cgmi(_sara_n2xx_info.cgmi) && cgmm(_sara_n2xx_info.cgmm) && cgmr(_sara_n2xx_info.cgmr) && cgsn(1, _sara_n2xx_info.cgsn) ); } bool UbloxCellularBaseN2xx::at_req(const char *cmd, const char *recvFormat, const char *response) { bool success = false; LOCK(); MBED_ASSERT(_at != NULL); tr_debug("ATREQ: %s => %s", cmd, recvFormat); if(_at->send(cmd) && _at->recv(recvFormat, response) && ATOK) { tr_debug("ATRESULT: %s", response); success = true; } else { tr_error("ATRESULT: No Answer!"); } UNLOCK(); return success; } bool UbloxCellularBaseN2xx::at_req(const char *cmd, const char *recvFormat, int *response) { bool success = false; LOCK(); MBED_ASSERT(_at != NULL); tr_debug("ATREQ: %s => %s", cmd, recvFormat); if(_at->send(cmd) && _at->recv(recvFormat, response) && ATOK) { tr_debug("ATRESULT: %d", *response); success = true; } else { tr_error("ATRESULT: No Answer!"); } UNLOCK(); return success; } bool UbloxCellularBaseN2xx::at_send(const char *cmd) { bool success = false; LOCK(); MBED_ASSERT(_at != NULL); tr_debug("ATSEND: %s", cmd); if(_at->send(cmd) && ATOK) { success = true; } else { tr_error("Failed to send %s", cmd); } UNLOCK(); return success; } bool UbloxCellularBaseN2xx::at_send(const char *cmd, int n) { bool success = false; LOCK(); MBED_ASSERT(_at != NULL); tr_debug("ATSEND: %s, %d", cmd, n); if(_at->send(cmd, n) && ATOK) { success = true; } else { tr_error("Failed to send %s,%d", cmd, n); } UNLOCK(); return success; } bool UbloxCellularBaseN2xx::at_send(const char *cmd, const char *arg) { bool success = false; LOCK(); MBED_ASSERT(_at != NULL); tr_debug("ATSEND: %s,%s", cmd, arg); if(_at->send(cmd, arg) && ATOK) { success = true; } else { tr_error("Failed to send %s,%s", cmd, arg); } UNLOCK(); return success; } bool UbloxCellularBaseN2xx::cgmi(const char *response) { return at_req("AT+CGMI", "%32[^\n]\n", response); } bool UbloxCellularBaseN2xx::cgmm(const char *response) { return at_req("AT+CGMM", "%32[^\n]\n", response); } bool UbloxCellularBaseN2xx::cimi(const char *response) { return at_req("AT+CIMI", "%32[^\n]\n", response); } bool UbloxCellularBaseN2xx::ccid(const char *response) { return at_req("AT+NCCID", "+NCCID:%32[^\n]\n", response); } bool UbloxCellularBaseN2xx::cgmr(const char *response) { return at_req("AT+CGMR", "%32[^\n]\n", response); } bool UbloxCellularBaseN2xx::cgsn(int snt, const char *response) { char cmd[10]; sprintf(cmd, "AT+CGSN=%d", snt); return at_req(cmd, "+CGSN:%32[^\n]\n", response); } bool UbloxCellularBaseN2xx::cereg(int n) { return at_send("AT+CEREG=%d", n); } nsapi_error_t UbloxCellularBaseN2xx::get_cereg() { nsapi_error_t r = NSAPI_ERROR_DEVICE_ERROR; LOCK(); MBED_ASSERT(_at != NULL); // The response will be handled by the CEREG URC, by waiting for the OK we know it has been serviced. if (at_send("AT+CEREG?")){ r = _dev_info.reg_status_eps; } UNLOCK(); return r; } nsapi_error_t UbloxCellularBaseN2xx::get_cscon() { char resp[3+1]; int n, stat; if (at_req("AT+CSCON?", "+CSCON:%3[^\n]\n", resp) && sscanf(resp, "%d,%d", &n, &stat)) { return stat; } return NSAPI_ERROR_DEVICE_ERROR; } nsapi_error_t UbloxCellularBaseN2xx::get_csq() { char resp[5+1]; nsapi_error_t rssi = NSAPI_ERROR_DEVICE_ERROR; LOCK(); MBED_ASSERT(_at != NULL); if (at_req("AT+CSQ", "+CSQ:%5[^\n]\n", resp) && sscanf(resp, "%d,%*d", &rssi)) { return rssi; } UNLOCK(); return rssi; } bool UbloxCellularBaseN2xx::cops(const char *plmn) { return at_send("AT+COPS=1,2,\"%s\"", plmn); } bool UbloxCellularBaseN2xx::cops(int mode) { return at_send("AT+COPS=%d", mode); } bool UbloxCellularBaseN2xx::get_cops(int *status) { return at_req("AT+COPS?", "+COPS: %d", status); } bool UbloxCellularBaseN2xx::cfun(int mode) { return at_send("AT+CFUN=%d", mode); } bool UbloxCellularBaseN2xx::reboot() { return at_send("AT+NRB"); } bool UbloxCellularBaseN2xx::auto_connect(bool state) { return nconfig("AUTOCONNECT", state); } bool UbloxCellularBaseN2xx::nconfig(const char *name, bool state) { char n[50]; if (state) sprintf(n, "AT+NCONFIG=%s,TRUE", name); else sprintf(n, "AT+NCONFIG=%s,FALSE", name); return at_send(n); } bool UbloxCellularBaseN2xx::get_iccid() { // 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 bool success = ccid(_dev_info.iccid); tr_info("DevInfo: ICCID=%s", _dev_info.iccid); return success; } bool UbloxCellularBaseN2xx::get_imsi() { // International mobile subscriber identification // AT Command Manual UBX-13002752, section 4.11 bool success = cimi(_dev_info.imsi); tr_info("DevInfo: IMSI=%s", _dev_info.imsi); return success; } bool UbloxCellularBaseN2xx::get_imei() { // International mobile equipment identifier // AT Command Manual UBX-13002752, section 4.7 bool success = cgsn(1, _dev_info.imei); tr_info("DevInfo: IMEI=%s", _dev_info.imei); return success; } bool UbloxCellularBaseN2xx::get_meid() { // *** NOT IMPLEMENTED return false; 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 UbloxCellularBaseN2xx::set_sms() { // *** NOT IMPLEMENTED return false; 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 UbloxCellularBaseN2xx::parser_abort_cb() { _at->abort(); } // Callback for CME ERROR and CMS ERROR. void UbloxCellularBaseN2xx::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 EPS registration URC. void UbloxCellularBaseN2xx::CEREG_URC() { char buf[20]; int status; int n, AcT; char tac[4], ci[4]; // 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() // We also hanlde the extended 4 or 5 argument // response if cereg is set to 2. if (read_at_to_newline(buf, sizeof (buf)) > 0) { if (sscanf(buf, ":%d,%d,%[0123456789abcdef],%[0123456789abcdef],%d\n", &n, &status, tac, ci, &AcT) == 5) { set_nwk_reg_status_eps(status); } else if (sscanf(buf, ":%d,%[0123456789abcdef],%[0123456789abcdef],%d\n`", &status, tac, ci, &AcT) == 4) { set_nwk_reg_status_eps(status); } else if (sscanf(buf, ":%d,%d\n", &n, &status) == 2) { set_nwk_reg_status_eps(status); } else if (sscanf(buf, ":%d\n", &status) == 1) { set_nwk_reg_status_eps(status); } } } /********************************************************************** * PROTECTED METHODS **********************************************************************/ #if MODEM_ON_BOARD void UbloxCellularBaseN2xx::modem_init() { ::onboard_modem_init(); } void UbloxCellularBaseN2xx::modem_deinit() { ::onboard_modem_deinit(); } void UbloxCellularBaseN2xx::modem_power_up() { ::onboard_modem_power_up(); } void UbloxCellularBaseN2xx::modem_power_down() { ::onboard_modem_power_down(); } #else void UbloxCellularBaseN2xx::modem_init() { // Meant to be overridden #error need to do something here! } void UbloxCellularBaseN2xx::modem_deinit() { // Meant to be overridden } void UbloxCellularBaseN2xx::modem_power_up() { // Meant to be overridden } void UbloxCellularBaseN2xx::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. UbloxCellularBaseN2xx::UbloxCellularBaseN2xx() { tr_debug("UbloxATCellularBaseN2xx Constructor"); _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; } // Destructor. UbloxCellularBaseN2xx::~UbloxCellularBaseN2xx() { deinit(); delete _at; delete _fh; } // Initialise the portions of this class that are parameterised. void UbloxCellularBaseN2xx::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; } // Set up File Handle for buffered serial comms with cellular module // (which will be used by the AT parser) _fh = new UARTSerial(tx, rx, baud); // Set up the AT parser _at = new ATCmdParser(_fh, OUTPUT_ENTER_KEY, AT_PARSER_BUFFER_SIZE, _at_timeout, _debug_trace_on); // Error cases, out of band handling _at->oob("ERROR", callback(this, &UbloxCellularBaseN2xx::parser_abort_cb)); _at->oob("+CME ERROR", callback(this, &UbloxCellularBaseN2xx::CMX_ERROR_URC)); _at->oob("+CMS ERROR", callback(this, &UbloxCellularBaseN2xx::CMX_ERROR_URC)); // Registration status, out of band handling _at->oob("+CEREG", callback(this, &UbloxCellularBaseN2xx::CEREG_URC)); } } // Set the AT parser timeout. // Note: the AT interface should be locked before this is called. void UbloxCellularBaseN2xx::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 UbloxCellularBaseN2xx::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 UbloxCellularBaseN2xx::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..."); onboard_modem_init(); /* Give SARA-N2XX time to reset */ tr_debug("Waiting for 5 seconds (booting SARA-N2xx)..."); wait_ms(5000); at_set_timeout(1000); for (int retry_count = 0; !success && (retry_count < 20); retry_count++) { _at->flush(); if (at_send("AT")) { success = true; } } at_set_timeout(at_timeout); // perform any initialisation AT commands here if (success) { success = at_send("AT+CMEE=1"); // Turn on verbose responses } if (!success) { tr_error("Preliminary modem setup failed."); } UNLOCK(); return success; } // Power down modem via AT interface. void UbloxCellularBaseN2xx::power_down() { LOCK(); MBED_ASSERT(_at != NULL); // If we have been running, do a soft power-off first if (_modem_initialised && (_at != NULL)) { // NOT IMPLEMENTED in B656 firmware // at_send("AT+CPWROFF"); } // Now do a hard power-off onboard_modem_power_down(); onboard_modem_deinit(); _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 UbloxCellularBaseN2xx::set_device_identity(DeviceType *dev) { char buf[20]; bool success; LOCK(); MBED_ASSERT(_at != NULL); success = at_req("AT+CGMM", "%19[^\n]\n", buf); if (success) { if (strstr(buf, "Neul Hi2110")) *dev = DEV_SARA_N2; } UNLOCK(); return success; } // Send initialisation AT commands that are specific to the device. bool UbloxCellularBaseN2xx::device_init(DeviceType dev) { // SARA-N2xx doesn't have anything to initialise return true; } // Get the SIM card going. bool UbloxCellularBaseN2xx::initialise_sim_card() { // SARA-N2XX doesn't have any SIM AT Commands for now. return true; } /********************************************************************** * PUBLIC METHODS **********************************************************************/ // Initialise the modem. bool UbloxCellularBaseN2xx::init(const char *pin) { MBED_ASSERT(_at != NULL); if (!_modem_initialised) { if (power_up()) { tr_info("Modem Ready."); if (pin != NULL) { _pin = pin; } if (initialise_sim_card()) { if (set_device_identity(&_dev_info.dev) && // Set up device identity device_init(_dev_info.dev) && get_sara_n2xx_info()) { tr_debug("CGMM: %s", _sara_n2xx_info.cgmm); tr_debug("CGMI: %s", _sara_n2xx_info.cgmi); tr_debug("CGMR: %s", _sara_n2xx_info.cgmr); tr_debug("CGSN: %s", _sara_n2xx_info.cgsn); // The modem is initialised. The following checks my still fail, // of course, but they are all of a "fatal" nature and so we wouldn't // want to retry them anyway _modem_initialised = true; } } } } return _modem_initialised; } // Perform registration. bool UbloxCellularBaseN2xx::nwk_registration() { bool registered = false; int status; int at_timeout; LOCK(); at_timeout = _at_timeout; // Has to be inside LOCK()s MBED_ASSERT(_at != NULL); // Enable the packet switched and network registration unsolicited result codes if (cereg(1)) { // See if we are already in automatic mode if (get_cops(&status)) { if (status != 0) { // Don't check return code here as there's not much // we can do if this fails. cops(0); } } // query cereg just in case get_cereg(); registered = is_registered_eps(); at_set_timeout(1000); for (int waitSeconds = 0; !registered && (waitSeconds < 180); waitSeconds++) { _at->recv(UNNATURAL_STRING); registered = is_registered_eps(); } at_set_timeout(at_timeout); } else { tr_error("Failed to set CEREG=1"); } UNLOCK(); return registered; } bool UbloxCellularBaseN2xx::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 UbloxCellularBaseN2xx::is_registered_psd() { return (_dev_info.reg_status_psd == PSD_REGISTERED) || (_dev_info.reg_status_psd == PSD_REGISTERED_ROAMING); } bool UbloxCellularBaseN2xx::is_registered_eps() { return (_dev_info.reg_status_eps == EPS_REGISTERED) || (_dev_info.reg_status_eps == EPS_REGISTERED_ROAMING); } // Perform deregistration. bool UbloxCellularBaseN2xx::nwk_deregistration() { bool success = false; MBED_ASSERT(_at != NULL); if (cops(2)) { // we need to wait here so that the internal status of the module wait_ms(1000); _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; } else { tr_error("Failed to set COPS=2"); } return success; } // Put the modem into its lowest power state. void UbloxCellularBaseN2xx::deinit() { power_down(); _modem_initialised = false; } // Set the PIN. void UbloxCellularBaseN2xx::set_pin(const char *pin) { _pin = pin; } // Enable or disable SIM pin checking. bool UbloxCellularBaseN2xx:: sim_pin_check_enable(bool enableNotDisable) { // *** NOT IMPLEMENTED on SARA-N2XX return false; 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 UbloxCellularBaseN2xx::change_sim_pin(const char *pin) { // *** NOT IMPLEMENTED on SARA-N2XX return false; 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; } // Read up to size bytes from the AT interface up to a newline. // This doesn't need a LOCK() UNLOCK() Wrapping as it's only called // from the URC function, which are already in a lock int UbloxCellularBaseN2xx::read_at_to_newline(char * buf, int size) { int count = 0; int x = 0; if (size > 0) { for (count = 0; (count < size) && (x >= 0) && (x != '\n'); count++) { x = _at->getc(); *(buf + count) = (char) x; } *(buf + count - 1) = 0; count--; } return count; } // End of File