mbed-os/features/cellular/framework/AT/AT_CellularNetwork.cpp

700 lines
21 KiB
C++

/*
* 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 <stdlib.h>
#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, "+CEREG", "+CEREG:"},
{ CellularNetwork::C_GREG, "+CGREG", "+CGREG:"},
{ CellularNetwork::C_REG, "+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<void(nsapi_event_t, intptr_t)> 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 {
if (urc_on) {
return _at.at_cmd_discard(at_reg[index].cmd, "=", "%d", mode);
} else {
return _at.at_cmd_discard(at_reg[index].cmd, "=0");
}
}
}
nsapi_error_t AT_CellularNetwork::get_network_registering_mode(NWRegisteringMode &mode)
{
int ret;
nsapi_error_t error = _at.at_cmd_int("+COPS", "?", ret);
mode = (NWRegisteringMode)ret;
return error;
}
nsapi_error_t AT_CellularNetwork::set_registration(const char *plmn)
{
if (!plmn) {
tr_debug("Automatic network registration");
NWRegisteringMode mode;
if (get_network_registering_mode(mode) != NSAPI_ERROR_OK) {
return NSAPI_ERROR_DEVICE_ERROR;
}
if (mode != NWModeAutomatic) {
return _at.at_cmd_discard("+COPS", "=0");
}
return NSAPI_ERROR_OK;
} else {
tr_debug("Manual network registration to %s", plmn);
if (_op_act != RAT_UNKNOWN) {
return _at.at_cmd_discard("+COPS", "=1,2,", "%s%d", plmn, _op_act);
} else {
return _at.at_cmd_discard("+COPS", "=1,2", "%s", plmn);
}
}
}
void AT_CellularNetwork::read_reg_params(RegistrationType type, registration_params_t &reg_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 [<cause_type>],[<reject_cause>]
_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();
AttachStatus status;
get_attach(status);
if (status == Detached) {
tr_debug("Network attach");
_at.at_cmd_discard("+CGATT", "=1");
}
return _at.unlock_return_error();
}
nsapi_error_t AT_CellularNetwork::get_attach(AttachStatus &status)
{
int attach_status;
nsapi_error_t err = _at.at_cmd_int("+CGATT", "?", attach_status);
status = (attach_status == 1) ? Attached : Detached;
return err;
}
nsapi_error_t AT_CellularNetwork::detach()
{
_at.lock();
tr_debug("Network detach");
_at.at_cmd_discard("+CGATT", "=0");
_at.at_cmd_discard("+COPS", "=2");
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_stop("+COPS", "=?");
_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<void(CIoT_Supported_Opt)> network_support_cb)
{
_ciotopt_network_support_cb = network_support_cb;
return _at.at_cmd_discard("+CCIOTOPT", "=1,", "%d%d", supported_opt, preferred_opt);
}
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_stop("+CCIOTOPT", "?");
_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_stop("+CSQ", "");
_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_stop("+COPS", "?");
_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_stop("+COPN", "");
_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_stop("+CGACT", "?");
_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 &reg_params)
{
reg_params = _reg_params;
return NSAPI_ERROR_OK;
}
nsapi_error_t AT_CellularNetwork::get_registration_params(RegistrationType type, registration_params_t &reg_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();
_at.cmd_start_stop(at_reg[i].cmd, "?");
_at.resp_start(at_reg[i].urc_prefix);
(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';
return _at.at_cmd_discard("+CEDRXS", "=", "%d%d%s", mode, act_type, edrx);
}
nsapi_error_t AT_CellularNetwork::set_packet_domain_event_reporting(bool on)
{
if (!get_property(AT_CellularBase::PROPERTY_AT_CGEREP)) {
return NSAPI_ERROR_UNSUPPORTED;
}
return _at.at_cmd_discard("+CGEREP", "=", "%d", on ? 1 : 0);
}
nsapi_error_t AT_CellularNetwork::clear()
{
tr_info("AT_CellularNetwork::clear");
_at.lock();
_at.cmd_start_stop("+CGDCONT", "?");
_at.resp_start("+CGDCONT:");
struct context_s {
int id;
context_s *next;
};
CellularList<context_s> contexts;
while (_at.info_resp()) {
int cid = _at.read_int();
// clear all but the default context
if (cid <= 0) {
continue;
} else if (cid == 1) {
#ifndef MBED_CONF_NSAPI_DEFAULT_CELLULAR_APN
continue;
#else
char pdp_type_from_context[10];
int pdp_type_len = _at.read_string(pdp_type_from_context, sizeof(pdp_type_from_context));
if (pdp_type_len > 0) {
char apn[MAX_ACCESSPOINT_NAME_LENGTH];
int apn_len = _at.read_string(apn, sizeof(apn));
if (apn_len >= 0) {
if (strcmp(apn, MBED_CONF_NSAPI_DEFAULT_CELLULAR_APN) == 0) {
continue;
}
}
}
#endif
}
contexts.add_new()->id = cid;
}
_at.resp_stop();
if (contexts.get_head()) {
// try to detach from network before deleting contexts
(void)detach();
context_s *context = contexts.get_head();
while (context) {
if (_at.at_cmd_discard("+CGDCONT", "=", "%d", context->id) != NSAPI_ERROR_OK) {
tr_warn("Clear context %d failed", context->id);
}
context = context->next;
}
#ifdef MBED_CONF_NSAPI_DEFAULT_CELLULAR_APN
char pdp_type_str[sizeof("IPV4V6")];
if (get_property(PROPERTY_IPV4V6_PDP_TYPE) ||
(get_property(PROPERTY_IPV4_PDP_TYPE) && get_property(PROPERTY_IPV6_PDP_TYPE))) {
strcpy(pdp_type_str, "IPV4V6");
} else if (get_property(PROPERTY_IPV6_PDP_TYPE)) {
strcpy(pdp_type_str, "IPV6");
} else {
strcpy(pdp_type_str, "IP");
}
_at.at_cmd_discard("+CGDCONT", "=", "%d%s%s", 1, pdp_type_str, MBED_CONF_NSAPI_DEFAULT_CELLULAR_APN);
#endif
}
return _at.unlock_return_error();
}