/* * Copyright (c) 2018, Arm Limited and affiliates. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "AT_CellularContext.h" #include "AT_CellularNetwork.h" #include "AT_CellularStack.h" #include "CellularLog.h" #include "CellularUtil.h" #include "CellularSIM.h" #include "UARTSerial.h" #include "mbed_wait_api.h" #define NETWORK_TIMEOUT 30 * 60 * 1000 // 30 minutes #define DEVICE_TIMEOUT 5 * 60 * 1000 // 5 minutes #if NSAPI_PPP_AVAILABLE #define AT_SYNC_TIMEOUT 1000 // 1 second timeout #include "nsapi_ppp.h" #endif #define USE_APN_LOOKUP (MBED_CONF_CELLULAR_USE_APN_LOOKUP || (NSAPI_PPP_AVAILABLE && MBED_CONF_PPP_CELL_IFACE_APN_LOOKUP)) #if USE_APN_LOOKUP #include "APN_db.h" #endif //USE_APN_LOOKUP using namespace mbed_cellular_util; using namespace mbed; AT_CellularContext::AT_CellularContext(ATHandler &at, CellularDevice *device, const char *apn) : AT_CellularBase(at), _ip_stack_type_requested(DEFAULT_STACK), _is_connected(false), _is_blocking(true), _current_op(OP_INVALID), _device(device), _nw(0), _fh(0) { tr_info("New CellularContext %s (%p)", apn ? apn : "", this); _stack = NULL; _ip_stack_type = DEFAULT_STACK; _authentication_type = CellularContext::CHAP; _connect_status = NSAPI_STATUS_DISCONNECTED; _is_context_active = false; _is_context_activated = false; _apn = apn; _uname = NULL; _pwd = NULL; _status_cb = NULL; _cid = -1; _new_context_set = false; _next = NULL; } AT_CellularContext::~AT_CellularContext() { tr_info("Delete CellularContext %s (%p)", _apn ? _apn : "", this); (void)disconnect(); if (_nw) { _device->close_network(); } } void AT_CellularContext::set_file_handle(FileHandle *fh) { tr_info("CellularContext filehandle %p", fh); _fh = fh; _at.set_file_handle(_fh); } nsapi_error_t AT_CellularContext::connect() { tr_info("CellularContext connect"); if (_is_connected) { return NSAPI_ERROR_IS_CONNECTED; } nsapi_error_t err = _device->attach_to_network(); _cb_data.error = check_operation(err, OP_CONNECT); if (_is_blocking) { if (_cb_data.error == NSAPI_ERROR_OK || _cb_data.error == NSAPI_ERROR_ALREADY) { do_connect(); } } else { if (_cb_data.error == NSAPI_ERROR_ALREADY) { // device is already attached, to be async we must use queue to connect and give proper callbacks int id = _device->get_queue()->call_in(0, this, &AT_CellularContext::do_connect); if (id == 0) { return NSAPI_ERROR_NO_MEMORY; } return NSAPI_ERROR_OK; } } return _cb_data.error; } nsapi_error_t AT_CellularContext::set_device_ready() { nsapi_error_t err = _device->set_device_ready(); return check_operation(err, OP_DEVICE_READY); } nsapi_error_t AT_CellularContext::set_sim_ready() { nsapi_error_t err = _device->set_sim_ready(); return check_operation(err, OP_SIM_READY); } nsapi_error_t AT_CellularContext::register_to_network() { nsapi_error_t err = _device->register_to_network(); return check_operation(err, OP_REGISTER); } nsapi_error_t AT_CellularContext::attach_to_network() { nsapi_error_t err = _device->attach_to_network(); return check_operation(err, OP_ATTACH); } nsapi_error_t AT_CellularContext::check_operation(nsapi_error_t err, ContextOperation op) { _current_op = op; if (err == NSAPI_ERROR_IN_PROGRESS || err == NSAPI_ERROR_OK) { if (_is_blocking) { int sema_err = _semaphore.wait(get_timeout_for_operation(op)); // cellular network searching may take several minutes if (sema_err != 1) { tr_warning("No cellular connection"); return NSAPI_ERROR_TIMEOUT; } return NSAPI_ERROR_OK; } } return err; } uint32_t AT_CellularContext::get_timeout_for_operation(ContextOperation op) const { uint32_t timeout = NETWORK_TIMEOUT; // default timeout is 30 minutes as registration and attach may take time if (op == OP_SIM_READY || op == OP_DEVICE_READY) { timeout = DEVICE_TIMEOUT; // use 5 minutes for device ready and sim } return timeout; } bool AT_CellularContext::is_connected() { return _is_connected; } NetworkStack *AT_CellularContext::get_stack() { #if NSAPI_PPP_AVAILABLE // use lwIP/PPP if modem does not have IP stack if (!_stack) { _stack = nsapi_ppp_get_stack(); } #endif return _stack; } const char *AT_CellularContext::get_netmask() { return NULL; } const char *AT_CellularContext::get_gateway() { return NULL; } const char *AT_CellularContext::get_ip_address() { #if NSAPI_PPP_AVAILABLE return nsapi_ppp_get_ip_addr(_at.get_file_handle()); #else if (!_stack) { _stack = get_stack(); } if (_stack) { return _stack->get_ip_address(); } return NULL; #endif } void AT_CellularContext::attach(Callback status_cb) { _status_cb = status_cb; } nsapi_error_t AT_CellularContext::set_blocking(bool blocking) { nsapi_error_t err = NSAPI_ERROR_OK; tr_info("CellularContext set blocking %d", blocking); #if NSAPI_PPP_AVAILABLE err = nsapi_ppp_set_blocking(blocking); #endif _is_blocking = blocking; return err; } void AT_CellularContext::set_plmn(const char *plmn) { tr_info("CellularContext plmn %s", plmn); _device->set_plmn(plmn); } void AT_CellularContext::set_sim_pin(const char *sim_pin) { _device->set_sim_pin(sim_pin); } nsapi_error_t AT_CellularContext::connect(const char *sim_pin, const char *apn, const char *uname, const char *pwd) { set_sim_pin(sim_pin); set_credentials(apn, uname, pwd); return connect(); } void AT_CellularContext::set_credentials(const char *apn, const char *uname, const char *pwd) { _apn = apn; _uname = uname; _pwd = pwd; } bool AT_CellularContext::stack_type_supported(nsapi_ip_stack_t stack_type) { if (stack_type == _ip_stack_type) { return true; } else { return false; } } nsapi_ip_stack_t AT_CellularContext::get_stack_type() { return _ip_stack_type; } nsapi_ip_stack_t AT_CellularContext::string_to_stack_type(const char *pdp_type) { nsapi_ip_stack_t stack = DEFAULT_STACK; int len = strlen(pdp_type); if (len == 6 && memcmp(pdp_type, "IPV4V6", len) == 0) { stack = IPV4V6_STACK; } else if (len == 4 && memcmp(pdp_type, "IPV6", len) == 0) { stack = IPV6_STACK; } else if (len == 2 && memcmp(pdp_type, "IP", len) == 0) { stack = IPV4_STACK; } return stack; } // PDP Context handling nsapi_error_t AT_CellularContext::delete_current_context() { tr_info("Delete context %d", _cid); _at.clear_error(); _at.cmd_start("AT+CGDCONT="); _at.write_int(_cid); _at.cmd_stop_read_resp(); if (_at.get_last_error() == NSAPI_ERROR_OK) { _cid = -1; _new_context_set = false; } return _at.get_last_error(); } nsapi_error_t AT_CellularContext::do_user_authentication() { // if user has defined user name and password we need to call CGAUTH before activating or modifying context if (_pwd && _uname) { if (!is_supported(AT_CGAUTH)) { return NSAPI_ERROR_UNSUPPORTED; } _at.cmd_start("AT+CGAUTH="); _at.write_int(_cid); _at.write_int(_authentication_type); _at.write_string(_uname); _at.write_string(_pwd); _at.cmd_stop_read_resp(); if (_at.get_last_error() != NSAPI_ERROR_OK) { return NSAPI_ERROR_AUTH_FAILURE; } } return NSAPI_ERROR_OK; } bool AT_CellularContext::get_context() { _at.cmd_start("AT+CGDCONT?"); _at.cmd_stop(); _at.resp_start("+CGDCONT:"); _cid = -1; int cid_max = 0; // needed when creating new context char apn[MAX_ACCESSPOINT_NAME_LENGTH]; int apn_len = 0; bool modem_supports_ipv6 = stack_type_supported(IPV6_STACK); bool modem_supports_ipv4 = stack_type_supported(IPV4_STACK); while (_at.info_resp()) { int cid = _at.read_int(); if (cid > cid_max) { cid_max = cid; } char pdp_type_from_context[10]; int pdp_type_len = _at.read_string(pdp_type_from_context, sizeof(pdp_type_from_context) - 1); if (pdp_type_len > 0) { apn_len = _at.read_string(apn, sizeof(apn) - 1); if (apn_len >= 0) { if (_apn && (strcmp(apn, _apn) != 0)) { continue; } nsapi_ip_stack_t pdp_stack = string_to_stack_type(pdp_type_from_context); // Accept dual PDP context for IPv4/IPv6 only modems if (pdp_stack != DEFAULT_STACK && (stack_type_supported(pdp_stack) || pdp_stack == IPV4V6_STACK)) { if (_ip_stack_type_requested == IPV4_STACK) { if (pdp_stack == IPV4_STACK || pdp_stack == IPV4V6_STACK) { _ip_stack_type = _ip_stack_type_requested; _cid = cid; break; } } else if (_ip_stack_type_requested == IPV6_STACK) { if (pdp_stack == IPV6_STACK || pdp_stack == IPV4V6_STACK) { _ip_stack_type = _ip_stack_type_requested; _cid = cid; break; } } else { // requested dual stack or stack is not specified // If dual PDP need to check for IPV4 or IPV6 modem support. Prefer IPv6. if (pdp_stack == IPV4V6_STACK) { if (modem_supports_ipv6) { _ip_stack_type = IPV6_STACK; _cid = cid; break; } else if (modem_supports_ipv4) { _ip_stack_type = IPV4_STACK; _cid = cid; break; } // If PDP is IPV4 or IPV6 they are already checked if supported } else { _ip_stack_type = pdp_stack; _cid = cid; if (pdp_stack == IPV6_STACK) { break; } if (pdp_stack == IPV4_STACK && !modem_supports_ipv6) { break; } } } } } } } _at.resp_stop(); if (_cid == -1) { // no suitable context was found so create a new one if (!set_new_context(cid_max + 1)) { return false; } } // save the apn if (apn_len > 0 && !_apn) { memcpy(_found_apn, apn, apn_len + 1); } tr_info("Found PDP context %d", _cid); return true; } bool AT_CellularContext::set_new_context(int cid) { nsapi_ip_stack_t tmp_stack = _ip_stack_type_requested; if (tmp_stack == DEFAULT_STACK) { bool modem_supports_ipv6 = stack_type_supported(IPV6_STACK); bool modem_supports_ipv4 = stack_type_supported(IPV4_STACK); if (modem_supports_ipv6 && modem_supports_ipv4) { tmp_stack = IPV4V6_STACK; } else if (modem_supports_ipv6) { tmp_stack = IPV6_STACK; } else if (modem_supports_ipv4) { tmp_stack = IPV4_STACK; } } char pdp_type[8 + 1] = {0}; switch (tmp_stack) { case IPV4_STACK: strncpy(pdp_type, "IP", sizeof(pdp_type)); break; case IPV6_STACK: strncpy(pdp_type, "IPV6", sizeof(pdp_type)); break; case IPV4V6_STACK: strncpy(pdp_type, "IPV6", sizeof(pdp_type)); // try first IPV6 and then fall-back to IPv4 break; default: break; } //apn: "If the value is null or omitted, then the subscription value will be requested." bool success = false; _at.cmd_start("AT+CGDCONT="); _at.write_int(cid); _at.write_string(pdp_type); _at.write_string(_apn); _at.cmd_stop_read_resp(); success = (_at.get_last_error() == NSAPI_ERROR_OK); // Fall back to ipv4 if (!success && tmp_stack == IPV4V6_STACK) { _at.clear_error(); tmp_stack = IPV4_STACK; _at.cmd_start("AT+FCLASS=0;+CGDCONT="); _at.write_int(cid); _at.write_string("IP"); _at.write_string(_apn); _at.cmd_stop_read_resp(); success = (_at.get_last_error() == NSAPI_ERROR_OK); } if (success) { _ip_stack_type = tmp_stack; _cid = cid; _new_context_set = true; tr_info("New PDP context %d, stack %s", _cid, pdp_type); } return success; } nsapi_error_t AT_CellularContext::do_activate_context() { _at.lock(); nsapi_error_t err = NSAPI_ERROR_OK; // try to find or create context with suitable stack if (get_context()) { #if NSAPI_PPP_AVAILABLE _at.unlock(); // in PPP we don't activate any context but leave it to PPP stack return err; #else // try to authenticate user before activating or modifying context err = do_user_authentication(); #endif // NSAPI_PPP_AVAILABLE } else { err = NSAPI_ERROR_NO_CONNECTION; } if (err != NSAPI_ERROR_OK) { _at.unlock(); tr_error("Failed to activate network context! (%d)", err); call_network_cb(NSAPI_STATUS_DISCONNECTED); return err; } // do check for stack to validate that we have support for stack if (!get_stack()) { _at.unlock(); tr_error("No cellular stack!"); return NSAPI_ERROR_UNSUPPORTED; } _is_context_active = false; _is_context_activated = false; _at.cmd_start("AT+CGACT?"); _at.cmd_stop(); _at.resp_start("+CGACT:"); while (_at.info_resp()) { int context_id = _at.read_int(); int context_activation_state = _at.read_int(); if (context_id == _cid && context_activation_state == 1) { _is_context_active = true; } } _at.resp_stop(); if (!_is_context_active) { tr_info("Activate PDP context %d", _cid); _at.cmd_start("AT+CGACT=1,"); _at.write_int(_cid); _at.cmd_stop_read_resp(); if (_at.get_last_error() == NSAPI_ERROR_OK) { _is_context_activated = true; } } err = (_at.get_last_error() == NSAPI_ERROR_OK) ? NSAPI_ERROR_OK : NSAPI_ERROR_NO_CONNECTION; // If new PDP context was created and failed to activate, delete it if (err != NSAPI_ERROR_OK && _new_context_set) { delete_current_context(); } else if (err == NSAPI_ERROR_OK) { _is_context_active = true; } _at.unlock(); return err; } void AT_CellularContext::do_connect() { call_network_cb(NSAPI_STATUS_CONNECTING); if (!_is_context_active) { _cb_data.error = do_activate_context(); #if !NSAPI_PPP_AVAILABLE // in PPP mode we did not activate any context, just searched the correct _cid if (_status_cb) { _status_cb((nsapi_event_t)CellularActivatePDPContext, (intptr_t)&_cb_data); } #endif // !NSAPI_PPP_AVAILABLE } if (_cb_data.error != NSAPI_ERROR_OK) { call_network_cb(NSAPI_STATUS_DISCONNECTED); _is_connected = false; return; } #if NSAPI_PPP_AVAILABLE if (_cb_data.error == NSAPI_ERROR_OK) { _at.lock(); _cb_data.error = open_data_channel(); _at.unlock(); if (_cb_data.error != NSAPI_ERROR_OK) { tr_error("Failed to open data channel!"); call_network_cb(NSAPI_STATUS_DISCONNECTED); _is_connected = false; } else { _is_context_activated = true; } } #else _is_connected = true; call_network_cb(NSAPI_STATUS_GLOBAL_UP); #endif } #if NSAPI_PPP_AVAILABLE nsapi_error_t AT_CellularContext::open_data_channel() { tr_info("CellularContext PPP connect"); if (is_supported(AT_CGDATA)) { _at.cmd_start("AT+CGDATA=\"PPP\","); _at.write_int(_cid); } else { MBED_ASSERT(_cid >= 0 && _cid <= 99); _at.cmd_start("ATD*99***"); _at.use_delimiter(false); _at.write_int(_cid); _at.write_string("#", false); _at.use_delimiter(true); } _at.cmd_stop(); _at.resp_start("CONNECT", true); if (_at.get_last_error()) { tr_error("Failed to CONNECT"); return _at.get_last_error(); } _at.set_is_filehandle_usable(false); /* Initialize PPP * If blocking: mbed_ppp_init() is a blocking call, it will block until connected, or timeout after 30 seconds*/ return nsapi_ppp_connect(_at.get_file_handle(), callback(this, &AT_CellularContext::ppp_status_cb), _uname, _pwd, _ip_stack_type); } void AT_CellularContext::ppp_status_cb(nsapi_event_t ev, intptr_t ptr) { tr_debug("ppp_status_cb: event %d, ptr %d", ev, ptr); if (ev == NSAPI_EVENT_CONNECTION_STATUS_CHANGE && ptr == NSAPI_STATUS_GLOBAL_UP) { _is_connected = true; } else { _is_connected = false; } _connect_status = (nsapi_connection_status_t)ptr; // call device's callback, it will broadcast this to here (cellular_callback) _device->cellular_callback(ev, ptr); } #endif //#if NSAPI_PPP_AVAILABLE nsapi_error_t AT_CellularContext::disconnect() { tr_info("CellularContext disconnect"); if (!_nw || !_is_connected) { return NSAPI_ERROR_NO_CONNECTION; } #if NSAPI_PPP_AVAILABLE nsapi_error_t err = nsapi_ppp_disconnect(_at.get_file_handle()); if (err != NSAPI_ERROR_OK) { tr_error("CellularContext disconnect failed!"); // continue even in failure due to ppp disconnect in any case releases filehandle } // after ppp disconnect if we wan't to use same at handler we need to set filehandle again to athandler so it // will set the correct sigio and nonblocking _at.lock(); _at.set_file_handle(_at.get_file_handle()); _at.set_is_filehandle_usable(true); if (!_at.sync(AT_SYNC_TIMEOUT)) { // consume extra characters after ppp disconnect, also it may take a while until modem listens AT commands tr_error("AT sync failed after PPP Disconnect"); } _at.unlock(); #endif // NSAPI_PPP_AVAILABLE _at.lock(); // deactivate a context only if we have activated if (_is_context_activated) { // CGACT and CGATT commands might take up to 3 minutes to respond. _at.set_at_timeout(180 * 1000); _is_context_active = false; size_t active_contexts_count = 0; _at.cmd_start("AT+CGACT?"); _at.cmd_stop(); _at.resp_start("+CGACT:"); while (_at.info_resp()) { int context_id = _at.read_int(); int context_activation_state = _at.read_int(); if (context_activation_state == 1) { active_contexts_count++; if (context_id == _cid) { _is_context_active = true; } } } _at.resp_stop(); CellularNetwork::RadioAccessTechnology rat = CellularNetwork::RAT_GSM; // always return NSAPI_ERROR_OK CellularNetwork::registration_params_t reg_params; _nw->get_registration_params(reg_params); rat = reg_params._act; // 3GPP TS 27.007: // For EPS, if an attempt is made to disconnect the last PDN connection, then the MT responds with ERROR if (_is_context_active && (rat < CellularNetwork::RAT_E_UTRAN || active_contexts_count > 1)) { _at.clear_error(); _at.cmd_start("AT+CGACT=0,"); _at.write_int(_cid); _at.cmd_stop_read_resp(); } if (_new_context_set) { _at.clear_error(); _at.cmd_start("AT+CGDCONT="); _at.write_int(_cid); _at.cmd_stop_read_resp(); } _at.clear_error(); _at.cmd_start("AT+CGATT=0"); _at.cmd_stop_read_resp(); _at.restore_at_timeout(); } _is_connected = false; call_network_cb(NSAPI_STATUS_DISCONNECTED); return _at.unlock_return_error(); } nsapi_error_t AT_CellularContext::get_apn_backoff_timer(int &backoff_timer) { // If apn is set if (_apn) { _at.lock(); _at.cmd_start("AT+CABTRDP="); _at.write_string(_apn); _at.cmd_stop(); _at.resp_start("+CABTRDP:"); if (_at.info_resp()) { _at.skip_param(); backoff_timer = _at.read_int(); } _at.resp_stop(); return _at.unlock_return_error(); } return NSAPI_ERROR_PARAMETER; } nsapi_error_t AT_CellularContext::get_rate_control( CellularContext::RateControlExceptionReports &reports, CellularContext::RateControlUplinkTimeUnit &timeUnit, int &uplinkRate) { _at.lock(); _at.cmd_start("AT+CGAPNRC="); _at.write_int(_cid); _at.cmd_stop(); _at.resp_start("+CGAPNRC:"); _at.read_int(); if (_at.get_last_error() == NSAPI_ERROR_OK) { bool comma_found = true; int next_element = _at.read_int(); if (next_element >= 0) { reports = (RateControlExceptionReports)next_element; next_element = _at.read_int(); } else { comma_found = false; } if (comma_found && next_element >= 0) { timeUnit = (RateControlUplinkTimeUnit)next_element; next_element = _at.read_int(); } else { comma_found = false; } if (comma_found && next_element >= 0) { uplinkRate = next_element; } if (_at.get_last_error() == NSAPI_ERROR_OK) { tr_debug("CGAPNRC: reports %d, time %d, rate %d", reports, timeUnit, uplinkRate); } } _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularContext::get_pdpcontext_params(pdpContextList_t ¶ms_list) { const int ipv6_subnet_size = 128; const int max_ipv6_size = 64; char *ipv6_and_subnetmask = new char[ipv6_subnet_size]; char *temp = new char[max_ipv6_size]; _at.lock(); _at.cmd_start("AT+CGCONTRDP="); _at.write_int(_cid); _at.cmd_stop(); _at.resp_start("+CGCONTRDP:"); pdpcontext_params_t *params = NULL; while (_at.info_resp()) { // response can be zero or many +CGDCONT lines params = params_list.add_new(); params->cid = _at.read_int(); params->bearer_id = _at.read_int(); _at.read_string(params->apn, sizeof(params->apn)); // rest are optional params ipv6_and_subnetmask[0] = '\0'; temp[0] = '\0'; _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); separate_ip_addresses(ipv6_and_subnetmask, params->local_addr, sizeof(params->local_addr), params->local_subnet_mask, sizeof(params->local_subnet_mask)); ipv6_and_subnetmask[0] = '\0'; _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); separate_ip_addresses(ipv6_and_subnetmask, params->gateway_addr, sizeof(params->gateway_addr), temp, max_ipv6_size); prefer_ipv6(params->gateway_addr, sizeof(params->gateway_addr), temp, max_ipv6_size); ipv6_and_subnetmask[0] = '\0'; temp[0] = '\0'; _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); separate_ip_addresses(ipv6_and_subnetmask, params->dns_primary_addr, sizeof(params->dns_primary_addr), temp, max_ipv6_size); prefer_ipv6(params->dns_primary_addr, sizeof(params->dns_primary_addr), temp, max_ipv6_size); ipv6_and_subnetmask[0] = '\0'; temp[0] = '\0'; _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); separate_ip_addresses(ipv6_and_subnetmask, params->dns_secondary_addr, sizeof(params->dns_secondary_addr), temp, max_ipv6_size); prefer_ipv6(params->dns_secondary_addr, sizeof(params->dns_secondary_addr), temp, max_ipv6_size); ipv6_and_subnetmask[0] = '\0'; temp[0] = '\0'; _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); separate_ip_addresses(ipv6_and_subnetmask, params->p_cscf_prim_addr, sizeof(params->p_cscf_prim_addr), temp, max_ipv6_size); prefer_ipv6(params->p_cscf_prim_addr, sizeof(params->p_cscf_prim_addr), temp, max_ipv6_size); ipv6_and_subnetmask[0] = '\0'; temp[0] = '\0'; _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); separate_ip_addresses(ipv6_and_subnetmask, params->p_cscf_sec_addr, sizeof(params->p_cscf_sec_addr), temp, max_ipv6_size); prefer_ipv6(params->p_cscf_sec_addr, sizeof(params->p_cscf_sec_addr), temp, max_ipv6_size); params->im_signalling_flag = _at.read_int(); params->lipa_indication = _at.read_int(); params->ipv4_mtu = _at.read_int(); params->wlan_offload = _at.read_int(); params->local_addr_ind = _at.read_int(); params->non_ip_mtu = _at.read_int(); params->serving_plmn_rate_control_value = _at.read_int(); } _at.resp_stop(); delete [] temp; delete [] ipv6_and_subnetmask; return _at.unlock_return_error(); } // Called by CellularDevice for network and cellular device changes void AT_CellularContext::cellular_callback(nsapi_event_t ev, intptr_t ptr) { if (ev >= NSAPI_EVENT_CELLULAR_STATUS_BASE && ev <= NSAPI_EVENT_CELLULAR_STATUS_END) { cell_callback_data_t *data = (cell_callback_data_t *)ptr; cellular_connection_status_t st = (cellular_connection_status_t)ev; _cb_data.error = data->error; tr_debug("CellularContext: event %d, err %d, data %d", ev, data->error, data->status_data); #if USE_APN_LOOKUP if (st == CellularSIMStatusChanged && data->status_data == CellularSIM::SimStateReady && _cb_data.error == NSAPI_ERROR_OK) { if (!_apn) { char imsi[MAX_IMSI_LENGTH + 1]; wait(1); // need to wait to access SIM in some modems _cb_data.error = _device->open_sim()->get_imsi(imsi); if (_cb_data.error == NSAPI_ERROR_OK) { const char *apn_config = apnconfig(imsi); if (apn_config) { const char *apn = _APN_GET(apn_config); const char *uname = _APN_GET(apn_config); const char *pwd = _APN_GET(apn_config); tr_info("Looked up APN %s", apn); set_credentials(apn, uname, pwd); } } else { tr_error("APN lookup failed"); _device->stop(); if (_is_blocking) { // operation failed, release semaphore _semaphore.release(); } } _device->close_sim(); } } #endif // USE_APN_LOOKUP if (!_nw && st == CellularDeviceReady && data->error == NSAPI_ERROR_OK) { _nw = _device->open_network(_fh); } if (_is_blocking) { if (data->error != NSAPI_ERROR_OK) { // operation failed, release semaphore _semaphore.release(); } else { if ((st == CellularDeviceReady && _current_op == OP_DEVICE_READY) || (st == CellularSIMStatusChanged && _current_op == OP_SIM_READY && data->status_data == CellularSIM::SimStateReady)) { // target reached, release semaphore _semaphore.release(); } else if (st == CellularRegistrationStatusChanged && (data->status_data == CellularNetwork::RegisteredHomeNetwork || data->status_data == CellularNetwork::RegisteredRoaming || data->status_data == CellularNetwork::AlreadyRegistered) && _current_op == OP_REGISTER) { // target reached, release semaphore _semaphore.release(); } else if (st == CellularAttachNetwork && (_current_op == OP_ATTACH || _current_op == OP_CONNECT) && data->status_data == CellularNetwork::Attached) { // target reached, release semaphore _semaphore.release(); } } } else { // non blocking if (st == CellularAttachNetwork && _current_op == OP_CONNECT && data->error == NSAPI_ERROR_OK && data->status_data == CellularNetwork::Attached) { // forward to application if (_status_cb) { _status_cb(ev, ptr); } do_connect(); return; } } } else { tr_debug("CellularContext: event %d, ptr %d", ev, ptr); #if NSAPI_PPP_AVAILABLE if (_is_blocking) { if (ev == NSAPI_EVENT_CONNECTION_STATUS_CHANGE && ptr == NSAPI_STATUS_GLOBAL_UP) { tr_info("CellularContext IP %s", get_ip_address()); _cb_data.error = NSAPI_ERROR_OK; _semaphore.release(); } else if (ev == NSAPI_EVENT_CONNECTION_STATUS_CHANGE && ptr == NSAPI_STATUS_DISCONNECTED) { tr_info("PPP disconnected"); _cb_data.error = NSAPI_ERROR_NO_CONNECTION; _semaphore.release(); } } #endif } // forward to application if (_status_cb) { _status_cb(ev, ptr); } } void AT_CellularContext::call_network_cb(nsapi_connection_status_t status) { if (_connect_status != status) { _connect_status = status; if (_status_cb) { _status_cb(NSAPI_EVENT_CONNECTION_STATUS_CHANGE, _connect_status); } if (_nw && _connect_status == NSAPI_STATUS_DISCONNECTED) { tr_info("CellularContext disconnected"); } } }