/* * Copyright (c) 2017, 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 #include "AT_CellularNetwork.h" #include "CellularUtil.h" #include "CellularLog.h" #include "CellularCommon.h" using namespace std; using namespace mbed_cellular_util; using namespace mbed; struct at_reg_t { const CellularNetwork::RegistrationType type; const char *const cmd; const char *const urc_prefix; }; static const at_reg_t at_reg[] = { { CellularNetwork::C_EREG, "AT+CEREG", "+CEREG:"}, { CellularNetwork::C_GREG, "AT+CGREG", "+CGREG:"}, { CellularNetwork::C_REG, "AT+CREG", "+CREG:"} }; #if MBED_CONF_MBED_TRACE_ENABLE static const char *const reg_type_str[(int)AT_CellularNetwork::RegistrationStatusMax] = { "NotRegistered", "RegisteredHomeNetwork", "SearchingNetwork", "RegistrationDenied", "RegistrationUnknown", "RegisteredRoaming", "RegisteredSMSOnlyHome", "RegisteredSMSOnlyRoaming", "AttachedEmergencyOnly", "RegisteredCSFBNotPreferredHome", "RegisteredCSFBNotPreferredRoaming", "AlreadyRegistered" }; static const char *const rat_str[AT_CellularNetwork::RAT_MAX] = { "GSM", "GSM_COMPACT", "UTRAN", "EGPRS", "HSDPA", "HSUPA", "HSDPA_HSUPA", "E_UTRAN", "CATM1", "NB1", "RAT unknown", }; #endif AT_CellularNetwork::AT_CellularNetwork(ATHandler &atHandler) : AT_CellularBase(atHandler), _connection_status_cb(NULL), _ciotopt_network_support_cb(NULL), _op_act(RAT_UNKNOWN), _connect_status(NSAPI_STATUS_DISCONNECTED), _supported_network_opt(CIOT_OPT_MAX) { _urc_funcs[C_EREG] = callback(this, &AT_CellularNetwork::urc_cereg); _urc_funcs[C_GREG] = callback(this, &AT_CellularNetwork::urc_cgreg); _urc_funcs[C_REG] = callback(this, &AT_CellularNetwork::urc_creg); for (int type = 0; type < CellularNetwork::C_MAX; type++) { if (get_property((AT_CellularBase::CellularProperty)type) != RegistrationModeDisable) { _at.set_urc_handler(at_reg[type].urc_prefix, _urc_funcs[type]); } } if (get_property(AT_CellularBase::PROPERTY_AT_CGEREP)) { // additional urc to get better disconnect info for application. Not critical. _at.set_urc_handler("+CGEV: NW DET", callback(this, &AT_CellularNetwork::urc_cgev)); _at.set_urc_handler("+CGEV: ME DET", callback(this, &AT_CellularNetwork::urc_cgev)); } _at.set_urc_handler("+CCIOTOPTI:", callback(this, &AT_CellularNetwork::urc_cciotopti)); } AT_CellularNetwork::~AT_CellularNetwork() { (void)set_packet_domain_event_reporting(false); for (int type = 0; type < CellularNetwork::C_MAX; type++) { if (get_property((AT_CellularBase::CellularProperty)type) != RegistrationModeDisable) { _at.set_urc_handler(at_reg[type].urc_prefix, 0); } } if (get_property(AT_CellularBase::PROPERTY_AT_CGEREP)) { _at.set_urc_handler("+CGEV: ME DET", 0); _at.set_urc_handler("+CGEV: NW DET", 0); } _at.set_urc_handler("+CCIOTOPTI:", 0); } void AT_CellularNetwork::urc_cgev() { call_network_cb(NSAPI_STATUS_DISCONNECTED); } void AT_CellularNetwork::read_reg_params_and_compare(RegistrationType type) { registration_params_t reg_params; read_reg_params(type, reg_params); if (_at.get_last_error() == NSAPI_ERROR_OK && _connection_status_cb) { _reg_params._type = type; cell_callback_data_t data; data.error = NSAPI_ERROR_OK; if (reg_params._act != _reg_params._act) { _reg_params._act = reg_params._act; data.status_data = reg_params._act; _connection_status_cb((nsapi_event_t)CellularRadioAccessTechnologyChanged, (intptr_t)&data); } if (reg_params._status != _reg_params._status) { RegistrationStatus previous_registration_status = _reg_params._status; _reg_params._status = reg_params._status; data.status_data = reg_params._status; _connection_status_cb((nsapi_event_t)CellularRegistrationStatusChanged, (intptr_t)&data); if (reg_params._status == NotRegistered) { // Other states means that we are trying to connect or connected if (previous_registration_status == RegisteredHomeNetwork || previous_registration_status == RegisteredRoaming) { if (type != C_REG) {// we are interested only if we drop from packet network _connection_status_cb(NSAPI_EVENT_CONNECTION_STATUS_CHANGE, NSAPI_STATUS_DISCONNECTED); } } } } if (reg_params._cell_id != -1 && reg_params._cell_id != _reg_params._cell_id) { _reg_params._cell_id = reg_params._cell_id; _reg_params._lac = reg_params._lac; data.status_data = reg_params._cell_id; _connection_status_cb((nsapi_event_t)CellularCellIDChanged, (intptr_t)&data); } } } void AT_CellularNetwork::urc_creg() { read_reg_params_and_compare(C_REG); } void AT_CellularNetwork::urc_cereg() { read_reg_params_and_compare(C_EREG); } void AT_CellularNetwork::urc_cgreg() { read_reg_params_and_compare(C_GREG); } void AT_CellularNetwork::call_network_cb(nsapi_connection_status_t status) { if (_connection_status_cb) { _connection_status_cb(NSAPI_EVENT_CONNECTION_STATUS_CHANGE, _connect_status); } } void AT_CellularNetwork::attach(Callback status_cb) { _connection_status_cb = status_cb; } nsapi_error_t AT_CellularNetwork::set_registration_urc(RegistrationType type, bool urc_on) { int index = (int)type; MBED_ASSERT(index >= 0 && index < C_MAX); RegistrationMode mode = (RegistrationMode)get_property((AT_CellularBase::CellularProperty)type); if (mode == RegistrationModeDisable) { return NSAPI_ERROR_UNSUPPORTED; } else { _at.lock(); if (urc_on) { _at.cmd_start(at_reg[index].cmd); const uint8_t ch_eq = '='; _at.write_bytes(&ch_eq, 1); _at.write_int((int)mode); } else { _at.cmd_start(at_reg[index].cmd); _at.write_string("=0", false); } _at.cmd_stop_read_resp(); return _at.unlock_return_error(); } } nsapi_error_t AT_CellularNetwork::get_network_registering_mode(NWRegisteringMode &mode) { _at.lock(); _at.cmd_start("AT+COPS?"); _at.cmd_stop(); _at.resp_start("+COPS:"); mode = (NWRegisteringMode)_at.read_int(); _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularNetwork::set_registration(const char *plmn) { _at.lock(); if (!plmn) { tr_debug("Automatic network registration"); _at.cmd_start("AT+COPS?"); _at.cmd_stop(); _at.resp_start("+COPS:"); int mode = _at.read_int(); _at.resp_stop(); if (mode != 0) { _at.clear_error(); _at.cmd_start("AT+COPS=0"); _at.cmd_stop_read_resp(); } } else { tr_debug("Manual network registration to %s", plmn); _at.cmd_start("AT+COPS=1,2,"); _at.write_string(plmn); if (_op_act != RAT_UNKNOWN) { _at.write_int(_op_act); } _at.cmd_stop_read_resp(); } return _at.unlock_return_error(); } void AT_CellularNetwork::read_reg_params(RegistrationType type, registration_params_t ®_params) { const int MAX_STRING_LENGTH = 9; char string_param[MAX_STRING_LENGTH] = {0}; reg_params._type = type; int int_param = _at.read_int(); reg_params._status = (int_param >= 0 && int_param < RegistrationStatusMax) ? (RegistrationStatus)int_param : NotRegistered; int len = _at.read_string(string_param, TWO_BYTES_HEX + 1); if (len > 0) { reg_params._lac = hex_str_to_int(string_param, TWO_BYTES_HEX); } else { reg_params._lac = -1; } len = _at.read_string(string_param, FOUR_BYTES_HEX + 1); if (len > 0) { reg_params._cell_id = hex_str_to_int(string_param, FOUR_BYTES_HEX); } else { reg_params._cell_id = -1; } int_param = _at.read_int(); reg_params._act = (int_param >= 0 && int_param < RAT_MAX) ? (RadioAccessTechnology)int_param : RAT_UNKNOWN ; // Skip [],[] _at.skip_param(2); len = _at.read_string(string_param, ONE_BYTE_BINARY + 1); reg_params._active_time = calculate_active_time(string_param, len); len = _at.read_string(string_param, ONE_BYTE_BINARY + 1); reg_params._periodic_tau = calculate_periodic_tau(string_param, len); #if MBED_CONF_MBED_TRACE_ENABLE tr_debug("%s %s, LAC %d, cell %d, %s", at_reg[(int)type].urc_prefix, reg_type_str[reg_params._status], reg_params._lac, reg_params._cell_id, rat_str[reg_params._act]); #endif } nsapi_error_t AT_CellularNetwork::set_attach() { _at.lock(); _at.cmd_start("AT+CGATT?"); _at.cmd_stop(); _at.resp_start("+CGATT:"); int attached_state = _at.read_int(); _at.resp_stop(); if (attached_state != 1) { tr_debug("Network attach"); _at.cmd_start("AT+CGATT=1"); _at.cmd_stop_read_resp(); } return _at.unlock_return_error(); } nsapi_error_t AT_CellularNetwork::get_attach(AttachStatus &status) { _at.lock(); _at.cmd_start("AT+CGATT?"); _at.cmd_stop(); _at.resp_start("+CGATT:"); if (_at.info_resp()) { int attach_status = _at.read_int(); status = (attach_status == 1) ? Attached : Detached; } _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularNetwork::detach() { _at.lock(); tr_debug("Network detach"); _at.cmd_start("AT+CGATT=0"); _at.cmd_stop_read_resp(); _at.cmd_start("AT+COPS=2"); _at.cmd_stop_read_resp(); call_network_cb(NSAPI_STATUS_DISCONNECTED); return _at.unlock_return_error(); } nsapi_error_t AT_CellularNetwork::set_access_technology_impl(RadioAccessTechnology opsAct) { _op_act = RAT_UNKNOWN; return NSAPI_ERROR_UNSUPPORTED; } nsapi_error_t AT_CellularNetwork::set_access_technology(RadioAccessTechnology opAct) { if (opAct == RAT_UNKNOWN) { return NSAPI_ERROR_UNSUPPORTED; } _op_act = opAct; return set_access_technology_impl(opAct); } nsapi_error_t AT_CellularNetwork::scan_plmn(operList_t &operators, int &opsCount) { int idx = 0; _at.lock(); _at.cmd_start("AT+COPS=?"); _at.cmd_stop(); _at.resp_start("+COPS:"); int ret, error_code = -1; operator_t *op = NULL; while (_at.info_elem('(')) { op = operators.add_new(); op->op_status = (operator_t::Status)_at.read_int(); _at.read_string(op->op_long, sizeof(op->op_long)); _at.read_string(op->op_short, sizeof(op->op_short)); _at.read_string(op->op_num, sizeof(op->op_num)); // Optional - try read an int ret = _at.read_int(); op->op_rat = (ret == error_code) ? RAT_UNKNOWN : (RadioAccessTechnology)ret; if ((_op_act == RAT_UNKNOWN) || ((op->op_rat != RAT_UNKNOWN) && (op->op_rat == _op_act))) { idx++; } else { operators.delete_last(); } } _at.resp_stop(); opsCount = idx; return _at.unlock_return_error(); } nsapi_error_t AT_CellularNetwork::set_ciot_optimization_config(CIoT_Supported_Opt supported_opt, CIoT_Preferred_UE_Opt preferred_opt, Callback network_support_cb) { _ciotopt_network_support_cb = network_support_cb; _at.lock(); _at.cmd_start("AT+CCIOTOPT="); _at.write_int(1); //enable CCIOTOPTI URC _at.write_int(supported_opt); _at.write_int(preferred_opt); _at.cmd_stop_read_resp(); return _at.unlock_return_error(); } void AT_CellularNetwork::urc_cciotopti() { _supported_network_opt = (CIoT_Supported_Opt)_at.read_int(); if (_ciotopt_network_support_cb) { _ciotopt_network_support_cb(_supported_network_opt); } } nsapi_error_t AT_CellularNetwork::get_ciot_ue_optimization_config(CIoT_Supported_Opt &supported_opt, CIoT_Preferred_UE_Opt &preferred_opt) { _at.lock(); _at.cmd_start("AT+CCIOTOPT?"); _at.cmd_stop(); _at.resp_start("+CCIOTOPT:"); _at.read_int(); if (_at.get_last_error() == NSAPI_ERROR_OK) { supported_opt = (CIoT_Supported_Opt)_at.read_int(); preferred_opt = (CIoT_Preferred_UE_Opt)_at.read_int(); } _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularNetwork::get_ciot_network_optimization_config(CIoT_Supported_Opt &supported_network_opt) { supported_network_opt = _supported_network_opt; return NSAPI_ERROR_OK; } nsapi_error_t AT_CellularNetwork::get_signal_quality(int &rssi, int *ber) { _at.lock(); _at.cmd_start("AT+CSQ"); _at.cmd_stop(); _at.resp_start("+CSQ:"); int t_rssi = _at.read_int(); int t_ber = _at.read_int(); _at.resp_stop(); if (t_rssi < 0 || t_ber < 0) { _at.unlock(); return NSAPI_ERROR_DEVICE_ERROR; } // RSSI value is returned in dBm with range from -51 to -113 dBm, see 3GPP TS 27.007 if (t_rssi == 99) { rssi = SignalQualityUnknown; } else { rssi = -113 + 2 * t_rssi; } if (ber) { if (t_ber == 99) { *ber = SignalQualityUnknown; } else { *ber = t_ber; } } return _at.unlock_return_error(); } /** Get the last 3GPP error code * @return see 3GPP TS 27.007 error codes */ int AT_CellularNetwork::get_3gpp_error() { return _at.get_3gpp_error(); } nsapi_error_t AT_CellularNetwork::get_operator_params(int &format, operator_t &operator_params) { _at.lock(); _at.cmd_start("AT+COPS?"); _at.cmd_stop(); _at.resp_start("+COPS:"); _at.read_int(); //ignore mode format = _at.read_int(); if (_at.get_last_error() == NSAPI_ERROR_OK) { switch (format) { case 0: _at.read_string(operator_params.op_long, sizeof(operator_params.op_long)); break; case 1: _at.read_string(operator_params.op_short, sizeof(operator_params.op_short)); break; default: _at.read_string(operator_params.op_num, sizeof(operator_params.op_num)); break; } operator_params.op_rat = (RadioAccessTechnology)_at.read_int(); } _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularNetwork::get_operator_names(operator_names_list &op_names) { _at.lock(); _at.cmd_start("AT+COPN"); _at.cmd_stop(); _at.resp_start("+COPN:"); operator_names_t *names = NULL; while (_at.info_resp()) { names = op_names.add_new(); _at.read_string(names->numeric, sizeof(names->numeric)); _at.read_string(names->alpha, sizeof(names->alpha)); } _at.resp_stop(); return _at.unlock_return_error(); } void AT_CellularNetwork::get_context_state_command() { _at.cmd_start("AT+CGACT?"); _at.cmd_stop(); _at.resp_start("+CGACT:"); } bool AT_CellularNetwork::is_active_context(int *number_of_active_contexts, int cid) { _at.lock(); if (number_of_active_contexts) { *number_of_active_contexts = 0; } bool active_found = false; int context_id; // read active contexts get_context_state_command(); while (_at.info_resp()) { context_id = _at.read_int(); // discard context id if (_at.read_int() == 1) { // check state tr_debug("Found active context"); if (number_of_active_contexts) { (*number_of_active_contexts)++; } if (cid == -1) { active_found = true; } else if (context_id == cid) { active_found = true; } if (!number_of_active_contexts && active_found) { break; } } } _at.resp_stop(); _at.unlock(); return active_found; } nsapi_error_t AT_CellularNetwork::get_registration_params(registration_params_t ®_params) { reg_params = _reg_params; return NSAPI_ERROR_OK; } nsapi_error_t AT_CellularNetwork::get_registration_params(RegistrationType type, registration_params_t ®_params) { int i = (int)type; MBED_ASSERT(i >= 0 && i < C_MAX); if (!get_property((AT_CellularBase::CellularProperty)at_reg[i].type)) { return NSAPI_ERROR_UNSUPPORTED; } _at.lock(); const char *rsp[] = { "+CEREG:", "+CGREG:", "+CREG:"}; _at.cmd_start(at_reg[i].cmd); _at.write_string("?", false); _at.cmd_stop(); _at.resp_start(rsp[i]); (void)_at.read_int(); // ignore urc mode subparam read_reg_params(type, reg_params); _at.resp_stop(); _reg_params = reg_params; return _at.unlock_return_error(); } int AT_CellularNetwork::calculate_active_time(const char *active_time_string, int active_time_length) { if (active_time_length != ONE_BYTE_BINARY) { return -1; } uint32_t ie_unit = binary_str_to_uint(active_time_string, TIMER_UNIT_LENGTH); uint32_t ie_value = binary_str_to_uint(active_time_string + TIMER_UNIT_LENGTH, active_time_length - TIMER_UNIT_LENGTH); switch (ie_unit) { case 0: // multiples of 2 seconds return 2 * ie_value; case 1: // multiples of 1 minute return 60 * ie_value; case 2: // multiples of decihours return 6 * 60 * ie_value; case 7: // timer is deactivated return 0; default: // other values shall be interpreted as multiples of 1 minute return 60 * ie_value; } } int AT_CellularNetwork::calculate_periodic_tau(const char *periodic_tau_string, int periodic_tau_length) { if (periodic_tau_length != ONE_BYTE_BINARY) { return -1; } uint32_t ie_unit = binary_str_to_uint(periodic_tau_string, TIMER_UNIT_LENGTH); uint32_t ie_value = binary_str_to_uint(periodic_tau_string + TIMER_UNIT_LENGTH, periodic_tau_length - TIMER_UNIT_LENGTH); switch (ie_unit) { case 0: // multiples of 10 minutes return 60 * 10 * ie_value; case 1: // multiples of 1 hour return 60 * 60 * ie_value; case 2: // multiples of 10 hours return 10 * 60 * 60 * ie_value; case 3: // multiples of 2 seconds return 2 * ie_value; case 4: // multiples of 30 seconds return 30 * ie_value; case 5: // multiples of 1 minute return 60 * ie_value; case 6: // multiples of 320 hours return 320 * 60 * 60 * ie_value; default: // timer is deactivated return 0; } } nsapi_error_t AT_CellularNetwork::set_receive_period(int mode, EDRXAccessTechnology act_type, uint8_t edrx_value) { char edrx[5]; uint_to_binary_str(edrx_value, edrx, 5, 4); edrx[4] = '\0'; _at.lock(); _at.cmd_start("AT+CEDRXS="); _at.write_int(mode); _at.write_int(act_type); _at.write_string(edrx); _at.cmd_stop_read_resp(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularNetwork::set_packet_domain_event_reporting(bool on) { if (!get_property(AT_CellularBase::PROPERTY_AT_CGEREP)) { return NSAPI_ERROR_UNSUPPORTED; } _at.lock(); _at.cmd_start("AT+CGEREP="); _at.write_int(on ? 1 : 0); // discard unsolicited result codes when MT TE link is reserved (e.g. in on line data mode); otherwise forward them directly to the TE _at.cmd_stop_read_resp(); return _at.unlock_return_error(); }