diff --git a/applications/asset_tracker_v2/prj.conf b/applications/asset_tracker_v2/prj.conf index e9aa78506f1..d4c9e1c8ee7 100644 --- a/applications/asset_tracker_v2/prj.conf +++ b/applications/asset_tracker_v2/prj.conf @@ -46,6 +46,7 @@ CONFIG_LTE_PSM_REQ=y CONFIG_LTE_PSM_REQ_RPTAU="11000001" ### 20 seconds active time. CONFIG_LTE_PSM_REQ_RAT="00001010" +CONFIG_LTE_LC_NCELLMEAS=y # Settings - Used to store real-time device configuration to flash. CONFIG_SETTINGS=y diff --git a/include/modem/lte_lc.h b/include/modem/lte_lc.h index 06c739059e2..0d72a3e90af 100644 --- a/include/modem/lte_lc.h +++ b/include/modem/lte_lc.h @@ -214,6 +214,7 @@ enum lte_lc_evt_type { */ LTE_LC_EVT_NW_REG_STATUS, +#if defined(CONFIG_LTE_LC_PSM) || defined(__DOXYGEN__) /** * PSM parameters provided by the network. * @@ -221,7 +222,9 @@ enum lte_lc_evt_type { * @ref lte_lc_psm_cfg in the event. */ LTE_LC_EVT_PSM_UPDATE, +#endif +#if defined(CONFIG_LTE_LC_EDRX) || defined(__DOXYGEN__) /** * eDRX parameters provided by the network. * @@ -229,6 +232,7 @@ enum lte_lc_evt_type { * @ref lte_lc_edrx_cfg in the event. */ LTE_LC_EVT_EDRX_UPDATE, +#endif /** * RRC connection state. @@ -272,6 +276,7 @@ enum lte_lc_evt_type { */ LTE_LC_EVT_TAU_PRE_WARNING, +#if defined(CONFIG_LTE_LC_NCELLMEAS) || defined(__DOXYGEN__) /** * Neighbor cell measurement results. * @@ -279,7 +284,9 @@ enum lte_lc_evt_type { * @ref lte_lc_cells_info in the event. */ LTE_LC_EVT_NEIGHBOR_CELL_MEAS, +#endif +#if defined(CONFIG_LTE_LC_XMODEMSLEEP) || defined(__DOXYGEN__) /** * Modem sleep pre-warning. * @@ -308,6 +315,7 @@ enum lte_lc_evt_type { * the duration of the sleep. */ LTE_LC_EVT_MODEM_SLEEP_ENTER, +#endif /** * Information about modem operation. @@ -317,6 +325,7 @@ enum lte_lc_evt_type { */ LTE_LC_EVT_MODEM_EVENT, +#if defined(CONFIG_LTE_LC_RAI) || defined(__DOXYGEN__) /** * Information about RAI (Release Assistance Indication) configuration. * @@ -326,6 +335,7 @@ enum lte_lc_evt_type { * @note This event is only supported by modem firmware versions >= 2.0.2. */ LTE_LC_EVT_RAI_UPDATE, +#endif }; /** RRC connection state. */ @@ -1198,11 +1208,15 @@ struct lte_lc_evt { /** Payload for event @ref LTE_LC_EVT_RRC_UPDATE. */ enum lte_lc_rrc_mode rrc_mode; +#if defined(CONFIG_LTE_LC_PSM) || defined(__DOXYGEN__) /** Payload for event @ref LTE_LC_EVT_PSM_UPDATE. */ struct lte_lc_psm_cfg psm_cfg; +#endif +#if defined(CONFIG_LTE_LC_EDRX) || defined(__DOXYGEN__) /** Payload for event @ref LTE_LC_EVT_EDRX_UPDATE. */ struct lte_lc_edrx_cfg edrx_cfg; +#endif /** Payload for event @ref LTE_LC_EVT_CELL_UPDATE. */ struct lte_lc_cell cell; @@ -1210,11 +1224,13 @@ struct lte_lc_evt { /** Payload for event @ref LTE_LC_EVT_LTE_MODE_UPDATE. */ enum lte_lc_lte_mode lte_mode; +#if defined(CONFIG_LTE_LC_XMODEMSLEEP) || defined(__DOXYGEN__) /** * Payload for events @ref LTE_LC_EVT_MODEM_SLEEP_EXIT_PRE_WARNING, * @ref LTE_LC_EVT_MODEM_SLEEP_EXIT and @ref LTE_LC_EVT_MODEM_SLEEP_ENTER. */ struct lte_lc_modem_sleep modem_sleep; +#endif /** Payload for event @ref LTE_LC_EVT_MODEM_EVENT. */ enum lte_lc_modem_evt modem_evt; @@ -1226,11 +1242,15 @@ struct lte_lc_evt { */ uint64_t time; +#if defined(CONFIG_LTE_LC_NCELLMEAS) || defined(__DOXYGEN__) /** Payload for event @ref LTE_LC_EVT_NEIGHBOR_CELL_MEAS. */ struct lte_lc_cells_info cells_info; +#endif +#if defined(CONFIG_LTE_LC_RAI) || defined(__DOXYGEN__) /** Payload for event @ref LTE_LC_EVT_RAI_UPDATE. */ struct lte_lc_rai_cfg rai_cfg; +#endif }; }; diff --git a/lib/lte_link_control/CMakeLists.txt b/lib/lte_link_control/CMakeLists.txt index 38b9dd7a8da..230d1e84f98 100644 --- a/lib/lte_link_control/CMakeLists.txt +++ b/lib/lte_link_control/CMakeLists.txt @@ -5,10 +5,13 @@ # zephyr_library() +zephyr_library_include_directories(include) zephyr_library_sources(lte_lc.c) -zephyr_library_sources(lte_lc_helpers.c) zephyr_library_sources(lte_lc_modem_hooks.c) zephyr_library_sources_ifdef(CONFIG_LTE_LC_TRACE lte_lc_trace.c) zephyr_library_sources_ifdef(CONFIG_LTE_SHELL lte_lc_shell.c) +add_subdirectory(common) +add_subdirectory(modules) + zephyr_linker_sources(RODATA lte_lc.ld) diff --git a/lib/lte_link_control/Kconfig b/lib/lte_link_control/Kconfig index a8d32564221..1317eb3e744 100644 --- a/lib/lte_link_control/Kconfig +++ b/lib/lte_link_control/Kconfig @@ -66,6 +66,7 @@ config LTE_PSM_REQ If this option is set the library will automatically request PSM when the modem is initialized. This will cause the modem to include a PSM request in every subsequent LTE attach request. + select LTE_LC_PSM choice LTE_PSM_REQ_FORMAT prompt "Format for PSM configuration" @@ -129,6 +130,7 @@ config LTE_EDRX_REQ help Enable request for use of eDRX using AT+CEDRXS. For reference, see 3GPP 27.007 Ch. 7.40. + select LTE_LC_EDRX config LTE_EDRX_REQ_VALUE_LTE_M string "Requested eDRX value for LTE-M" @@ -354,4 +356,6 @@ module-dep = LOG module-str = LTE link control library source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" +rsource "modules/Kconfig" + endif # LTE_LINK_CONTROL diff --git a/lib/lte_link_control/common/CMakeLists.txt b/lib/lte_link_control/common/CMakeLists.txt new file mode 100644 index 00000000000..0333506c94a --- /dev/null +++ b/lib/lte_link_control/common/CMakeLists.txt @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library_sources(event_handler_list.c) +zephyr_library_sources(helpers.c) +zephyr_library_sources(work_q.c) diff --git a/lib/lte_link_control/common/event_handler_list.c b/lib/lte_link_control/common/event_handler_list.c new file mode 100644 index 00000000000..35b6957782e --- /dev/null +++ b/lib/lte_link_control/common/event_handler_list.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +static K_MUTEX_DEFINE(list_mtx); + +/**@brief List element for event handler list. */ +struct event_handler { + sys_snode_t node; + lte_lc_evt_handler_t handler; +}; + +static sys_slist_t handler_list; + +/** + * @brief Find the handler from the event handler list. + * + * @return The node or NULL if not found and its previous node in @p prev_out. + */ +static struct event_handler *event_handler_list_node_find(struct event_handler **prev_out, + lte_lc_evt_handler_t handler) +{ + struct event_handler *prev = NULL, *curr; + + SYS_SLIST_FOR_EACH_CONTAINER(&handler_list, curr, node) { + if (curr->handler == handler) { + *prev_out = prev; + return curr; + } + prev = curr; + } + return NULL; +} + +/**@brief Add the handler in the event handler list if not already present. */ +int event_handler_list_handler_append(lte_lc_evt_handler_t handler) +{ + struct event_handler *to_ins; + + k_mutex_lock(&list_mtx, K_FOREVER); + + /* Check if handler is already registered. */ + if (event_handler_list_node_find(&to_ins, handler) != NULL) { + LOG_DBG("Handler already registered. Nothing to do"); + k_mutex_unlock(&list_mtx); + return 0; + } + + /* Allocate memory and fill. */ + to_ins = (struct event_handler *)k_malloc(sizeof(struct event_handler)); + if (to_ins == NULL) { + k_mutex_unlock(&list_mtx); + return -ENOBUFS; + } + memset(to_ins, 0, sizeof(struct event_handler)); + to_ins->handler = handler; + + /* Insert handler in the list. */ + sys_slist_append(&handler_list, &to_ins->node); + k_mutex_unlock(&list_mtx); + return 0; +} + +/**@brief Remove the handler from the event handler list if registered. */ +int event_handler_list_handler_remove(lte_lc_evt_handler_t handler) +{ + struct event_handler *curr, *prev = NULL; + + k_mutex_lock(&list_mtx, K_FOREVER); + + /* Check if the handler is registered before removing it. */ + curr = event_handler_list_node_find(&prev, handler); + if (curr == NULL) { + LOG_WRN("Handler not registered. Nothing to do"); + k_mutex_unlock(&list_mtx); + return 0; + } + + /* Remove the handler from the list. */ + sys_slist_remove(&handler_list, &prev->node, &curr->node); + k_free(curr); + + k_mutex_unlock(&list_mtx); + return 0; +} + +/**@brief dispatch events. */ +void event_handler_list_dispatch(const struct lte_lc_evt *const evt) +{ + struct event_handler *curr, *tmp; + + if (event_handler_list_is_empty()) { + return; + } + + k_mutex_lock(&list_mtx, K_FOREVER); + + /* Dispatch events to all registered handlers */ + LOG_DBG("Dispatching event: type=%d", evt->type); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&handler_list, curr, tmp, node) { + LOG_DBG(" - handler=0x%08X", (uint32_t)curr->handler); + curr->handler(evt); + } + LOG_DBG("Done"); + + k_mutex_unlock(&list_mtx); +} + +/**@brief Test if the handler list is empty. */ +bool event_handler_list_is_empty(void) +{ + return sys_slist_is_empty(&handler_list); +} diff --git a/lib/lte_link_control/common/helpers.c b/lib/lte_link_control/common/helpers.c new file mode 100644 index 00000000000..a29b90e935a --- /dev/null +++ b/lib/lte_link_control/common/helpers.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +int string_to_int(const char *str_buf, int base, int *output) +{ + int temp; + char *end_ptr; + + __ASSERT_NO_MSG(str_buf != NULL); + + errno = 0; + temp = strtol(str_buf, &end_ptr, base); + + if (end_ptr == str_buf || *end_ptr != '\0' || + ((temp == LONG_MAX || temp == LONG_MIN) && errno == ERANGE)) { + return -ENODATA; + } + + *output = temp; + + return 0; +} + +int string_param_to_int(struct at_parser *parser, size_t idx, int *output, int base) +{ + int err; + char str_buf[16]; + size_t len = sizeof(str_buf); + + __ASSERT_NO_MSG(parser != NULL); + __ASSERT_NO_MSG(output != NULL); + + err = at_parser_string_get(parser, idx, str_buf, &len); + if (err) { + return err; + } + + if (string_to_int(str_buf, base, output)) { + return -ENODATA; + } + + return 0; +} + +int plmn_param_string_to_mcc_mnc(struct at_parser *parser, size_t idx, int *mcc, int *mnc) +{ + int err; + char str_buf[7]; + size_t len = sizeof(str_buf); + + err = at_parser_string_get(parser, idx, str_buf, &len); + if (err) { + LOG_ERR("Could not get PLMN, error: %d", err); + return err; + } + + str_buf[len] = '\0'; + + /* Read MNC and store as integer. The MNC starts as the fourth character + * in the string, following three characters long MCC. + */ + err = string_to_int(&str_buf[3], 10, mnc); + if (err) { + LOG_ERR("Could not get MNC, error: %d", err); + return err; + } + + /* NUL-terminate MCC, read and store it. */ + str_buf[3] = '\0'; + + err = string_to_int(str_buf, 10, mcc); + if (err) { + LOG_ERR("Could not get MCC, error: %d", err); + return err; + } + + return 0; +} diff --git a/lib/lte_link_control/common/work_q.c b/lib/lte_link_control/common/work_q.c new file mode 100644 index 00000000000..8ee24561e97 --- /dev/null +++ b/lib/lte_link_control/common/work_q.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include "common/work_q.h" + +K_THREAD_STACK_DEFINE(work_q_stack, CONFIG_LTE_LC_WORKQUEUE_STACK_SIZE); + +struct k_work_q work_q; + +int work_q_start(void) +{ + struct k_work_queue_config cfg = { + .name = "work_q", + }; + + k_work_queue_start(&work_q, work_q_stack, K_THREAD_STACK_SIZEOF(work_q_stack), + K_LOWEST_APPLICATION_THREAD_PRIO, &cfg); + + return 0; +} + +struct k_work_q *work_q_get(void) +{ + return &work_q; +} diff --git a/lib/lte_link_control/include/common/event_handler_list.h b/lib/lte_link_control/include/common/event_handler_list.h new file mode 100644 index 00000000000..e7b2160feb7 --- /dev/null +++ b/lib/lte_link_control/include/common/event_handler_list.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef EVENT_HANDLER_LIST_H__ +#define EVENT_HANDLER_LIST_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* @brief Add the handler in the event handler list if not already present. + * + * @param handler Event handler. + * + * @return Zero on success, negative errno code if the API call fails. + */ +int event_handler_list_handler_append(lte_lc_evt_handler_t handler); + +/* @brief Remove the handler from the event handler list if present. + * + * @param handler Event handler. + * + * @return Zero on success, negative errno code if the API call fails. + */ +int event_handler_list_handler_remove(lte_lc_evt_handler_t handler); + +/* @brief Dispatch events for the registered event handlers. + * + * @param evt Event. + * + * @return Zero on success, negative errno code if the API call fails. + */ +void event_handler_list_dispatch(const struct lte_lc_evt *const evt); + +/* @brief Test if the handler list is empty. + * + * @return a boolean, true if it's empty, false otherwise + */ +bool event_handler_list_is_empty(void); + +#ifdef __cplusplus +} +#endif + +#endif /* EVENT_HANDLER_LIST_H__ */ diff --git a/lib/lte_link_control/include/common/helpers.h b/lib/lte_link_control/include/common/helpers.h new file mode 100644 index 00000000000..71ebd384273 --- /dev/null +++ b/lib/lte_link_control/include/common/helpers.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef HELPERS_H__ +#define HELPERS_H__ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int string_to_int(const char *str_buf, int base, int *output); + +/* Converts integer on string format to integer type. + * Returns zero on success, otherwise negative error on failure. + */ +int string_param_to_int(struct at_parser *parser, size_t idx, int *output, int base); + +/* Converts PLMN string to integer type MCC and MNC. + * Returns zero on success, otherwise negative error on failure. + */ +int plmn_param_string_to_mcc_mnc(struct at_parser *parser, size_t idx, int *mcc, int *mnc); + +#ifdef __cplusplus +} +#endif + +#endif /* HELPERS_H__ */ diff --git a/lib/lte_link_control/include/common/work_q.h b/lib/lte_link_control/include/common/work_q.h new file mode 100644 index 00000000000..e143d97465f --- /dev/null +++ b/lib/lte_link_control/include/common/work_q.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef WORK_Q_H__ +#define WORK_Q_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int work_q_start(void); +struct k_work_q *work_q_get(void); + +#ifdef __cplusplus +} +#endif + +#endif /* WORK_Q_H__ */ diff --git a/lib/lte_link_control/include/modules/cereg.h b/lib/lte_link_control/include/modules/cereg.h new file mode 100644 index 00000000000..906b2c4718e --- /dev/null +++ b/lib/lte_link_control/include/modules/cereg.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef CEREG_H__ +#define CEREG_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int cereg_status_get(enum lte_lc_nw_reg_status *status); +int cereg_mode_get(enum lte_lc_lte_mode *mode); +int cereg_lte_connect(bool blocking); +int cereg_notifications_enable(void); + +#ifdef __cplusplus +} +#endif + +#endif /* CEREG_H__ */ diff --git a/lib/lte_link_control/include/modules/cfun.h b/lib/lte_link_control/include/modules/cfun.h new file mode 100644 index 00000000000..e06e963e27b --- /dev/null +++ b/lib/lte_link_control/include/modules/cfun.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef CFUN_H__ +#define CFUN_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int cfun_mode_get(enum lte_lc_func_mode *mode); +int cfun_mode_set(enum lte_lc_func_mode mode); + +#ifdef __cplusplus +} +#endif + +#endif /* CFUN_H__ */ diff --git a/lib/lte_link_control/include/modules/coneval.h b/lib/lte_link_control/include/modules/coneval.h new file mode 100644 index 00000000000..dd6708385f6 --- /dev/null +++ b/lib/lte_link_control/include/modules/coneval.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef CONEVAL_H__ +#define CONEVAL_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int coneval_params_get(struct lte_lc_conn_eval_params *params); + +#ifdef __cplusplus +} +#endif + +#endif /* CONEVAL_H__ */ diff --git a/lib/lte_link_control/include/modules/cscon.h b/lib/lte_link_control/include/modules/cscon.h new file mode 100644 index 00000000000..04e248e175f --- /dev/null +++ b/lib/lte_link_control/include/modules/cscon.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef CSCON_H__ +#define CSCON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +int cscon_notifications_enable(void); + +#ifdef __cplusplus +} +#endif + +#endif /* CSCON_H__ */ diff --git a/lib/lte_link_control/include/modules/edrx.h b/lib/lte_link_control/include/modules/edrx.h new file mode 100644 index 00000000000..fe6a177bc51 --- /dev/null +++ b/lib/lte_link_control/include/modules/edrx.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef EDRX_H__ +#define EDRX_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Length for eDRX and PTW values */ +#define LTE_LC_EDRX_VALUE_LEN 5 + +int edrx_cfg_get(struct lte_lc_edrx_cfg *edrx_cfg); +int edrx_ptw_set(enum lte_lc_lte_mode mode, const char *ptw); +int edrx_param_set(enum lte_lc_lte_mode mode, const char *edrx); +int edrx_request(bool enable); + +#ifdef __cplusplus +} +#endif + +#endif /* EDRX_H__ */ diff --git a/lib/lte_link_control/include/modules/mdmev.h b/lib/lte_link_control/include/modules/mdmev.h new file mode 100644 index 00000000000..138bdb0c4e4 --- /dev/null +++ b/lib/lte_link_control/include/modules/mdmev.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef MDMEV_H__ +#define MDMEV_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int mdmev_enable(void); +int mdmev_disable(void); + +#ifdef __cplusplus +} +#endif + +#endif /* MDMEV_H__ */ diff --git a/lib/lte_link_control/include/modules/ncellmeas.h b/lib/lte_link_control/include/modules/ncellmeas.h new file mode 100644 index 00000000000..ec6fb1befe8 --- /dev/null +++ b/lib/lte_link_control/include/modules/ncellmeas.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef NCELLMEAS_H__ +#define NCELLMEAS_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int ncellmeas_start(struct lte_lc_ncellmeas_params *params); +int ncellmeas_cancel(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NCELLMEAS_H__ */ diff --git a/lib/lte_link_control/include/modules/periodicsearchconf.h b/lib/lte_link_control/include/modules/periodicsearchconf.h new file mode 100644 index 00000000000..567fc17465b --- /dev/null +++ b/lib/lte_link_control/include/modules/periodicsearchconf.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef PERIODICSEARCHCONF_H__ +#define PERIODICSEARCHCONF_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int periodicsearchconf_set(const struct lte_lc_periodic_search_cfg *const cfg); +int periodicsearchconf_get(struct lte_lc_periodic_search_cfg *const cfg); +int periodicsearchconf_clear(void); +int periodicsearchconf_request(void); + +#ifdef __cplusplus +} +#endif + +#endif /* PERIODICSEARCHCONF_H__ */ diff --git a/lib/lte_link_control/include/modules/psm.h b/lib/lte_link_control/include/modules/psm.h new file mode 100644 index 00000000000..22de4e332c2 --- /dev/null +++ b/lib/lte_link_control/include/modules/psm.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef PSM_H__ +#define PSM_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int psm_param_set(const char *rptau, const char *rat); +int psm_param_set_seconds(int rptau, int rat); +int psm_req(bool enable); +int psm_proprietary_req(bool enable); +int psm_get(int *tau, int *active_time); +void psm_evt_update_send(struct lte_lc_psm_cfg *psm_cfg); +int psm_parse(const char *active_time_str, const char *tau_ext_str, + const char *tau_legacy_str, struct lte_lc_psm_cfg *psm_cfg); +struct k_work *psm_work_get(void); + +#ifdef __cplusplus +} +#endif + +#endif /* PSM_H__ */ diff --git a/lib/lte_link_control/include/modules/rai.h b/lib/lte_link_control/include/modules/rai.h new file mode 100644 index 00000000000..4cb8584cfbd --- /dev/null +++ b/lib/lte_link_control/include/modules/rai.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef RAI_H__ +#define RAI_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int rai_set(void); + +#ifdef __cplusplus +} +#endif + +#endif /* RAI_H__ */ diff --git a/lib/lte_link_control/include/modules/redmob.h b/lib/lte_link_control/include/modules/redmob.h new file mode 100644 index 00000000000..0c482f68ee2 --- /dev/null +++ b/lib/lte_link_control/include/modules/redmob.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef REDMOB_H__ +#define REDMOB_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int redmob_get(enum lte_lc_reduced_mobility_mode *mode); +int redmob_set(enum lte_lc_reduced_mobility_mode mode); + +#ifdef __cplusplus +} +#endif + +#endif /* REDMOB_H__ */ diff --git a/lib/lte_link_control/include/modules/xfactoryreset.h b/lib/lte_link_control/include/modules/xfactoryreset.h new file mode 100644 index 00000000000..a3dbce5e281 --- /dev/null +++ b/lib/lte_link_control/include/modules/xfactoryreset.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef XFACTORYRESET_H__ +#define XFACTORYRESET_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int xfactoryreset_reset(enum lte_lc_factory_reset_type type); + +#ifdef __cplusplus +} +#endif + +#endif /* XFACTORYRESET_H__ */ diff --git a/lib/lte_link_control/include/modules/xmodemsleep.h b/lib/lte_link_control/include/modules/xmodemsleep.h new file mode 100644 index 00000000000..57ebed08057 --- /dev/null +++ b/lib/lte_link_control/include/modules/xmodemsleep.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef XMODEMSLEEP_H__ +#define XMODEMSLEEP_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int xmodemsleep_notifications_enable(void); + +#ifdef __cplusplus +} +#endif + +#endif /* XMODEMSLEEP_H__ */ diff --git a/lib/lte_link_control/include/modules/xsystemmode.h b/lib/lte_link_control/include/modules/xsystemmode.h new file mode 100644 index 00000000000..6014b3471f5 --- /dev/null +++ b/lib/lte_link_control/include/modules/xsystemmode.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef XSYSTEMMODE_H__ +#define XSYSTEMMODE_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int xsystemmode_mode_set(enum lte_lc_system_mode mode, + enum lte_lc_system_mode_preference preference); +int xsystemmode_mode_get(enum lte_lc_system_mode *mode, + enum lte_lc_system_mode_preference *preference); + +#ifdef __cplusplus +} +#endif + +#endif /* XSYSTEMMODE_H__ */ diff --git a/lib/lte_link_control/include/modules/xt3412.h b/lib/lte_link_control/include/modules/xt3412.h new file mode 100644 index 00000000000..8799a4d5723 --- /dev/null +++ b/lib/lte_link_control/include/modules/xt3412.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef XT3412_H__ +#define XT3412_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int xt3412_notifications_enable(void); + +#ifdef __cplusplus +} +#endif + +#endif /* XT3412_H__ */ diff --git a/lib/lte_link_control/lte_lc.c b/lib/lte_link_control/lte_lc.c index 34030a33c36..a804a0cf58b 100644 --- a/lib/lte_link_control/lte_lc.c +++ b/lib/lte_link_control/lte_lc.c @@ -4,755 +4,39 @@ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#include -#include #include -#include #include #include +#include +#include +#include #include +#include #include #include #include #include -#include -#include -#include -#include -#include "lte_lc_helpers.h" +#include "modules/edrx.h" +#include "modules/cereg.h" +#include "modules/cfun.h" +#include "modules/coneval.h" +#include "modules/cscon.h" +#include "modules/mdmev.h" +#include "modules/ncellmeas.h" +#include "modules/periodicsearchconf.h" +#include "modules/psm.h" +#include "modules/redmob.h" +#include "modules/xt3412.h" +#include "modules/xfactoryreset.h" +#include "modules/xmodemsleep.h" +#include "modules/xsystemmode.h" + +#include "common/work_q.h" +#include "common/event_handler_list.h" LOG_MODULE_REGISTER(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); -/* Internal system mode value used when CONFIG_LTE_NETWORK_MODE_DEFAULT is enabled. */ -#define LTE_LC_SYSTEM_MODE_DEFAULT 0xff - -#define SYS_MODE_PREFERRED \ - (IS_ENABLED(CONFIG_LTE_NETWORK_MODE_LTE_M) ? \ - LTE_LC_SYSTEM_MODE_LTEM : \ - IS_ENABLED(CONFIG_LTE_NETWORK_MODE_NBIOT) ? \ - LTE_LC_SYSTEM_MODE_NBIOT : \ - IS_ENABLED(CONFIG_LTE_NETWORK_MODE_LTE_M_GPS) ? \ - LTE_LC_SYSTEM_MODE_LTEM_GPS : \ - IS_ENABLED(CONFIG_LTE_NETWORK_MODE_NBIOT_GPS) ? \ - LTE_LC_SYSTEM_MODE_NBIOT_GPS : \ - IS_ENABLED(CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT) ? \ - LTE_LC_SYSTEM_MODE_LTEM_NBIOT : \ - IS_ENABLED(CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT_GPS) ? \ - LTE_LC_SYSTEM_MODE_LTEM_NBIOT_GPS : \ - LTE_LC_SYSTEM_MODE_DEFAULT) - -/* Length for eDRX and PTW values */ -#define LTE_LC_EDRX_VALUE_LEN 5 - -/* Internal enums */ - -enum feaconf_oper { - FEACONF_OPER_WRITE = 0, - FEACONF_OPER_READ = 1, - FEACONF_OPER_LIST = 2 -}; - -enum feaconf_feat { - FEACONF_FEAT_PROPRIETARY_PSM = 0 -}; - -/* Static variables */ - -/* Previously received LTE mode as indicated by the modem */ -static enum lte_lc_lte_mode prev_lte_mode = LTE_LC_LTE_MODE_NONE; -/* Requested eDRX state (enabled/disabled) */ -static bool requested_edrx_enable; -/* Requested eDRX setting */ -static char requested_edrx_value_ltem[LTE_LC_EDRX_VALUE_LEN] = CONFIG_LTE_EDRX_REQ_VALUE_LTE_M; -static char requested_edrx_value_nbiot[LTE_LC_EDRX_VALUE_LEN] = CONFIG_LTE_EDRX_REQ_VALUE_NBIOT; -/* Requested PTW setting */ -static char requested_ptw_value_ltem[LTE_LC_EDRX_VALUE_LEN] = CONFIG_LTE_PTW_VALUE_LTE_M; -static char requested_ptw_value_nbiot[LTE_LC_EDRX_VALUE_LEN] = CONFIG_LTE_PTW_VALUE_NBIOT; -/* Currently used eDRX setting as indicated by the modem */ -static char edrx_value_ltem[LTE_LC_EDRX_VALUE_LEN]; -static char edrx_value_nbiot[LTE_LC_EDRX_VALUE_LEN]; -/* Currently used PTW setting as indicated by the modem */ -static char ptw_value_ltem[LTE_LC_EDRX_VALUE_LEN]; -static char ptw_value_nbiot[LTE_LC_EDRX_VALUE_LEN]; -/* Requested PSM RAT setting */ -static char requested_psm_param_rat[9] = CONFIG_LTE_PSM_REQ_RAT; -/* Requested PSM RPTAU setting */ -static char requested_psm_param_rptau[9] = CONFIG_LTE_PSM_REQ_RPTAU; -/* Request PSM to be disabled and timers set to default values */ -static const char psm_disable[] = "AT+CPSMS="; -/* Enable CSCON (RRC mode) notifications */ -static const char cscon[] = "AT+CSCON=1"; - -/* Requested NCELLMEAS params */ -static struct lte_lc_ncellmeas_params ncellmeas_params; -/* Sempahore value 1 means ncellmeas is not ongoing, and 0 means it's ongoing. */ -K_SEM_DEFINE(ncellmeas_idle_sem, 1, 1); -/* Network attach semaphore */ -static K_SEM_DEFINE(link, 0, 1); - -/* The preferred system mode to use when connecting to LTE network. Can be changed by calling - * lte_lc_system_mode_set(). - * - * extern in lte_lc_modem_hooks.c - */ -enum lte_lc_system_mode lte_lc_sys_mode = SYS_MODE_PREFERRED; -/* System mode preference to set when configuring system mode. Can be changed by calling - * lte_lc_system_mode_set(). - * - * extern in lte_lc_modem_hooks.c - */ -enum lte_lc_system_mode_preference lte_lc_sys_mode_pref = CONFIG_LTE_MODE_PREFERENCE_VALUE; - -/* Parameters to be passed using AT%XSYSTEMMMODE=, */ -static const char *const system_mode_params[] = { - [LTE_LC_SYSTEM_MODE_LTEM] = "1,0,0", - [LTE_LC_SYSTEM_MODE_NBIOT] = "0,1,0", - [LTE_LC_SYSTEM_MODE_GPS] = "0,0,1", - [LTE_LC_SYSTEM_MODE_LTEM_GPS] = "1,0,1", - [LTE_LC_SYSTEM_MODE_NBIOT_GPS] = "0,1,1", - [LTE_LC_SYSTEM_MODE_LTEM_NBIOT] = "1,1,0", - [LTE_LC_SYSTEM_MODE_LTEM_NBIOT_GPS] = "1,1,1", -}; - -/* LTE preference to be passed using AT%XSYSTEMMMODE=, */ -static const char system_mode_preference[] = { - /* No LTE preference, automatically selected by the modem. */ - [LTE_LC_SYSTEM_MODE_PREFER_AUTO] = '0', - /* LTE-M has highest priority. */ - [LTE_LC_SYSTEM_MODE_PREFER_LTEM] = '1', - /* NB-IoT has highest priority. */ - [LTE_LC_SYSTEM_MODE_PREFER_NBIOT] = '2', - /* Equal priority, but prefer LTE-M. */ - [LTE_LC_SYSTEM_MODE_PREFER_LTEM_PLMN_PRIO] = '3', - /* Equal priority, but prefer NB-IoT. */ - [LTE_LC_SYSTEM_MODE_PREFER_NBIOT_PLMN_PRIO] = '4', -}; - -static void lte_lc_psm_get_work_fn(struct k_work *work_item); -K_WORK_DEFINE(lte_lc_psm_get_work, lte_lc_psm_get_work_fn); - -static void lte_lc_edrx_ptw_send_work_fn(struct k_work *work_item); -K_WORK_DEFINE(lte_lc_edrx_ptw_send_work, lte_lc_edrx_ptw_send_work_fn); - -K_THREAD_STACK_DEFINE(lte_lc_work_q_stack, CONFIG_LTE_LC_WORKQUEUE_STACK_SIZE); - -static struct k_work_q lte_lc_work_q; - -static bool is_cellid_valid(uint32_t cellid) -{ - if (cellid == LTE_LC_CELL_EUTRAN_ID_INVALID) { - return false; - } - - return true; -} - -static void lte_lc_evt_psm_update_send(struct lte_lc_psm_cfg *psm_cfg) -{ - static struct lte_lc_psm_cfg prev_psm_cfg; - struct lte_lc_evt evt = {0}; - - /* PSM configuration update event */ - if ((psm_cfg->tau != prev_psm_cfg.tau) || - (psm_cfg->active_time != prev_psm_cfg.active_time)) { - evt.type = LTE_LC_EVT_PSM_UPDATE; - - memcpy(&prev_psm_cfg, psm_cfg, sizeof(struct lte_lc_psm_cfg)); - memcpy(&evt.psm_cfg, psm_cfg, sizeof(struct lte_lc_psm_cfg)); - event_handler_list_dispatch(&evt); - } -} - -static void lte_lc_psm_get_work_fn(struct k_work *work_item) -{ - int err; - struct lte_lc_psm_cfg psm_cfg = { - .active_time = -1, - .tau = -1 - }; - - err = lte_lc_psm_get(&psm_cfg.tau, &psm_cfg.active_time); - if (err) { - if (err != -EBADMSG) { - LOG_ERR("Failed to get PSM information"); - } - return; - } - - lte_lc_evt_psm_update_send(&psm_cfg); -} - -static void lte_lc_edrx_current_values_clear(void) -{ - memset(edrx_value_ltem, 0, sizeof(edrx_value_ltem)); - memset(ptw_value_ltem, 0, sizeof(ptw_value_ltem)); - memset(edrx_value_nbiot, 0, sizeof(edrx_value_nbiot)); - memset(ptw_value_nbiot, 0, sizeof(ptw_value_nbiot)); -} - -static void lte_lc_edrx_values_store( - enum lte_lc_lte_mode mode, - char *edrx_value, - char *ptw_value) -{ - switch (mode) { - case LTE_LC_LTE_MODE_LTEM: - strcpy(edrx_value_ltem, edrx_value); - strcpy(ptw_value_ltem, ptw_value); - break; - case LTE_LC_LTE_MODE_NBIOT: - strcpy(edrx_value_nbiot, edrx_value); - strcpy(ptw_value_nbiot, ptw_value); - break; - default: - lte_lc_edrx_current_values_clear(); - break; - } -} - -static void lte_lc_edrx_ptw_send_work_fn(struct k_work *work_item) -{ - int err; - int actt[] = {AT_CEDRXS_ACTT_WB, AT_CEDRXS_ACTT_NB}; - - /* Apply the configurations for both LTE-M and NB-IoT. */ - for (size_t i = 0; i < ARRAY_SIZE(actt); i++) { - char *requested_ptw_value = (actt[i] == AT_CEDRXS_ACTT_WB) ? - requested_ptw_value_ltem : requested_ptw_value_nbiot; - char *ptw_value = (actt[i] == AT_CEDRXS_ACTT_WB) ? - ptw_value_ltem : ptw_value_nbiot; - - if (strlen(requested_ptw_value) == 4 && - strcmp(ptw_value, requested_ptw_value) != 0) { - - err = nrf_modem_at_printf( - "AT%%XPTW=%d,\"%s\"", actt[i], requested_ptw_value); - if (err) { - LOG_ERR("Failed to request PTW, reported error: %d", err); - } - } - } -} - -AT_MONITOR(ltelc_atmon_cereg, "+CEREG", at_handler_cereg); -AT_MONITOR(ltelc_atmon_cscon, "+CSCON", at_handler_cscon); -AT_MONITOR(ltelc_atmon_cedrxp, "+CEDRXP", at_handler_cedrxp); -AT_MONITOR(ltelc_atmon_xt3412, "%XT3412", at_handler_xt3412); -AT_MONITOR(ltelc_atmon_ncellmeas, "%NCELLMEAS", at_handler_ncellmeas); -AT_MONITOR(ltelc_atmon_xmodemsleep, "%XMODEMSLEEP", at_handler_xmodemsleep); -AT_MONITOR(ltelc_atmon_mdmev, "%MDMEV", at_handler_mdmev); -AT_MONITOR(ltelc_atmon_rai, "%RAI", at_handler_rai); - -static void at_handler_cereg(const char *response) -{ - int err; - struct lte_lc_evt evt = {0}; - - __ASSERT_NO_MSG(response != NULL); - - static enum lte_lc_nw_reg_status prev_reg_status = LTE_LC_NW_REG_NOT_REGISTERED; - static struct lte_lc_cell prev_cell; - enum lte_lc_nw_reg_status reg_status; - struct lte_lc_cell cell; - enum lte_lc_lte_mode lte_mode; - struct lte_lc_psm_cfg psm_cfg; - - LOG_DBG("+CEREG notification: %.*s", strlen(response) - strlen("\r\n"), response); - - err = parse_cereg(response, ®_status, &cell, <e_mode, &psm_cfg); - if (err) { - LOG_ERR("Failed to parse notification (error %d): %s", - err, response); - return; - } - - if ((reg_status == LTE_LC_NW_REG_REGISTERED_HOME) || - (reg_status == LTE_LC_NW_REG_REGISTERED_ROAMING)) { - /* Set the network registration status to UNKNOWN if the cell ID is parsed - * to UINT32_MAX (FFFFFFFF) when the registration status is either home or - * roaming. - */ - if (!is_cellid_valid(cell.id)) { - reg_status = LTE_LC_NW_REG_UNKNOWN; - } else { - k_sem_give(&link); - } - } - - switch (reg_status) { - case LTE_LC_NW_REG_NOT_REGISTERED: - LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_NOT_REGISTERED); - break; - case LTE_LC_NW_REG_REGISTERED_HOME: - LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_REGISTERED_HOME); - break; - case LTE_LC_NW_REG_SEARCHING: - LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_SEARCHING); - break; - case LTE_LC_NW_REG_REGISTRATION_DENIED: - LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_REGISTRATION_DENIED); - break; - case LTE_LC_NW_REG_UNKNOWN: - LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_UNKNOWN); - break; - case LTE_LC_NW_REG_REGISTERED_ROAMING: - LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_REGISTERED_ROAMING); - break; - case LTE_LC_NW_REG_UICC_FAIL: - LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_UICC_FAIL); - break; - default: - LOG_ERR("Unknown network registration status: %d", reg_status); - return; - } - - if (event_handler_list_is_empty()) { - return; - } - - /* Network registration status event */ - if (reg_status != prev_reg_status) { - prev_reg_status = reg_status; - evt.type = LTE_LC_EVT_NW_REG_STATUS; - evt.nw_reg_status = reg_status; - - event_handler_list_dispatch(&evt); - } - - /* Cell update event */ - if ((cell.id != prev_cell.id) || (cell.tac != prev_cell.tac)) { - evt.type = LTE_LC_EVT_CELL_UPDATE; - - memcpy(&prev_cell, &cell, sizeof(struct lte_lc_cell)); - memcpy(&evt.cell, &cell, sizeof(struct lte_lc_cell)); - event_handler_list_dispatch(&evt); - } - - if (lte_mode != prev_lte_mode) { - switch (lte_mode) { - case LTE_LC_LTE_MODE_LTEM: - LTE_LC_TRACE(LTE_LC_TRACE_LTE_MODE_UPDATE_LTEM); - break; - case LTE_LC_LTE_MODE_NBIOT: - LTE_LC_TRACE(LTE_LC_TRACE_LTE_MODE_UPDATE_NBIOT); - break; - case LTE_LC_LTE_MODE_NONE: - LTE_LC_TRACE(LTE_LC_TRACE_LTE_MODE_UPDATE_NONE); - break; - default: - LOG_ERR("Unknown LTE mode: %d", lte_mode); - return; - } - - prev_lte_mode = lte_mode; - evt.type = LTE_LC_EVT_LTE_MODE_UPDATE; - evt.lte_mode = lte_mode; - - event_handler_list_dispatch(&evt); - } - - if ((reg_status != LTE_LC_NW_REG_REGISTERED_HOME) && - (reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING)) { - return; - } - - if (psm_cfg.tau == -1) { - /* Need to get legacy T3412 value as TAU using AT%XMONITOR. - * - * As we are in an AT notification handler that is run from the system work queue, - * we shall not send AT commands here because another AT command might be ongoing, - * and the second command will be blocked until the first one completes. - * Further AT notifications from the modem will gradually exhaust AT monitor - * library's heap, and eventually it will run out causing an assert or - * AT notifications not being dispatched. - */ - k_work_submit_to_queue(<e_lc_work_q, <e_lc_psm_get_work); - return; - } - - lte_lc_evt_psm_update_send(&psm_cfg); -} - -static void at_handler_cscon(const char *response) -{ - int err; - struct lte_lc_evt evt = {0}; - - __ASSERT_NO_MSG(response != NULL); - - LOG_DBG("+CSCON notification"); - - err = parse_rrc_mode(response, &evt.rrc_mode, AT_CSCON_RRC_MODE_INDEX); - if (err) { - LOG_ERR("Can't parse signalling mode, error: %d", err); - return; - } - - if (evt.rrc_mode == LTE_LC_RRC_MODE_IDLE) { - LTE_LC_TRACE(LTE_LC_TRACE_RRC_IDLE); - } else if (evt.rrc_mode == LTE_LC_RRC_MODE_CONNECTED) { - LTE_LC_TRACE(LTE_LC_TRACE_RRC_CONNECTED); - } - - evt.type = LTE_LC_EVT_RRC_UPDATE; - - event_handler_list_dispatch(&evt); -} - -static void at_handler_cedrxp(const char *response) -{ - int err; - struct lte_lc_evt evt = {0}; - char edrx_value[LTE_LC_EDRX_VALUE_LEN] = {0}; - char ptw_value[LTE_LC_EDRX_VALUE_LEN] = {0}; - - __ASSERT_NO_MSG(response != NULL); - - LOG_DBG("+CEDRXP notification"); - - err = parse_edrx(response, &evt.edrx_cfg, edrx_value, ptw_value); - if (err) { - LOG_ERR("Can't parse eDRX, error: %d", err); - return; - } - - /* PTW must be requested after eDRX is enabled */ - lte_lc_edrx_values_store(evt.edrx_cfg.mode, edrx_value, ptw_value); - /* Send PTW setting if eDRX is enabled, i.e., we have network mode */ - if (evt.edrx_cfg.mode != LTE_LC_LTE_MODE_NONE) { - k_work_submit_to_queue(<e_lc_work_q, <e_lc_edrx_ptw_send_work); - } - evt.type = LTE_LC_EVT_EDRX_UPDATE; - - event_handler_list_dispatch(&evt); -} - -static void at_handler_xt3412(const char *response) -{ - int err; - struct lte_lc_evt evt = {0}; - - __ASSERT_NO_MSG(response != NULL); - - LOG_DBG("%%XT3412 notification"); - - err = parse_xt3412(response, &evt.time); - if (err) { - LOG_ERR("Can't parse TAU pre-warning notification, error: %d", err); - return; - } - - if (evt.time != CONFIG_LTE_LC_TAU_PRE_WARNING_TIME_MS) { - /* Only propagate TAU pre-warning notifications when the received time - * parameter is the duration of the set pre-warning time. - */ - return; - } - - evt.type = LTE_LC_EVT_TAU_PRE_WARNING; - - event_handler_list_dispatch(&evt); -} - -static void at_handler_ncellmeas_gci(const char *response) -{ - int err; - struct lte_lc_evt evt = {0}; - const char *resp = response; - struct lte_lc_cell *cells = NULL; - - __ASSERT_NO_MSG(response != NULL); - __ASSERT_NO_MSG(ncellmeas_params.gci_count != 0); - - LOG_DBG("%%NCELLMEAS GCI notification parsing starts"); - - cells = k_calloc(ncellmeas_params.gci_count, sizeof(struct lte_lc_cell)); - if (cells == NULL) { - LOG_ERR("Failed to allocate memory for the GCI cells"); - return; - } - - evt.cells_info.gci_cells = cells; - err = parse_ncellmeas_gci(&ncellmeas_params, resp, &evt.cells_info); - LOG_DBG("parse_ncellmeas_gci returned %d", err); - switch (err) { - case -E2BIG: - LOG_WRN("Not all neighbor cells could be parsed. " - "More cells than the configured max count of %d were found", - CONFIG_LTE_NEIGHBOR_CELLS_MAX); - /* Fall through */ - case 0: /* Fall through */ - case 1: - LOG_DBG("Neighbor cell count: %d, GCI cells count: %d", - evt.cells_info.ncells_count, - evt.cells_info.gci_cells_count); - evt.type = LTE_LC_EVT_NEIGHBOR_CELL_MEAS; - event_handler_list_dispatch(&evt); - break; - default: - LOG_ERR("Parsing of neighbor cells failed, err: %d", err); - break; - } - - k_free(cells); - k_free(evt.cells_info.neighbor_cells); -} - -static void at_handler_ncellmeas(const char *response) -{ - int err; - struct lte_lc_evt evt = {0}; - - __ASSERT_NO_MSG(response != NULL); - - if (event_handler_list_is_empty()) { - /* No need to parse the response if there is no handler - * to receive the parsed data. - */ - goto exit; - } - - if (ncellmeas_params.search_type > LTE_LC_NEIGHBOR_SEARCH_TYPE_EXTENDED_COMPLETE) { - at_handler_ncellmeas_gci(response); - goto exit; - } - - int ncell_count = neighborcell_count_get(response); - struct lte_lc_ncell *neighbor_cells = NULL; - - LOG_DBG("%%NCELLMEAS notification: neighbor cell count: %d", ncell_count); - - if (ncell_count != 0) { - neighbor_cells = k_calloc(ncell_count, sizeof(struct lte_lc_ncell)); - if (neighbor_cells == NULL) { - LOG_ERR("Failed to allocate memory for neighbor cells"); - goto exit; - } - } - - evt.cells_info.neighbor_cells = neighbor_cells; - - err = parse_ncellmeas(response, &evt.cells_info); - - switch (err) { - case -E2BIG: - LOG_WRN("Not all neighbor cells could be parsed"); - LOG_WRN("More cells than the configured max count of %d were found", - CONFIG_LTE_NEIGHBOR_CELLS_MAX); - /* Fall through */ - case 0: /* Fall through */ - case 1: - evt.type = LTE_LC_EVT_NEIGHBOR_CELL_MEAS; - event_handler_list_dispatch(&evt); - break; - default: - LOG_ERR("Parsing of neighbor cells failed, err: %d", err); - break; - } - - if (neighbor_cells) { - k_free(neighbor_cells); - } -exit: - k_sem_give(&ncellmeas_idle_sem); -} - -static void at_handler_xmodemsleep(const char *response) -{ - int err; - struct lte_lc_evt evt = {0}; - - __ASSERT_NO_MSG(response != NULL); - - LOG_DBG("%%XMODEMSLEEP notification"); - - err = parse_xmodemsleep(response, &evt.modem_sleep); - if (err) { - LOG_ERR("Can't parse modem sleep pre-warning notification, error: %d", err); - return; - } - - /* Link controller only supports PSM, RF inactivity, limited service, flight mode - * and proprietary PSM modem sleep types. - */ - if ((evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_PSM) && - (evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_RF_INACTIVITY) && - (evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_LIMITED_SERVICE) && - (evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_FLIGHT_MODE) && - (evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_PROPRIETARY_PSM)) { - return; - } - - /* Propagate the appropriate event depending on the parsed time parameter. */ - if (evt.modem_sleep.time == CONFIG_LTE_LC_MODEM_SLEEP_PRE_WARNING_TIME_MS) { - evt.type = LTE_LC_EVT_MODEM_SLEEP_EXIT_PRE_WARNING; - } else if (evt.modem_sleep.time == 0) { - LTE_LC_TRACE(LTE_LC_TRACE_MODEM_SLEEP_EXIT); - - evt.type = LTE_LC_EVT_MODEM_SLEEP_EXIT; - } else { - LTE_LC_TRACE(LTE_LC_TRACE_MODEM_SLEEP_ENTER); - - evt.type = LTE_LC_EVT_MODEM_SLEEP_ENTER; - } - - event_handler_list_dispatch(&evt); -} - -static void at_handler_mdmev(const char *response) -{ - int err; - struct lte_lc_evt evt = {0}; - - __ASSERT_NO_MSG(response != NULL); - - LOG_DBG("%%MDMEV notification"); - - err = parse_mdmev(response, &evt.modem_evt); - if (err) { - LOG_ERR("Can't parse modem event notification, error: %d", err); - return; - } - - evt.type = LTE_LC_EVT_MODEM_EVENT; - - event_handler_list_dispatch(&evt); -} - -static void at_handler_rai(const char *response) -{ - int err; - struct lte_lc_evt evt = {0}; - - __ASSERT_NO_MSG(response != NULL); - - LOG_DBG("%%RAI notification"); - - err = parse_rai(response, &evt.rai_cfg); - if (err) { - LOG_ERR("Can't parse RAI notification, error: %d", err); - return; - } - - evt.type = LTE_LC_EVT_RAI_UPDATE; - - event_handler_list_dispatch(&evt); -} - -static int enable_notifications(void) -{ - int err; - - /* +CEREG notifications, level 5 */ - err = nrf_modem_at_printf(AT_CEREG_5); - if (err) { - LOG_ERR("Failed to subscribe to CEREG notifications, error: %d", err); - return -EFAULT; - } - - if (IS_ENABLED(CONFIG_LTE_LC_TAU_PRE_WARNING_NOTIFICATIONS)) { - err = nrf_modem_at_printf(AT_XT3412_SUB, - CONFIG_LTE_LC_TAU_PRE_WARNING_TIME_MS, - CONFIG_LTE_LC_TAU_PRE_WARNING_THRESHOLD_MS); - if (err) { - LOG_WRN("Enabling TAU pre-warning notifications failed, error: %d", err); - LOG_WRN("TAU pre-warning notifications require nRF9160 modem >= v1.3.0"); - } - } - - if (IS_ENABLED(CONFIG_LTE_LC_MODEM_SLEEP_NOTIFICATIONS)) { - /* %XMODEMSLEEP notifications subscribe */ - err = nrf_modem_at_printf(AT_XMODEMSLEEP_SUB, - CONFIG_LTE_LC_MODEM_SLEEP_PRE_WARNING_TIME_MS, - CONFIG_LTE_LC_MODEM_SLEEP_NOTIFICATIONS_THRESHOLD_MS); - if (err) { - LOG_WRN("Enabling modem sleep notifications failed, error: %d", err); - LOG_WRN("Modem sleep notifications require nRF9160 modem >= v1.3.0"); - } - } - - /* +CSCON notifications */ - err = nrf_modem_at_printf(cscon); - if (err) { - LOG_WRN("Failed to enable RRC notifications (+CSCON), error %d", err); - return -EFAULT; - } - - return 0; -} - -static int connect_lte(bool blocking) -{ - int err; - enum lte_lc_func_mode original_func_mode; - bool func_mode_changed = false; - enum lte_lc_nw_reg_status reg_status; - static atomic_t in_progress; - - /* Check if a connection attempt is already in progress */ - if (atomic_set(&in_progress, 1)) { - LOG_WRN("Connect already in progress"); - return -EINPROGRESS; - } - - err = lte_lc_nw_reg_status_get(®_status); - if (err) { - LOG_ERR("Failed to get current registration status"); - err = -EFAULT; - goto exit; - } - - /* Do not attempt to register with an LTE network if the device already is registered. - * This check is needed for blocking _connect() calls to avoid hanging for - * CONFIG_LTE_NETWORK_TIMEOUT seconds waiting for a semaphore that will not be given. - */ - if ((reg_status == LTE_LC_NW_REG_REGISTERED_HOME) || - (reg_status == LTE_LC_NW_REG_REGISTERED_ROAMING)) { - LOG_DBG("The device is already registered with an LTE network"); - - err = 0; - goto exit; - } - - err = lte_lc_func_mode_get(&original_func_mode); - if (err) { - err = -EFAULT; - goto exit; - } - - /* Reset the semaphore, it may have already been given by an earlier +CEREG notification. */ - k_sem_reset(&link); - - err = lte_lc_func_mode_set(LTE_LC_FUNC_MODE_NORMAL); - if (err || !blocking) { - goto exit; - } - - func_mode_changed = true; - - err = k_sem_take(&link, K_SECONDS(CONFIG_LTE_NETWORK_TIMEOUT)); - if (err == -EAGAIN) { - LOG_INF("Network connection attempt timed out"); - err = -ETIMEDOUT; - } - -exit: - if (err && func_mode_changed) { - /* Connecting to LTE network failed, restore original functional mode. */ - lte_lc_func_mode_set(original_func_mode); - } - - atomic_clear(&in_progress); - - return err; -} - -static int feaconf_write(enum feaconf_feat feat, bool state) -{ - return nrf_modem_at_printf("AT%%FEACONF=%d,%d,%u", FEACONF_OPER_WRITE, feat, state); -} - /* Public API */ void lte_lc_register_handler(lte_lc_evt_handler_t handler) @@ -763,7 +47,7 @@ void lte_lc_register_handler(lte_lc_evt_handler_t handler) return; } - event_handler_list_append_handler(handler); + event_handler_list_handler_append(handler); } int lte_lc_deregister_handler(lte_lc_evt_handler_t handler) @@ -773,1090 +57,183 @@ int lte_lc_deregister_handler(lte_lc_evt_handler_t handler) return -EINVAL; } - return event_handler_list_remove_handler(handler); + return event_handler_list_handler_remove(handler); } int lte_lc_connect(void) { LOG_DBG("Connecting synchronously"); - return connect_lte(true); + return cereg_lte_connect(true); } int lte_lc_connect_async(lte_lc_evt_handler_t handler) { LOG_DBG("Connecting asynchronously"); if (handler) { - event_handler_list_append_handler(handler); + event_handler_list_handler_append(handler); } else if (event_handler_list_is_empty()) { LOG_ERR("No handler registered"); return -EINVAL; } - return connect_lte(false); + return cereg_lte_connect(false); } int lte_lc_normal(void) { - return lte_lc_func_mode_set(LTE_LC_FUNC_MODE_NORMAL) ? -EFAULT : 0; + return cfun_mode_set(LTE_LC_FUNC_MODE_NORMAL) ? -EFAULT : 0; } int lte_lc_offline(void) { - return lte_lc_func_mode_set(LTE_LC_FUNC_MODE_OFFLINE) ? -EFAULT : 0; + return cfun_mode_set(LTE_LC_FUNC_MODE_OFFLINE) ? -EFAULT : 0; } int lte_lc_power_off(void) { - return lte_lc_func_mode_set(LTE_LC_FUNC_MODE_POWER_OFF) ? -EFAULT : 0; + return cfun_mode_set(LTE_LC_FUNC_MODE_POWER_OFF) ? -EFAULT : 0; } int lte_lc_psm_param_set(const char *rptau, const char *rat) { - if ((rptau != NULL && strlen(rptau) != 8) || - (rat != NULL && strlen(rat) != 8)) { - return -EINVAL; - } - - if (rptau != NULL) { - strcpy(requested_psm_param_rptau, rptau); - LOG_DBG("RPTAU set to %s", requested_psm_param_rptau); - } else { - *requested_psm_param_rptau = '\0'; - LOG_DBG("Using modem default value for RPTAU"); - } - - if (rat != NULL) { - strcpy(requested_psm_param_rat, rat); - LOG_DBG("RAT set to %s", requested_psm_param_rat); - } else { - *requested_psm_param_rat = '\0'; - LOG_DBG("Using modem default value for RAT"); - } - - return 0; + return psm_param_set(rptau, rat); } int lte_lc_psm_param_set_seconds(int rptau, int rat) { - int ret; - - ret = encode_psm(requested_psm_param_rptau, requested_psm_param_rat, rptau, rat); - - if (ret != 0) { - *requested_psm_param_rptau = '\0'; - *requested_psm_param_rat = '\0'; - } - - LOG_DBG("RPTAU=%d (%s), RAT=%d (%s), ret=%d", - rptau, requested_psm_param_rptau, rat, requested_psm_param_rat, ret); - - return ret; + return psm_param_set_seconds(rptau, rat); } int lte_lc_psm_req(bool enable) { - int err; - - LOG_DBG("enable=%d, tau=%s, rat=%s", - enable, requested_psm_param_rptau, requested_psm_param_rat); - - if (enable) { - if (strlen(requested_psm_param_rptau) == 8 && - strlen(requested_psm_param_rat) == 8) { - err = nrf_modem_at_printf("AT+CPSMS=1,,,\"%s\",\"%s\"", - requested_psm_param_rptau, - requested_psm_param_rat); - } else if (strlen(requested_psm_param_rptau) == 8) { - err = nrf_modem_at_printf("AT+CPSMS=1,,,\"%s\"", requested_psm_param_rptau); - } else if (strlen(requested_psm_param_rat) == 8) { - err = nrf_modem_at_printf("AT+CPSMS=1,,,,\"%s\"", requested_psm_param_rat); - } else { - err = nrf_modem_at_printf("AT+CPSMS=1"); - } - } else { - err = nrf_modem_at_printf(psm_disable); - } - - if (err) { - LOG_ERR("nrf_modem_at_printf failed, reported error: %d", err); - return -EFAULT; - } - - return 0; + return psm_req(enable); } int lte_lc_psm_get(int *tau, int *active_time) { - int err; - struct lte_lc_psm_cfg psm_cfg; - struct at_parser parser; - char active_time_str[9] = {0}; - char tau_ext_str[9] = {0}; - char tau_legacy_str[9] = {0}; - int len; - int reg_status = 0; - static char response[160] = { 0 }; - - if ((tau == NULL) || (active_time == NULL)) { - return -EINVAL; - } - - /* Format of XMONITOR AT command response: - * %XMONITOR: ,[,,,,,,, - * ,,,,,, - * ,] - * We need to parse the three last parameters, Active-Time, Periodic-TAU-ext and - * Periodic-TAU. - */ - - response[0] = '\0'; - - err = nrf_modem_at_cmd(response, sizeof(response), "AT%%XMONITOR"); - if (err) { - LOG_ERR("AT command failed, error: %d", err); - return -EFAULT; - } - - err = at_parser_init(&parser, response); - __ASSERT_NO_MSG(err == 0); - - /* Check registration status */ - err = at_parser_num_get(&parser, 1, ®_status); - if (err) { - LOG_ERR("AT command parsing failed, error: %d", err); - return -EBADMSG; - } else if (reg_status != LTE_LC_NW_REG_REGISTERED_HOME && - reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING) { - LOG_WRN("No PSM parameters because device not registered, status: %d", reg_status); - return -EBADMSG; - } - - /* */ - len = sizeof(active_time_str); - err = at_parser_string_get(&parser, 14, active_time_str, &len); - if (err) { - LOG_ERR("AT command parsing failed, error: %d", err); - return -EBADMSG; - } - - /* */ - len = sizeof(tau_ext_str); - err = at_parser_string_get(&parser, 15, tau_ext_str, &len); - if (err) { - LOG_ERR("AT command parsing failed, error: %d", err); - return -EBADMSG; - } - - /* */ - len = sizeof(tau_legacy_str); - err = at_parser_string_get(&parser, 16, tau_legacy_str, &len); - if (err) { - LOG_ERR("AT command parsing failed, error: %d", err); - return -EBADMSG; - } - - err = parse_psm(active_time_str, tau_ext_str, tau_legacy_str, &psm_cfg); - if (err) { - LOG_ERR("Failed to parse PSM configuration, error: %d", err); - return -EBADMSG; - } - - *tau = psm_cfg.tau; - *active_time = psm_cfg.active_time; - - LOG_DBG("TAU: %d sec, active time: %d sec", *tau, *active_time); - - return 0; + return psm_get(tau, active_time); } int lte_lc_proprietary_psm_req(bool enable) { - if (feaconf_write(FEACONF_FEAT_PROPRIETARY_PSM, enable) != 0) { - return -EFAULT; - } - - return 0; + return psm_proprietary_req(enable); } int lte_lc_edrx_param_set(enum lte_lc_lte_mode mode, const char *edrx) { - char *edrx_value; - - if (mode != LTE_LC_LTE_MODE_LTEM && mode != LTE_LC_LTE_MODE_NBIOT) { - LOG_ERR("LTE mode must be LTE-M or NB-IoT"); - return -EINVAL; - } - - if (edrx != NULL && strlen(edrx) != 4) { - return -EINVAL; - } - - edrx_value = (mode == LTE_LC_LTE_MODE_LTEM) ? requested_edrx_value_ltem : - requested_edrx_value_nbiot; - - if (edrx) { - strcpy(edrx_value, edrx); - LOG_DBG("eDRX set to %s for %s", edrx_value, - (mode == LTE_LC_LTE_MODE_LTEM) ? "LTE-M" : "NB-IoT"); - } else { - *edrx_value = '\0'; - LOG_DBG("eDRX use default for %s", - (mode == LTE_LC_LTE_MODE_LTEM) ? "LTE-M" : "NB-IoT"); - } - - return 0; + return edrx_param_set(mode, edrx); } int lte_lc_ptw_set(enum lte_lc_lte_mode mode, const char *ptw) { - char *ptw_value; - - if (mode != LTE_LC_LTE_MODE_LTEM && mode != LTE_LC_LTE_MODE_NBIOT) { - LOG_ERR("LTE mode must be LTE-M or NB-IoT"); - return -EINVAL; - } - - if (ptw != NULL && strlen(ptw) != 4) { - return -EINVAL; - } - - ptw_value = (mode == LTE_LC_LTE_MODE_LTEM) ? requested_ptw_value_ltem : - requested_ptw_value_nbiot; - - if (ptw != NULL) { - strcpy(ptw_value, ptw); - LOG_DBG("PTW set to %s for %s", ptw_value, - (mode == LTE_LC_LTE_MODE_LTEM) ? "LTE-M" : "NB-IoT"); - } else { - *ptw_value = '\0'; - LOG_DBG("PTW use default for %s", - (mode == LTE_LC_LTE_MODE_LTEM) ? "LTE-M" : "NB-IoT"); - } - - return 0; + return edrx_ptw_set(mode, ptw); } int lte_lc_edrx_req(bool enable) { - int err = 0; - int actt[] = {AT_CEDRXS_ACTT_WB, AT_CEDRXS_ACTT_NB}; - - LOG_DBG("enable=%d, " - "requested_edrx_value_ltem=%s, edrx_value_ltem=%s, " - "requested_ptw_value_ltem=%s, ptw_value_ltem=%s, ", - enable, - requested_edrx_value_ltem, edrx_value_ltem, - requested_ptw_value_ltem, ptw_value_ltem); - LOG_DBG("enable=%d, " - "requested_edrx_value_nbiot=%s, edrx_value_nbiot=%s, " - "requested_ptw_value_nbiot=%s, ptw_value_nbiot=%s", - enable, - requested_edrx_value_nbiot, edrx_value_nbiot, - requested_ptw_value_nbiot, ptw_value_nbiot); - - requested_edrx_enable = enable; - - if (!enable) { - err = nrf_modem_at_printf("AT+CEDRXS=3"); - if (err) { - LOG_ERR("Failed to disable eDRX, reported error: %d", err); - return -EFAULT; - } - lte_lc_edrx_current_values_clear(); - - return 0; - } - - /* Apply the configurations for both LTE-M and NB-IoT. */ - for (size_t i = 0; i < ARRAY_SIZE(actt); i++) { - char *requested_edrx_value = (actt[i] == AT_CEDRXS_ACTT_WB) ? - requested_edrx_value_ltem : requested_edrx_value_nbiot; - char *edrx_value = (actt[i] == AT_CEDRXS_ACTT_WB) ? - edrx_value_ltem : edrx_value_nbiot; - - if (strlen(requested_edrx_value) == 4) { - if (strcmp(edrx_value, requested_edrx_value) != 0) { - err = nrf_modem_at_printf( - "AT+CEDRXS=2,%d,\"%s\"", - actt[i], - requested_edrx_value); - } else { - /* If current eDRX value is equal to requested value, set PTW */ - lte_lc_edrx_ptw_send_work_fn(NULL); - } - } else { - err = nrf_modem_at_printf("AT+CEDRXS=2,%d", actt[i]); - } - - if (err) { - LOG_ERR("Failed to enable eDRX, reported error: %d", err); - return -EFAULT; - } - } - - return 0; + return edrx_request(enable); } int lte_lc_edrx_get(struct lte_lc_edrx_cfg *edrx_cfg) { - int err; - char response[48]; - char edrx_value[LTE_LC_EDRX_VALUE_LEN] = {0}; - char ptw_value[LTE_LC_EDRX_VALUE_LEN] = {0}; - - if (edrx_cfg == NULL) { - return -EINVAL; - } - - err = nrf_modem_at_cmd(response, sizeof(response), "AT+CEDRXRDP"); - if (err) { - LOG_ERR("Failed to request eDRX parameters, error: %d", err); - return -EFAULT; - } - - err = parse_edrx(response, edrx_cfg, edrx_value, ptw_value); - if (err) { - LOG_ERR("Failed to parse eDRX parameters, error: %d", err); - return -EBADMSG; - } - - lte_lc_edrx_values_store(edrx_cfg->mode, edrx_value, ptw_value); - - return 0; -} - -#if defined(CONFIG_UNITY) -void lte_lc_edrx_on_modem_cfun(int mode, void *ctx) -#else -NRF_MODEM_LIB_ON_CFUN(lte_lc_edrx_cfun_hook, lte_lc_edrx_on_modem_cfun, NULL); - -static void lte_lc_edrx_on_modem_cfun(int mode, void *ctx) -#endif -{ - ARG_UNUSED(ctx); - - /* If eDRX is enabled and modem is powered off, subscription of unsolicited eDRX - * notifications must be re-newed because modem forgets that information - * although it stores eDRX value and PTW for both system modes. - */ - if (mode == LTE_LC_FUNC_MODE_POWER_OFF && requested_edrx_enable) { - lte_lc_edrx_current_values_clear(); - /* We want to avoid sending AT commands in the callback. However, - * when modem is powered off, we are not expecting AT notifications - * that could cause an assertion or missing notification. - */ - lte_lc_edrx_req(requested_edrx_enable); - } + return edrx_cfg_get(edrx_cfg); } int lte_lc_nw_reg_status_get(enum lte_lc_nw_reg_status *status) { - int err; - uint16_t status_tmp; - uint32_t cell_id = 0; - - if (status == NULL) { - return -EINVAL; - } - - /* Read network registration status */ - err = nrf_modem_at_scanf("AT+CEREG?", - "+CEREG: " - "%*u," /* */ - "%hu," /* */ - "%*[^,]," /* */ - "\"%x\",", /* */ - &status_tmp, - &cell_id); - if (err < 1) { - LOG_ERR("Could not get registration status, error: %d", err); - return -EFAULT; - } - - if (!is_cellid_valid(cell_id)) { - *status = LTE_LC_NW_REG_UNKNOWN; - } else { - *status = status_tmp; - } - - return 0; + return cereg_status_get(status); } int lte_lc_system_mode_set(enum lte_lc_system_mode mode, enum lte_lc_system_mode_preference preference) { - int err; - - switch (mode) { - case LTE_LC_SYSTEM_MODE_LTEM: - case LTE_LC_SYSTEM_MODE_LTEM_GPS: - case LTE_LC_SYSTEM_MODE_NBIOT: - case LTE_LC_SYSTEM_MODE_NBIOT_GPS: - case LTE_LC_SYSTEM_MODE_GPS: - case LTE_LC_SYSTEM_MODE_LTEM_NBIOT: - case LTE_LC_SYSTEM_MODE_LTEM_NBIOT_GPS: - break; - default: - LOG_ERR("Invalid system mode requested: %d", mode); - return -EINVAL; - } - - switch (preference) { - case LTE_LC_SYSTEM_MODE_PREFER_AUTO: - case LTE_LC_SYSTEM_MODE_PREFER_LTEM: - case LTE_LC_SYSTEM_MODE_PREFER_NBIOT: - case LTE_LC_SYSTEM_MODE_PREFER_LTEM_PLMN_PRIO: - case LTE_LC_SYSTEM_MODE_PREFER_NBIOT_PLMN_PRIO: - break; - default: - LOG_ERR("Invalid LTE preference requested: %d", preference); - return -EINVAL; - } - - err = nrf_modem_at_printf("AT%%XSYSTEMMODE=%s,%c", - system_mode_params[mode], - system_mode_preference[preference]); - if (err) { - LOG_ERR("Could not send AT command, error: %d", err); - return -EFAULT; - } - - lte_lc_sys_mode = mode; - lte_lc_sys_mode_pref = preference; - - LOG_DBG("System mode set to %d, preference %d", lte_lc_sys_mode, lte_lc_sys_mode_pref); - - return 0; + return xsystemmode_mode_set(mode, preference); } int lte_lc_system_mode_get(enum lte_lc_system_mode *mode, enum lte_lc_system_mode_preference *preference) { - int err; - int mode_bitmask = 0; - int ltem_mode = 0; - int nbiot_mode = 0; - int gps_mode = 0; - int mode_preference = 0; - - if (mode == NULL) { - return -EINVAL; - } - - /* It's expected to have all 4 arguments matched */ - err = nrf_modem_at_scanf(AT_XSYSTEMMODE_READ, "%%XSYSTEMMODE: %d,%d,%d,%d", - <em_mode, &nbiot_mode, &gps_mode, &mode_preference); - if (err != 4) { - LOG_ERR("Failed to get system mode, error: %d", err); - return -EFAULT; - } - - mode_bitmask = (ltem_mode ? BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX) : 0) | - (nbiot_mode ? BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX) : 0) | - (gps_mode ? BIT(AT_XSYSTEMMODE_READ_GPS_INDEX) : 0); - - switch (mode_bitmask) { - case BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX): - *mode = LTE_LC_SYSTEM_MODE_LTEM; - break; - case BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX): - *mode = LTE_LC_SYSTEM_MODE_NBIOT; - break; - case BIT(AT_XSYSTEMMODE_READ_GPS_INDEX): - *mode = LTE_LC_SYSTEM_MODE_GPS; - break; - case (BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX) | BIT(AT_XSYSTEMMODE_READ_GPS_INDEX)): - *mode = LTE_LC_SYSTEM_MODE_LTEM_GPS; - break; - case (BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX) | BIT(AT_XSYSTEMMODE_READ_GPS_INDEX)): - *mode = LTE_LC_SYSTEM_MODE_NBIOT_GPS; - break; - case (BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX) | BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX)): - *mode = LTE_LC_SYSTEM_MODE_LTEM_NBIOT; - break; - case (BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX) | - BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX) | - BIT(AT_XSYSTEMMODE_READ_GPS_INDEX)): - *mode = LTE_LC_SYSTEM_MODE_LTEM_NBIOT_GPS; - break; - default: - LOG_ERR("Invalid system mode, assuming parsing error"); - return -EFAULT; - } - - /* Get LTE preference. */ - if (preference != NULL) { - switch (mode_preference) { - case 0: - *preference = LTE_LC_SYSTEM_MODE_PREFER_AUTO; - break; - case 1: - *preference = LTE_LC_SYSTEM_MODE_PREFER_LTEM; - break; - case 2: - *preference = LTE_LC_SYSTEM_MODE_PREFER_NBIOT; - break; - case 3: - *preference = LTE_LC_SYSTEM_MODE_PREFER_LTEM_PLMN_PRIO; - break; - case 4: - *preference = LTE_LC_SYSTEM_MODE_PREFER_NBIOT_PLMN_PRIO; - break; - default: - LOG_ERR("Unsupported LTE preference: %d", mode_preference); - return -EFAULT; - } - } - - return 0; + return xsystemmode_mode_get(mode, preference); } int lte_lc_func_mode_get(enum lte_lc_func_mode *mode) { - int err; - uint16_t mode_tmp; - - if (mode == NULL) { - return -EINVAL; - } - - /* Exactly one parameter is expected to match. */ - err = nrf_modem_at_scanf(AT_CFUN_READ, "+CFUN: %hu", &mode_tmp); - if (err != 1) { - LOG_ERR("AT command failed, nrf_modem_at_scanf() returned error: %d", err); - return -EFAULT; - } - - *mode = mode_tmp; - - return 0; + return cfun_mode_get(mode); } int lte_lc_func_mode_set(enum lte_lc_func_mode mode) { - int err; - - switch (mode) { - case LTE_LC_FUNC_MODE_ACTIVATE_LTE: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_ACTIVATE_LTE); - - err = enable_notifications(); - if (err) { - LOG_ERR("Failed to enable notifications, error: %d", err); - return -EFAULT; - } - - break; - case LTE_LC_FUNC_MODE_NORMAL: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_NORMAL); - - err = enable_notifications(); - if (err) { - LOG_ERR("Failed to enable notifications, error: %d", err); - return -EFAULT; - } - - break; - case LTE_LC_FUNC_MODE_POWER_OFF: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_POWER_OFF); - break; - case LTE_LC_FUNC_MODE_RX_ONLY: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_RX_ONLY); - break; - case LTE_LC_FUNC_MODE_OFFLINE: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_OFFLINE); - break; - case LTE_LC_FUNC_MODE_DEACTIVATE_LTE: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_DEACTIVATE_LTE); - break; - case LTE_LC_FUNC_MODE_DEACTIVATE_GNSS: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_DEACTIVATE_GNSS); - break; - case LTE_LC_FUNC_MODE_ACTIVATE_GNSS: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_ACTIVATE_GNSS); - break; - case LTE_LC_FUNC_MODE_DEACTIVATE_UICC: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_DEACTIVATE_UICC); - break; - case LTE_LC_FUNC_MODE_ACTIVATE_UICC: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_ACTIVATE_UICC); - break; - case LTE_LC_FUNC_MODE_OFFLINE_UICC_ON: - LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_OFFLINE_UICC_ON); - break; - default: - LOG_ERR("Invalid functional mode: %d", mode); - return -EINVAL; - } - - err = nrf_modem_at_printf("AT+CFUN=%d", mode); - if (err) { - LOG_ERR("Failed to set functional mode. Please check XSYSTEMMODE."); - return -EFAULT; - } - - LOG_DBG("Functional mode set to %d", mode); - - return 0; + return cfun_mode_set(mode); } int lte_lc_lte_mode_get(enum lte_lc_lte_mode *mode) { - int err; - uint16_t mode_tmp; - - if (mode == NULL) { - return -EINVAL; - } - - err = nrf_modem_at_scanf(AT_CEREG_READ, - "+CEREG: " - "%*u," /* */ - "%*u," /* */ - "%*[^,]," /* */ - "%*[^,]," /* */ - "%hu", /* */ - &mode_tmp); - if (err == -NRF_EBADMSG) { - /* The AT command was successful, but there were no matches. - * This is not an error, but the LTE mode is unknown. - */ - *mode = LTE_LC_LTE_MODE_NONE; - - return 0; - } else if (err < 1) { - LOG_ERR("Could not get the LTE mode, error: %d", err); - return -EFAULT; - } - - *mode = mode_tmp; - - switch (*mode) { - case LTE_LC_LTE_MODE_NONE: - case LTE_LC_LTE_MODE_LTEM: - case LTE_LC_LTE_MODE_NBIOT: - break; - default: - return -EBADMSG; - } - - return 0; + return cereg_mode_get(mode); } int lte_lc_neighbor_cell_measurement(struct lte_lc_ncellmeas_params *params) { - int err; - /* lte_lc defaults for the used params */ - struct lte_lc_ncellmeas_params used_params = { - .search_type = LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT, - .gci_count = 0, - }; - - if (params == NULL) { - LOG_DBG("Using default parameters"); - } - LOG_DBG("Search type=%d, gci_count=%d", - params != NULL ? params->search_type : used_params.search_type, - params != NULL ? params->gci_count : used_params.gci_count); - - __ASSERT(!IN_RANGE( - (int)params, - LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT, - LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_COMPLETE), - "Invalid argument, API does not accept enum values directly anymore"); - - if (params != NULL && - (params->search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_DEFAULT || - params->search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_LIGHT || - params->search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_COMPLETE)) { - if (params->gci_count < 2 || params->gci_count > 15) { - LOG_ERR("Invalid GCI count, must be in range 2-15"); - return -EINVAL; - } - } - - if (k_sem_take(&ncellmeas_idle_sem, K_SECONDS(1)) != 0) { - LOG_WRN("Neighbor cell measurement already in progress"); - return -EINPROGRESS; - } - - if (params != NULL) { - used_params = *params; - } - ncellmeas_params = used_params; - - /* Starting from modem firmware v1.3.1, there is an optional parameter to specify - * the type of search. - * If the type is LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT, we therefore use the AT - * command without parameters to avoid error messages for older firmware version. - * Starting from modem firmware v1.3.4, additional CGI search types and - * GCI count are supported. - */ - if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_EXTENDED_LIGHT) { - err = nrf_modem_at_printf("AT%%NCELLMEAS=1"); - } else if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_EXTENDED_COMPLETE) { - err = nrf_modem_at_printf("AT%%NCELLMEAS=2"); - } else if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_DEFAULT) { - err = nrf_modem_at_printf("AT%%NCELLMEAS=3,%d", used_params.gci_count); - } else if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_LIGHT) { - err = nrf_modem_at_printf("AT%%NCELLMEAS=4,%d", used_params.gci_count); - } else if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_COMPLETE) { - err = nrf_modem_at_printf("AT%%NCELLMEAS=5,%d", used_params.gci_count); - } else { - /* Defaulting to use LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT */ - err = nrf_modem_at_printf("AT%%NCELLMEAS"); - } - - if (err) { - LOG_ERR("Sending AT%%NCELLMEAS failed, error: %d", err); - err = -EFAULT; - k_sem_give(&ncellmeas_idle_sem); - } - - return err; + return ncellmeas_start(params); } int lte_lc_neighbor_cell_measurement_cancel(void) { - LOG_DBG("Cancelling"); - - int err = nrf_modem_at_printf(AT_NCELLMEAS_STOP); - - if (err) { - err = -EFAULT; - } - - k_sem_give(&ncellmeas_idle_sem); - - return err; + return ncellmeas_cancel(); } int lte_lc_conn_eval_params_get(struct lte_lc_conn_eval_params *params) { - int err; - enum lte_lc_func_mode mode; - int result; - /* PLMN field is a string of maximum 6 characters, plus null termination. */ - char plmn_str[7] = {0}; - uint16_t rrc_state_tmp, energy_estimate_tmp, tau_trig_tmp, ce_level_tmp; - - if (params == NULL) { - return -EINVAL; - } - - err = lte_lc_func_mode_get(&mode); - if (err) { - LOG_ERR("Could not get functional mode"); - return -EFAULT; - } - - switch (mode) { - case LTE_LC_FUNC_MODE_NORMAL: - case LTE_LC_FUNC_MODE_ACTIVATE_LTE: - case LTE_LC_FUNC_MODE_RX_ONLY: - break; - default: - LOG_WRN("Connection evaluation is not available in the current functional mode"); - return -EOPNOTSUPP; - } - - /* AT%CONEVAL response format, from nRF91 AT Commands - Command Reference Guide, v1.7: - * - * %CONEVAL: [,,,,,,,, - * ,,,,,,, - * ,] - * - * In total, 17 parameters are expected to match for a successful command response. - */ - err = nrf_modem_at_scanf( - AT_CONEVAL_READ, - "%%CONEVAL: " - "%d," /* */ - "%hu," /* */ - "%hu," /* */ - "%hd," /* */ - "%hd," /* */ - "%hd," /* */ - "\"%x\"," /* */ - "\"%6[^\"]\"," /* */ - "%hd," /* */ - "%d," /* */ - "%hd," /* */ - "%hu," /* */ - "%hu," /* */ - "%hd," /* */ - "%hd," /* */ - "%hd," /* */ - "%hd", /* */ - &result, &rrc_state_tmp, &energy_estimate_tmp, - ¶ms->rsrp, ¶ms->rsrq, ¶ms->snr, ¶ms->cell_id, - plmn_str, ¶ms->phy_cid, ¶ms->earfcn, ¶ms->band, - &tau_trig_tmp, &ce_level_tmp, ¶ms->tx_power, - ¶ms->tx_rep, ¶ms->rx_rep, ¶ms->dl_pathloss); - if (err < 0) { - LOG_ERR("AT command failed, error: %d", err); - return -EFAULT; - } else if (result != 0) { - LOG_WRN("Connection evaluation failed with reason: %d", result); - return result; - } else if (err != 17) { - LOG_ERR("AT command parsing failed, error: %d", err); - return -EBADMSG; - } - - params->rrc_state = rrc_state_tmp; - params->energy_estimate = energy_estimate_tmp; - params->tau_trig = tau_trig_tmp; - params->ce_level = ce_level_tmp; - - /* Read MNC and store as integer. The MNC starts as the fourth character - * in the string, following three characters long MCC. - */ - err = string_to_int(&plmn_str[3], 10, ¶ms->mnc); - if (err) { - return -EBADMSG; - } - - /* Null-terminated MCC, read and store it. */ - plmn_str[3] = '\0'; - - err = string_to_int(plmn_str, 10, ¶ms->mcc); - if (err) { - return -EBADMSG; - } - - return 0; + return coneval_params_get(params); } int lte_lc_modem_events_enable(void) { - /* First try to enable both warning and informational type events, which is only supported - * by modem firmware versions >= 2.0.0. - * If that fails, try to enable the legacy set of events. - */ - if (nrf_modem_at_printf(AT_MDMEV_ENABLE_2)) { - if (nrf_modem_at_printf(AT_MDMEV_ENABLE_1)) { - return -EFAULT; - } - } - - return 0; + return mdmev_enable(); } int lte_lc_modem_events_disable(void) { - return nrf_modem_at_printf(AT_MDMEV_DISABLE) ? -EFAULT : 0; + return mdmev_disable(); } int lte_lc_periodic_search_set(const struct lte_lc_periodic_search_cfg *const cfg) { - int err; - char pattern_buf[4][40]; - - if (!cfg || (cfg->pattern_count == 0) || (cfg->pattern_count > 4)) { - return -EINVAL; - } - - /* Command syntax: - * AT%PERIODICSEARCHCONF=[,,,, - * [,][,][,]] - */ - - err = nrf_modem_at_printf( - "AT%%PERIODICSEARCHCONF=0," /* Write mode */ - "%hu," /* */ - "%hu," /* */ - "%hu," /* */ - "%s%s" /* */ - "%s%s" /* */ - "%s%s" /* */ - "%s", /* */ - cfg->loop, cfg->return_to_pattern, cfg->band_optimization, - /* Pattern 1 */ - periodic_search_pattern_get(pattern_buf[0], sizeof(pattern_buf[0]), - &cfg->patterns[0]), - /* Pattern 2, if configured */ - cfg->pattern_count > 1 ? "," : "", - cfg->pattern_count > 1 ? periodic_search_pattern_get(pattern_buf[1], - sizeof(pattern_buf[1]), &cfg->patterns[1]) : "", - /* Pattern 3, if configured */ - cfg->pattern_count > 2 ? "," : "", - cfg->pattern_count > 2 ? periodic_search_pattern_get(pattern_buf[2], - sizeof(pattern_buf[2]), &cfg->patterns[2]) : "", - /* Pattern 4, if configured */ - cfg->pattern_count > 3 ? "," : "", - cfg->pattern_count > 3 ? periodic_search_pattern_get(pattern_buf[3], - sizeof(pattern_buf[3]), &cfg->patterns[3]) : "" - ); - if (err < 0) { - /* Failure to send the AT command. */ - LOG_ERR("AT command failed, returned error code: %d", err); - return -EFAULT; - } else if (err > 0) { - /* The modem responded with "ERROR". */ - LOG_ERR("The modem rejected the configuration, err: %d", err); - return -EBADMSG; - } - - return 0; + return periodicsearchconf_set(cfg); } int lte_lc_periodic_search_clear(void) { - int err; - - err = nrf_modem_at_printf("AT%%PERIODICSEARCHCONF=2"); - if (err < 0) { - return -EFAULT; - } else if (err > 0) { - return -EBADMSG; - } - - return 0; + return periodicsearchconf_clear(); } int lte_lc_periodic_search_request(void) { - return nrf_modem_at_printf("AT%%PERIODICSEARCHCONF=3") ? -EFAULT : 0; + return periodicsearchconf_request(); } int lte_lc_periodic_search_get(struct lte_lc_periodic_search_cfg *const cfg) { - - int err; - char pattern_buf[4][40]; - uint16_t loop_tmp; - - if (!cfg) { - return -EINVAL; - } - - /* Response format: - * %PERIODICSEARCHCONF=,,, - * [,][,][,] - */ - - err = nrf_modem_at_scanf("AT%PERIODICSEARCHCONF=1", - "%%PERIODICSEARCHCONF: " - "%hu," /* */ - "%hu," /* */ - "%hu," /* */ - "\"%40[^\"]\"," /* */ - "\"%40[^\"]\"," /* */ - "\"%40[^\"]\"," /* */ - "\"%40[^\"]\"", /* */ - &loop_tmp, &cfg->return_to_pattern, &cfg->band_optimization, - pattern_buf[0], pattern_buf[1], pattern_buf[2], pattern_buf[3] - ); - if (err == -NRF_EBADMSG) { - return -ENOENT; - } else if (err < 0) { - return -EFAULT; - } else if (err < 4) { - /* Not all parameters and at least one pattern found */ - return -EBADMSG; - } - - cfg->loop = loop_tmp; - - /* Pattern count is matched parameters minus 3 for loop, return_to_pattern - * and band_optimization. - */ - cfg->pattern_count = err - 3; - - LOG_DBG("Pattern 1: %s", pattern_buf[0]); - - err = parse_periodic_search_pattern(pattern_buf[0], &cfg->patterns[0]); - if (err) { - LOG_ERR("Failed to parse periodic search pattern"); - return err; - } - - if (cfg->pattern_count >= 2) { - LOG_DBG("Pattern 2: %s", pattern_buf[1]); - - err = parse_periodic_search_pattern(pattern_buf[1], &cfg->patterns[1]); - if (err) { - LOG_ERR("Failed to parse periodic search pattern"); - return err; - } - } - - if (cfg->pattern_count >= 3) { - LOG_DBG("Pattern 3: %s", pattern_buf[2]); - - err = parse_periodic_search_pattern(pattern_buf[2], &cfg->patterns[2]); - if (err) { - LOG_ERR("Failed to parse periodic search pattern"); - return err; - } - } - - if (cfg->pattern_count == 4) { - LOG_DBG("Pattern 4: %s", pattern_buf[3]); - - err = parse_periodic_search_pattern(pattern_buf[3], &cfg->patterns[3]); - if (err) { - LOG_ERR("Failed to parse periodic search pattern"); - return err; - } - } - - return 0; + return periodicsearchconf_get(cfg); } int lte_lc_reduced_mobility_get(enum lte_lc_reduced_mobility_mode *mode) { - int ret; - uint16_t mode_tmp; - - if (mode == NULL) { - return -EINVAL; - } - - ret = nrf_modem_at_scanf("AT%REDMOB?", "%%REDMOB: %hu", &mode_tmp); - if (ret != 1) { - LOG_ERR("AT command failed, nrf_modem_at_scanf() returned error: %d", ret); - return -EFAULT; - } - - *mode = mode_tmp; - - return 0; + return redmob_get(mode); } int lte_lc_reduced_mobility_set(enum lte_lc_reduced_mobility_mode mode) { - int ret = nrf_modem_at_printf("AT%%REDMOB=%d", mode); - - if (ret) { - /* Failure to send the AT command. */ - LOG_ERR("AT command failed, returned error code: %d", ret); - return -EFAULT; - } - - return 0; + return redmob_set(mode); } int lte_lc_factory_reset(enum lte_lc_factory_reset_type type) { - return nrf_modem_at_printf("AT%%XFACTORYRESET=%d", type) ? -EFAULT : 0; + return xfactoryreset_reset(type); } static int lte_lc_sys_init(void) { - struct k_work_queue_config cfg = { - .name = "lte_lc_work_q", - }; - - k_work_queue_start( - <e_lc_work_q, - lte_lc_work_q_stack, - K_THREAD_STACK_SIZEOF(lte_lc_work_q_stack), - K_LOWEST_APPLICATION_THREAD_PRIO, - &cfg); - - return 0; + return work_q_start(); } SYS_INIT(lte_lc_sys_init, APPLICATION, 0); diff --git a/lib/lte_link_control/lte_lc_modem_hooks.c b/lib/lte_link_control/lte_lc_modem_hooks.c index 2c52de004d9..9347501c17f 100644 --- a/lib/lte_link_control/lte_lc_modem_hooks.c +++ b/lib/lte_link_control/lte_lc_modem_hooks.c @@ -9,7 +9,7 @@ #include #include -#include "lte_lc_helpers.h" +#include "modules/rai.h" LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); @@ -37,6 +37,7 @@ static void on_modem_init(int err, void *ctx) } } +#if defined(CONFIG_LTE_LC_PSM) if (IS_ENABLED(CONFIG_LTE_PSM_REQ_FORMAT_SECONDS)) { err = lte_lc_psm_param_set_seconds(CONFIG_LTE_PSM_REQ_RPTAU_SECONDS, CONFIG_LTE_PSM_REQ_RAT_SECONDS); @@ -80,6 +81,7 @@ static void on_modem_init(int err, void *ctx) */ (void)lte_lc_proprietary_psm_req(false); } +#endif /* Only supported in mfw 2.0.1 and newer. * Ignore the return value; an error likely means that the feature @@ -89,11 +91,13 @@ static void on_modem_init(int err, void *ctx) (void)nrf_modem_at_printf("AT%%FEACONF=0,3,%d", IS_ENABLED(CONFIG_LTE_PLMN_SELECTION_OPTIMIZATION)); +#if defined(CONFIG_LTE_LC_EDRX) err = lte_lc_edrx_req(IS_ENABLED(CONFIG_LTE_EDRX_REQ)); if (err) { LOG_ERR("Failed to configure eDRX, err %d", err); return; } +#endif #if defined(CONFIG_LTE_LOCK_BANDS) /* Set LTE band lock (volatile setting). @@ -124,8 +128,10 @@ static void on_modem_init(int err, void *ctx) } #endif +#if defined(CONFIG_LTE_LC_RAI) /* Configure Release Assistance Indication (RAI). */ rai_set(); +#endif } #if defined(CONFIG_UNITY) diff --git a/lib/lte_link_control/modules/CMakeLists.txt b/lib/lte_link_control/modules/CMakeLists.txt new file mode 100644 index 00000000000..e4ffbdb933f --- /dev/null +++ b/lib/lte_link_control/modules/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library_sources(cereg.c) +zephyr_library_sources(cfun.c) +zephyr_library_sources(cscon.c) +zephyr_library_sources(mdmev.c) +zephyr_library_sources(xsystemmode.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_CONEVAL coneval.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_EDRX edrx.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_NCELLMEAS ncellmeas.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_PSM psm.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_RAI rai.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_PERIODICSEARCHCONF periodicsearchconf.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_REDMOB redmob.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_XFACTORYRESET xfactoryreset.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_XMODEMSLEEP xmodemsleep.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_XT3412 xt3412.c) diff --git a/lib/lte_link_control/modules/Kconfig b/lib/lte_link_control/modules/Kconfig new file mode 100644 index 00000000000..21b8d3572a3 --- /dev/null +++ b/lib/lte_link_control/modules/Kconfig @@ -0,0 +1,45 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config LTE_LC_CONEVAL + bool "" + default n + +config LTE_LC_EDRX + bool "" + default n + +config LTE_LC_NCELLMEAS + bool "" + default n + +config LTE_LC_PERIODICSEARCHCONF + bool "" + default n + +config LTE_LC_PSM + bool "" + default n + +config LTE_LC_RAI + bool "" + default n + +config LTE_LC_REDMOB + bool "" + default n + +config LTE_LC_XFACTORYRESET + bool "" + default n + +config LTE_LC_XMODEMSLEEP + bool "" + default n + +config LTE_LC_XT3412 + bool "" + default n diff --git a/lib/lte_link_control/modules/cereg.c b/lib/lte_link_control/modules/cereg.c new file mode 100644 index 00000000000..f4663f645ef --- /dev/null +++ b/lib/lte_link_control/modules/cereg.c @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/work_q.h" +#include "common/event_handler_list.h" +#include "modules/cereg.h" +#include "modules/cfun.h" +#include "modules/psm.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +#define AT_CEREG_READ "AT+CEREG?" +#define AT_CEREG_5 "AT+CEREG=5" + +#define AT_CEREG_REG_STATUS_INDEX 1 +#define AT_CEREG_TAC_INDEX 2 +#define AT_CEREG_CELL_ID_INDEX 3 +#define AT_CEREG_ACT_INDEX 4 +#define AT_CEREG_CAUSE_TYPE_INDEX 5 +#define AT_CEREG_REJECT_CAUSE_INDEX 6 +#define AT_CEREG_ACTIVE_TIME_INDEX 7 +#define AT_CEREG_TAU_INDEX 8 + +/* Previously received LTE mode as indicated by the modem */ +static enum lte_lc_lte_mode prev_lte_mode = LTE_LC_LTE_MODE_NONE; + +/* Network attach semaphore */ +static K_SEM_DEFINE(link, 0, 1); + +AT_MONITOR(ltelc_atmon_cereg, "+CEREG", at_handler_cereg); + +static bool is_cellid_valid(uint32_t cellid) +{ + if (cellid == LTE_LC_CELL_EUTRAN_ID_INVALID) { + return false; + } + + return true; +} + +static int parse_cereg(const char *at_response, enum lte_lc_nw_reg_status *reg_status, + struct lte_lc_cell *cell, enum lte_lc_lte_mode *lte_mode, + struct lte_lc_psm_cfg *psm_cfg) +{ + int err, temp; + struct at_parser parser; + char str_buf[10]; + size_t len = sizeof(str_buf); + size_t count = 0; + + __ASSERT_NO_MSG(at_response != NULL); + __ASSERT_NO_MSG(reg_status != NULL); + __ASSERT_NO_MSG(cell != NULL); + __ASSERT_NO_MSG(lte_mode != NULL); + __ASSERT_NO_MSG(psm_cfg != NULL); + + /* Initialize all return values. */ + *reg_status = LTE_LC_NW_REG_UNKNOWN; + (void)memset(cell, 0, sizeof(struct lte_lc_cell)); + cell->id = LTE_LC_CELL_EUTRAN_ID_INVALID; + cell->tac = LTE_LC_CELL_TAC_INVALID; + *lte_mode = LTE_LC_LTE_MODE_NONE; + psm_cfg->active_time = -1; + psm_cfg->tau = -1; + + err = at_parser_init(&parser, at_response); + __ASSERT_NO_MSG(err == 0); + + /* Get network registration status */ + err = at_parser_num_get(&parser, AT_CEREG_REG_STATUS_INDEX, &temp); + if (err) { + LOG_ERR("Could not get registration status, error: %d", err); + goto clean_exit; + } + + *reg_status = temp; + LOG_DBG("Network registration status: %d", *reg_status); + + err = at_parser_cmd_count_get(&parser, &count); + if (err) { + LOG_ERR("Could not get CEREG param count, potentially malformed notification, " + "error: %d", + err); + goto clean_exit; + } + + if ((*reg_status != LTE_LC_NW_REG_UICC_FAIL) && (count > AT_CEREG_CELL_ID_INDEX)) { + /* Parse tracking area code */ + err = at_parser_string_get(&parser, AT_CEREG_TAC_INDEX, str_buf, &len); + if (err) { + LOG_DBG("Could not get tracking area code, error: %d", err); + } else { + cell->tac = strtoul(str_buf, NULL, 16); + } + + /* Parse cell ID */ + len = sizeof(str_buf); + + err = at_parser_string_get(&parser, AT_CEREG_CELL_ID_INDEX, str_buf, &len); + if (err) { + LOG_DBG("Could not get cell ID, error: %d", err); + } else { + cell->id = strtoul(str_buf, NULL, 16); + } + } + + /* Get currently active LTE mode. */ + err = at_parser_num_get(&parser, AT_CEREG_ACT_INDEX, &temp); + if (err) { + LOG_DBG("LTE mode not found, error code: %d", err); + + /* This is not an error that should be returned, as it's + * expected in some situations that LTE mode is not available. + */ + err = 0; + } else { + *lte_mode = temp; + LOG_DBG("LTE mode: %d", *lte_mode); + } + +#if defined(CONFIG_LOG) + int cause_type; + int reject_cause; + + /* Log reject cause if present. */ + err = at_parser_num_get(&parser, AT_CEREG_CAUSE_TYPE_INDEX, &cause_type); + err |= at_parser_num_get(&parser, AT_CEREG_REJECT_CAUSE_INDEX, &reject_cause); + if (!err && cause_type == 0 /* EMM cause */) { + LOG_WRN("Registration rejected, EMM cause: %d, Cell ID: %d, Tracking area: %d, " + "LTE mode: %d", + reject_cause, cell->id, cell->tac, *lte_mode); + } + /* Absence of reject cause is not considered an error. */ + err = 0; +#endif /* CONFIG_LOG */ + +#if defined(CONFIG_LTE_LC_PSM) + /* Check PSM parameters only if we are connected */ + if ((*reg_status != LTE_LC_NW_REG_REGISTERED_HOME) && + (*reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING)) { + goto clean_exit; + } + + char active_time_str[9] = {0}; + char tau_ext_str[9] = {0}; + int str_len; + int err_active_time; + int err_tau; + + str_len = sizeof(active_time_str); + + /* Get active time */ + err_active_time = at_parser_string_get(&parser, AT_CEREG_ACTIVE_TIME_INDEX, active_time_str, + &str_len); + if (err_active_time) { + LOG_DBG("Active time not found, error: %d", err_active_time); + } else { + LOG_DBG("Active time: %s", active_time_str); + } + + str_len = sizeof(tau_ext_str); + + /* Get Periodic-TAU-ext */ + err_tau = at_parser_string_get(&parser, AT_CEREG_TAU_INDEX, tau_ext_str, &str_len); + if (err_tau) { + LOG_DBG("TAU not found, error: %d", err_tau); + } else { + LOG_DBG("TAU: %s", tau_ext_str); + } + + if (err_active_time == 0 && err_tau == 0) { + /* Legacy TAU is not requested because we do not get it from CEREG. + * If extended TAU is not set, TAU will be set to inactive so + * caller can then make its conclusions. + */ + err = psm_parse(active_time_str, tau_ext_str, NULL, psm_cfg); + if (err) { + LOG_ERR("Failed to parse PSM configuration, error: %d", err); + } + } + /* The notification does not always contain PSM parameters, + * so this is not considered an error + */ + err = 0; +#endif + +clean_exit: + return err; +} + +static void at_handler_cereg(const char *response) +{ + int err; + struct lte_lc_evt evt = {0}; + + __ASSERT_NO_MSG(response != NULL); + + static enum lte_lc_nw_reg_status prev_reg_status = LTE_LC_NW_REG_NOT_REGISTERED; + static struct lte_lc_cell prev_cell; + enum lte_lc_nw_reg_status reg_status; + struct lte_lc_cell cell; + enum lte_lc_lte_mode lte_mode; + struct lte_lc_psm_cfg psm_cfg; + + LOG_DBG("+CEREG notification: %.*s", strlen(response) - strlen("\r\n"), response); + + err = parse_cereg(response, ®_status, &cell, <e_mode, &psm_cfg); + if (err) { + LOG_ERR("Failed to parse notification (error %d): %s", err, response); + return; + } + + if ((reg_status == LTE_LC_NW_REG_REGISTERED_HOME) || + (reg_status == LTE_LC_NW_REG_REGISTERED_ROAMING)) { + /* Set the network registration status to UNKNOWN if the cell ID is parsed + * to UINT32_MAX (FFFFFFFF) when the registration status is either home or + * roaming. + */ + if (!is_cellid_valid(cell.id)) { + reg_status = LTE_LC_NW_REG_UNKNOWN; + } else { + k_sem_give(&link); + } + } + + switch (reg_status) { + case LTE_LC_NW_REG_NOT_REGISTERED: + LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_NOT_REGISTERED); + break; + case LTE_LC_NW_REG_REGISTERED_HOME: + LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_REGISTERED_HOME); + break; + case LTE_LC_NW_REG_SEARCHING: + LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_SEARCHING); + break; + case LTE_LC_NW_REG_REGISTRATION_DENIED: + LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_REGISTRATION_DENIED); + break; + case LTE_LC_NW_REG_UNKNOWN: + LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_UNKNOWN); + break; + case LTE_LC_NW_REG_REGISTERED_ROAMING: + LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_REGISTERED_ROAMING); + break; + case LTE_LC_NW_REG_UICC_FAIL: + LTE_LC_TRACE(LTE_LC_TRACE_NW_REG_UICC_FAIL); + break; + default: + LOG_ERR("Unknown network registration status: %d", reg_status); + return; + } + + if (event_handler_list_is_empty()) { + return; + } + + /* Network registration status event */ + if (reg_status != prev_reg_status) { + prev_reg_status = reg_status; + evt.type = LTE_LC_EVT_NW_REG_STATUS; + evt.nw_reg_status = reg_status; + + event_handler_list_dispatch(&evt); + } + + /* Cell update event */ + if ((cell.id != prev_cell.id) || (cell.tac != prev_cell.tac)) { + evt.type = LTE_LC_EVT_CELL_UPDATE; + + memcpy(&prev_cell, &cell, sizeof(struct lte_lc_cell)); + memcpy(&evt.cell, &cell, sizeof(struct lte_lc_cell)); + event_handler_list_dispatch(&evt); + } + + if (lte_mode != prev_lte_mode) { + switch (lte_mode) { + case LTE_LC_LTE_MODE_LTEM: + LTE_LC_TRACE(LTE_LC_TRACE_LTE_MODE_UPDATE_LTEM); + break; + case LTE_LC_LTE_MODE_NBIOT: + LTE_LC_TRACE(LTE_LC_TRACE_LTE_MODE_UPDATE_NBIOT); + break; + case LTE_LC_LTE_MODE_NONE: + LTE_LC_TRACE(LTE_LC_TRACE_LTE_MODE_UPDATE_NONE); + break; + default: + LOG_ERR("Unknown LTE mode: %d", lte_mode); + return; + } + + prev_lte_mode = lte_mode; + evt.type = LTE_LC_EVT_LTE_MODE_UPDATE; + evt.lte_mode = lte_mode; + + event_handler_list_dispatch(&evt); + } + + if ((reg_status != LTE_LC_NW_REG_REGISTERED_HOME) && + (reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING)) { + return; + } + +#if defined(CONFIG_LTE_LC_PSM) + if (psm_cfg.tau == -1) { + /* Need to get legacy T3412 value as TAU using AT%XMONITOR. + * + * As we are in an AT notification handler that is run from the system work queue, + * we shall not send AT commands here because another AT command might be ongoing, + * and the second command will be blocked until the first one completes. + * Further AT notifications from the modem will gradually exhaust AT monitor + * library's heap, and eventually it will run out causing an assert or + * AT notifications not being dispatched. + */ + k_work_submit_to_queue(work_q_get(), psm_work_get()); + return; + } + + psm_evt_update_send(&psm_cfg); +#endif +} + +int cereg_status_get(enum lte_lc_nw_reg_status *status) +{ + int err; + uint16_t status_tmp; + uint32_t cell_id = 0; + + if (status == NULL) { + return -EINVAL; + } + + /* Read network registration status */ + err = nrf_modem_at_scanf("AT+CEREG?", + "+CEREG: " + "%*u," /* */ + "%hu," /* */ + "%*[^,]," /* */ + "\"%x\",", /* */ + &status_tmp, &cell_id); + if (err < 1) { + LOG_ERR("Could not get registration status, error: %d", err); + return -EFAULT; + } + + if (!is_cellid_valid(cell_id)) { + *status = LTE_LC_NW_REG_UNKNOWN; + } else { + *status = status_tmp; + } + + return 0; +} + +int cereg_mode_get(enum lte_lc_lte_mode *mode) +{ + int err; + uint16_t mode_tmp; + + if (mode == NULL) { + return -EINVAL; + } + + err = nrf_modem_at_scanf(AT_CEREG_READ, + "+CEREG: " + "%*u," /* */ + "%*u," /* */ + "%*[^,]," /* */ + "%*[^,]," /* */ + "%hu", /* */ + &mode_tmp); + if (err == -NRF_EBADMSG) { + /* The AT command was successful, but there were no matches. + * This is not an error, but the LTE mode is unknown. + */ + *mode = LTE_LC_LTE_MODE_NONE; + + return 0; + } else if (err < 1) { + LOG_ERR("Could not get the LTE mode, error: %d", err); + return -EFAULT; + } + + *mode = mode_tmp; + + switch (*mode) { + case LTE_LC_LTE_MODE_NONE: + case LTE_LC_LTE_MODE_LTEM: + case LTE_LC_LTE_MODE_NBIOT: + break; + default: + return -EBADMSG; + } + + return 0; +} + +int cereg_lte_connect(bool blocking) +{ + int err; + enum lte_lc_func_mode original_func_mode; + bool func_mode_changed = false; + enum lte_lc_nw_reg_status reg_status; + static atomic_t in_progress; + + /* Check if a connection attempt is already in progress */ + if (atomic_set(&in_progress, 1)) { + LOG_WRN("Connect already in progress"); + return -EINPROGRESS; + } + + err = cereg_status_get(®_status); + if (err) { + LOG_ERR("Failed to get current registration status"); + err = -EFAULT; + goto exit; + } + + /* Do not attempt to register with an LTE network if the device already is registered. + * This check is needed for blocking _connect() calls to avoid hanging for + * CONFIG_LTE_NETWORK_TIMEOUT seconds waiting for a semaphore that will not be given. + */ + if ((reg_status == LTE_LC_NW_REG_REGISTERED_HOME) || + (reg_status == LTE_LC_NW_REG_REGISTERED_ROAMING)) { + LOG_DBG("The device is already registered with an LTE network"); + + err = 0; + goto exit; + } + + err = cfun_mode_get(&original_func_mode); + if (err) { + err = -EFAULT; + goto exit; + } + + /* Reset the semaphore, it may have already been given by an earlier +CEREG notification. */ + k_sem_reset(&link); + + err = cfun_mode_set(LTE_LC_FUNC_MODE_NORMAL); + if (err || !blocking) { + goto exit; + } + + func_mode_changed = true; + + err = k_sem_take(&link, K_SECONDS(CONFIG_LTE_NETWORK_TIMEOUT)); + if (err == -EAGAIN) { + LOG_INF("Network connection attempt timed out"); + err = -ETIMEDOUT; + } + +exit: + if (err && func_mode_changed) { + /* Connecting to LTE network failed, restore original functional mode. */ + cfun_mode_set(original_func_mode); + } + + atomic_clear(&in_progress); + + return err; +} + +int cereg_notifications_enable(void) +{ + int err; + + /* +CEREG notifications, level 5 */ + err = nrf_modem_at_printf(AT_CEREG_5); + if (err) { + LOG_ERR("Failed to subscribe to CEREG notifications, error: %d", err); + return -EFAULT; + } + + return 0; +} diff --git a/lib/lte_link_control/modules/cfun.c b/lib/lte_link_control/modules/cfun.c new file mode 100644 index 00000000000..9e3f0eeda52 --- /dev/null +++ b/lib/lte_link_control/modules/cfun.c @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/event_handler_list.h" +#include "modules/cereg.h" +#include "modules/cfun.h" +#include "modules/cscon.h" +#include "modules/xmodemsleep.h" +#include "modules/xt3412.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +#define AT_CFUN_READ "AT+CFUN?" + +static int enable_notifications(void) +{ + int err; + + err = cereg_notifications_enable(); + if (err) { + return err; + } + + err = cscon_notifications_enable(); + if (err) { + return err; + } + +#if defined(CONFIG_LTE_LC_XMODEMSLEEP) + err = xmodemsleep_notifications_enable(); + if (err) { + return err; + } +#endif + +#if defined(CONFIG_LTE_LC_XT3412) + err = xt3412_notifications_enable(); + if (err) { + return err; + } +#endif + + return 0; +} + +int cfun_mode_get(enum lte_lc_func_mode *mode) +{ + int err; + uint16_t mode_tmp; + + if (mode == NULL) { + return -EINVAL; + } + + /* Exactly one parameter is expected to match. */ + err = nrf_modem_at_scanf(AT_CFUN_READ, "+CFUN: %hu", &mode_tmp); + if (err != 1) { + LOG_ERR("AT command failed, nrf_modem_at_scanf() returned error: %d", err); + return -EFAULT; + } + + *mode = mode_tmp; + + return 0; +} + +int cfun_mode_set(enum lte_lc_func_mode mode) +{ + int err; + + switch (mode) { + case LTE_LC_FUNC_MODE_ACTIVATE_LTE: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_ACTIVATE_LTE); + + err = enable_notifications(); + if (err) { + LOG_ERR("Failed to enable notifications, error: %d", err); + return -EFAULT; + } + + break; + case LTE_LC_FUNC_MODE_NORMAL: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_NORMAL); + + err = enable_notifications(); + if (err) { + LOG_ERR("Failed to enable notifications, error: %d", err); + return -EFAULT; + } + + break; + case LTE_LC_FUNC_MODE_POWER_OFF: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_POWER_OFF); + break; + case LTE_LC_FUNC_MODE_RX_ONLY: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_RX_ONLY); + break; + case LTE_LC_FUNC_MODE_OFFLINE: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_OFFLINE); + break; + case LTE_LC_FUNC_MODE_DEACTIVATE_LTE: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_DEACTIVATE_LTE); + break; + case LTE_LC_FUNC_MODE_DEACTIVATE_GNSS: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_DEACTIVATE_GNSS); + break; + case LTE_LC_FUNC_MODE_ACTIVATE_GNSS: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_ACTIVATE_GNSS); + break; + case LTE_LC_FUNC_MODE_DEACTIVATE_UICC: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_DEACTIVATE_UICC); + break; + case LTE_LC_FUNC_MODE_ACTIVATE_UICC: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_ACTIVATE_UICC); + break; + case LTE_LC_FUNC_MODE_OFFLINE_UICC_ON: + LTE_LC_TRACE(LTE_LC_TRACE_FUNC_MODE_OFFLINE_UICC_ON); + break; + default: + LOG_ERR("Invalid functional mode: %d", mode); + return -EINVAL; + } + + err = nrf_modem_at_printf("AT+CFUN=%d", mode); + if (err) { + LOG_ERR("Failed to set functional mode. Please check XSYSTEMMODE."); + return -EFAULT; + } + + LOG_DBG("Functional mode set to %d", mode); + + return 0; +} diff --git a/lib/lte_link_control/modules/coneval.c b/lib/lte_link_control/modules/coneval.c new file mode 100644 index 00000000000..f4ebf5a7a8b --- /dev/null +++ b/lib/lte_link_control/modules/coneval.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/event_handler_list.h" +#include "common/helpers.h" +#include "modules/cfun.h" +#include "modules/coneval.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +/* CONEVAL command parameters */ +#define AT_CONEVAL_READ "AT%CONEVAL" +#define AT_CONEVAL_PARAMS_MAX 19 +#define AT_CONEVAL_RESULT_INDEX 1 +#define AT_CONEVAL_RRC_STATE_INDEX 2 +#define AT_CONEVAL_ENERGY_ESTIMATE_INDEX 3 +#define AT_CONEVAL_RSRP_INDEX 4 +#define AT_CONEVAL_RSRQ_INDEX 5 +#define AT_CONEVAL_SNR_INDEX 6 +#define AT_CONEVAL_CELL_ID_INDEX 7 +#define AT_CONEVAL_PLMN_INDEX 8 +#define AT_CONEVAL_PHYSICAL_CELL_ID_INDEX 9 +#define AT_CONEVAL_EARFCN_INDEX 10 +#define AT_CONEVAL_BAND_INDEX 11 +#define AT_CONEVAL_TAU_TRIGGERED_INDEX 12 +#define AT_CONEVAL_CE_LEVEL_INDEX 13 +#define AT_CONEVAL_TX_POWER_INDEX 14 +#define AT_CONEVAL_TX_REPETITIONS_INDEX 15 +#define AT_CONEVAL_RX_REPETITIONS_INDEX 16 +#define AT_CONEVAL_DL_PATHLOSS_INDEX 17 + +int coneval_params_get(struct lte_lc_conn_eval_params *params) +{ + int err; + enum lte_lc_func_mode mode; + int result; + /* PLMN field is a string of maximum 6 characters, plus null termination. */ + char plmn_str[7] = {0}; + uint16_t rrc_state_tmp, energy_estimate_tmp, tau_trig_tmp, ce_level_tmp; + + if (params == NULL) { + return -EINVAL; + } + + err = cfun_mode_get(&mode); + if (err) { + LOG_ERR("Could not get functional mode"); + return -EFAULT; + } + + switch (mode) { + case LTE_LC_FUNC_MODE_NORMAL: + case LTE_LC_FUNC_MODE_ACTIVATE_LTE: + case LTE_LC_FUNC_MODE_RX_ONLY: + break; + default: + LOG_WRN("Connection evaluation is not available in the current functional mode"); + return -EOPNOTSUPP; + } + + /* AT%CONEVAL response format, from nRF91 AT Commands - Command Reference Guide, v1.7: + * + * %CONEVAL: [,,,,,,,, + * ,,,,,,, + * ,] + * + * In total, 17 parameters are expected to match for a successful command response. + */ + err = nrf_modem_at_scanf(AT_CONEVAL_READ, + "%%CONEVAL: " + "%d," /* */ + "%hu," /* */ + "%hu," /* */ + "%hd," /* */ + "%hd," /* */ + "%hd," /* */ + "\"%x\"," /* */ + "\"%6[^\"]\"," /* */ + "%hd," /* */ + "%d," /* */ + "%hd," /* */ + "%hu," /* */ + "%hu," /* */ + "%hd," /* */ + "%hd," /* */ + "%hd," /* */ + "%hd", /* */ + &result, &rrc_state_tmp, &energy_estimate_tmp, ¶ms->rsrp, + ¶ms->rsrq, ¶ms->snr, ¶ms->cell_id, plmn_str, + ¶ms->phy_cid, ¶ms->earfcn, ¶ms->band, &tau_trig_tmp, + &ce_level_tmp, ¶ms->tx_power, ¶ms->tx_rep, ¶ms->rx_rep, + ¶ms->dl_pathloss); + if (err < 0) { + LOG_ERR("AT command failed, error: %d", err); + return -EFAULT; + } else if (result != 0) { + LOG_WRN("Connection evaluation failed with reason: %d", result); + return result; + } else if (err != 17) { + LOG_ERR("AT command parsing failed, error: %d", err); + return -EBADMSG; + } + + params->rrc_state = rrc_state_tmp; + params->energy_estimate = energy_estimate_tmp; + params->tau_trig = tau_trig_tmp; + params->ce_level = ce_level_tmp; + + /* Read MNC and store as integer. The MNC starts as the fourth character + * in the string, following three characters long MCC. + */ + err = string_to_int(&plmn_str[3], 10, ¶ms->mnc); + if (err) { + return -EBADMSG; + } + + /* Null-terminated MCC, read and store it. */ + plmn_str[3] = '\0'; + + err = string_to_int(plmn_str, 10, ¶ms->mcc); + if (err) { + return -EBADMSG; + } + + return 0; +} diff --git a/lib/lte_link_control/modules/cscon.c b/lib/lte_link_control/modules/cscon.c new file mode 100644 index 00000000000..960e95e1a6b --- /dev/null +++ b/lib/lte_link_control/modules/cscon.c @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/event_handler_list.h" +#include "modules/cscon.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +/* CSCON command parameters */ +#define AT_CSCON_RRC_MODE_INDEX 1 +#define AT_CSCON_READ_RRC_MODE_INDEX 2 + +/* Enable CSCON (RRC mode) notifications */ +static const char cscon[] = "AT+CSCON=1"; + +AT_MONITOR(ltelc_atmon_cscon, "+CSCON", at_handler_cscon); + +/**@brief Parses an AT command response, and returns the current RRC mode. + * + * @param at_response Pointer to buffer with AT response. + * @param mode Pointer to where the RRC mode is stored. + * @param mode_index Parameter index for mode. + * + * @return Zero on success or (negative) error code otherwise. + */ +static int parse_rrc_mode(const char *at_response, enum lte_lc_rrc_mode *mode, size_t mode_index) +{ + int err, temp_mode; + struct at_parser parser; + + __ASSERT_NO_MSG(at_response != NULL); + __ASSERT_NO_MSG(mode != NULL); + + err = at_parser_init(&parser, at_response); + __ASSERT_NO_MSG(err == 0); + + /* Get the RRC mode from the response */ + err = at_parser_num_get(&parser, mode_index, &temp_mode); + if (err) { + LOG_ERR("Could not get signalling mode, error: %d", err); + goto clean_exit; + } + + /* Check if the parsed value maps to a valid registration status */ + if (temp_mode == 0) { + *mode = LTE_LC_RRC_MODE_IDLE; + } else if (temp_mode == 1) { + *mode = LTE_LC_RRC_MODE_CONNECTED; + } else { + LOG_ERR("Invalid signalling mode: %d", temp_mode); + err = -EINVAL; + } + +clean_exit: + return err; +} + +static void at_handler_cscon(const char *response) +{ + int err; + struct lte_lc_evt evt = {0}; + + __ASSERT_NO_MSG(response != NULL); + + LOG_DBG("+CSCON notification"); + + err = parse_rrc_mode(response, &evt.rrc_mode, AT_CSCON_RRC_MODE_INDEX); + if (err) { + LOG_ERR("Can't parse signalling mode, error: %d", err); + return; + } + + if (evt.rrc_mode == LTE_LC_RRC_MODE_IDLE) { + LTE_LC_TRACE(LTE_LC_TRACE_RRC_IDLE); + } else if (evt.rrc_mode == LTE_LC_RRC_MODE_CONNECTED) { + LTE_LC_TRACE(LTE_LC_TRACE_RRC_CONNECTED); + } + + evt.type = LTE_LC_EVT_RRC_UPDATE; + + event_handler_list_dispatch(&evt); +} + +int cscon_notifications_enable(void) +{ + int err; + + err = nrf_modem_at_printf(cscon); + if (err) { + LOG_WRN("Failed to enable RRC notifications (+CSCON), error %d", err); + return -EFAULT; + } + + return 0; +} diff --git a/lib/lte_link_control/modules/edrx.c b/lib/lte_link_control/modules/edrx.c new file mode 100644 index 00000000000..78618c71024 --- /dev/null +++ b/lib/lte_link_control/modules/edrx.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/work_q.h" +#include "common/event_handler_list.h" +#include "modules/edrx.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +/* CEDRXP notification parameters */ +#define AT_CEDRXP_ACTT_INDEX 1 +#define AT_CEDRXP_REQ_EDRX_INDEX 2 +#define AT_CEDRXP_NW_EDRX_INDEX 3 +#define AT_CEDRXP_NW_PTW_INDEX 4 + +/* CEDRXS command parameters */ +#define AT_CEDRXS_MODE_INDEX +#define AT_CEDRXS_ACTT_WB 4 +#define AT_CEDRXS_ACTT_NB 5 + +/* Requested eDRX state (enabled/disabled) */ +static bool requested_edrx_enable; +/* Requested eDRX setting */ +static char requested_edrx_value_ltem[LTE_LC_EDRX_VALUE_LEN] = CONFIG_LTE_EDRX_REQ_VALUE_LTE_M; +static char requested_edrx_value_nbiot[LTE_LC_EDRX_VALUE_LEN] = CONFIG_LTE_EDRX_REQ_VALUE_NBIOT; + +/* Currently used eDRX setting as indicated by the modem */ +static char edrx_value_ltem[LTE_LC_EDRX_VALUE_LEN]; +static char edrx_value_nbiot[LTE_LC_EDRX_VALUE_LEN]; +/* Currently used PTW setting as indicated by the modem */ +static char ptw_value_ltem[LTE_LC_EDRX_VALUE_LEN]; +static char ptw_value_nbiot[LTE_LC_EDRX_VALUE_LEN]; +/* Requested PTW setting */ +static char requested_ptw_value_ltem[LTE_LC_EDRX_VALUE_LEN] = CONFIG_LTE_PTW_VALUE_LTE_M; +static char requested_ptw_value_nbiot[LTE_LC_EDRX_VALUE_LEN] = CONFIG_LTE_PTW_VALUE_NBIOT; + +static void edrx_ptw_send_work_fn(struct k_work *work_item); +K_WORK_DEFINE(edrx_ptw_send_work, edrx_ptw_send_work_fn); + +AT_MONITOR(ltelc_atmon_cedrxp, "+CEDRXP", at_handler_cedrxp); + +static void lte_lc_edrx_current_values_clear(void) +{ + memset(edrx_value_ltem, 0, sizeof(edrx_value_ltem)); + memset(ptw_value_ltem, 0, sizeof(ptw_value_ltem)); + memset(edrx_value_nbiot, 0, sizeof(edrx_value_nbiot)); + memset(ptw_value_nbiot, 0, sizeof(ptw_value_nbiot)); +} + +static void lte_lc_edrx_values_store(enum lte_lc_lte_mode mode, char *edrx_value, char *ptw_value) +{ + switch (mode) { + case LTE_LC_LTE_MODE_LTEM: + strcpy(edrx_value_ltem, edrx_value); + strcpy(ptw_value_ltem, ptw_value); + break; + case LTE_LC_LTE_MODE_NBIOT: + strcpy(edrx_value_nbiot, edrx_value); + strcpy(ptw_value_nbiot, ptw_value); + break; + default: + lte_lc_edrx_current_values_clear(); + break; + } +} + +static void edrx_ptw_send_work_fn(struct k_work *work_item) +{ + int err; + int actt[] = {AT_CEDRXS_ACTT_WB, AT_CEDRXS_ACTT_NB}; + + /* Apply the configurations for both LTE-M and NB-IoT. */ + for (size_t i = 0; i < ARRAY_SIZE(actt); i++) { + char *requested_ptw_value = (actt[i] == AT_CEDRXS_ACTT_WB) + ? requested_ptw_value_ltem + : requested_ptw_value_nbiot; + char *ptw_value = (actt[i] == AT_CEDRXS_ACTT_WB) ? ptw_value_ltem : ptw_value_nbiot; + + if (strlen(requested_ptw_value) == 4 && + strcmp(ptw_value, requested_ptw_value) != 0) { + + err = nrf_modem_at_printf("AT%%XPTW=%d,\"%s\"", actt[i], + requested_ptw_value); + if (err) { + LOG_ERR("Failed to request PTW, reported error: %d", err); + } + } + } +} + +#if defined(CONFIG_UNITY) +void lte_lc_edrx_on_modem_cfun(int mode, void *ctx) +#else +NRF_MODEM_LIB_ON_CFUN(lte_lc_edrx_cfun_hook, lte_lc_edrx_on_modem_cfun, NULL); + +static void lte_lc_edrx_on_modem_cfun(int mode, void *ctx) +#endif +{ + ARG_UNUSED(ctx); + + /* If eDRX is enabled and modem is powered off, subscription of unsolicited eDRX + * notifications must be re-newed because modem forgets that information + * although it stores eDRX value and PTW for both system modes. + */ + if (mode == LTE_LC_FUNC_MODE_POWER_OFF && requested_edrx_enable) { + lte_lc_edrx_current_values_clear(); + /* We want to avoid sending AT commands in the callback. However, + * when modem is powered off, we are not expecting AT notifications + * that could cause an assertion or missing notification. + */ + edrx_request(requested_edrx_enable); + } +} + +/* Get Paging Time Window multiplier for the LTE mode. + * Multiplier is 1.28 s for LTE-M, and 2.56 s for NB-IoT, derived from + * Figure 10.5.5.32/3GPP TS 24.008. + */ +static void get_ptw_multiplier(enum lte_lc_lte_mode lte_mode, float *ptw_multiplier) +{ + __ASSERT_NO_MSG(ptw_multiplier != NULL); + + if (lte_mode == LTE_LC_LTE_MODE_NBIOT) { + *ptw_multiplier = 2.56; + } else { + __ASSERT_NO_MSG(lte_mode == LTE_LC_LTE_MODE_LTEM); + *ptw_multiplier = 1.28; + } +} + +static void get_edrx_value(enum lte_lc_lte_mode lte_mode, uint8_t idx, float *edrx_value) +{ + uint16_t multiplier = 0; + + /* Lookup table to eDRX multiplier values, based on T_eDRX values found + * in Table 10.5.5.32/3GPP TS 24.008. The actual value is + * (multiplier * 10.24 s), except for the first entry which is handled + * as a special case per note 3 in the specification. + */ + static const uint16_t edrx_lookup_ltem[16] = {0, 1, 2, 4, 6, 8, 10, 12, + 14, 16, 32, 64, 128, 256, 256, 256}; + static const uint16_t edrx_lookup_nbiot[16] = {2, 2, 2, 4, 2, 8, 2, 2, + 2, 16, 32, 64, 128, 256, 512, 1024}; + + __ASSERT_NO_MSG(edrx_value != NULL); + /* idx is parsed from 4 character bit field string so it cannot be more than 15 */ + __ASSERT_NO_MSG(idx < ARRAY_SIZE(edrx_lookup_ltem)); + + if (lte_mode == LTE_LC_LTE_MODE_LTEM) { + multiplier = edrx_lookup_ltem[idx]; + } else { + __ASSERT_NO_MSG(lte_mode == LTE_LC_LTE_MODE_NBIOT); + multiplier = edrx_lookup_nbiot[idx]; + } + + *edrx_value = multiplier == 0 ? 5.12 : multiplier * 10.24; +} + +/* Parses eDRX parameters from a +CEDRXS notification or a +CEDRXRDP response. */ +static int parse_edrx(const char *at_response, struct lte_lc_edrx_cfg *cfg, char *edrx_str, + char *ptw_str) +{ + int err, tmp_int; + uint8_t idx; + struct at_parser parser; + char tmp_buf[5]; + size_t len = sizeof(tmp_buf); + float ptw_multiplier; + + __ASSERT_NO_MSG(at_response != NULL); + __ASSERT_NO_MSG(cfg != NULL); + __ASSERT_NO_MSG(edrx_str != NULL); + __ASSERT_NO_MSG(ptw_str != NULL); + + err = at_parser_init(&parser, at_response); + __ASSERT_NO_MSG(err == 0); + + err = at_parser_num_get(&parser, AT_CEDRXP_ACTT_INDEX, &tmp_int); + if (err) { + LOG_ERR("Failed to get LTE mode, error: %d", err); + goto clean_exit; + } + + /* The access technology indicators 4 for LTE-M and 5 for NB-IoT are + * specified in 3GPP 27.007 Ch. 7.41. + * 0 indicates that the access technology does not currently use eDRX. + * Any other value is not expected, and we use 0xFFFFFFFF to represent those. + */ + cfg->mode = tmp_int == 0 ? LTE_LC_LTE_MODE_NONE + : tmp_int == 4 ? LTE_LC_LTE_MODE_LTEM + : tmp_int == 5 ? LTE_LC_LTE_MODE_NBIOT + : 0xFFFFFFFF; /* Intentionally illegal value */ + + /* Check for the case where eDRX is not used. */ + if (cfg->mode == LTE_LC_LTE_MODE_NONE) { + cfg->edrx = 0; + cfg->ptw = 0; + + err = 0; + goto clean_exit; + } else if (cfg->mode == 0xFFFFFFFF) { + err = -ENODATA; + goto clean_exit; + } + + err = at_parser_string_get(&parser, AT_CEDRXP_NW_EDRX_INDEX, tmp_buf, &len); + if (err) { + LOG_ERR("Failed to get eDRX configuration, error: %d", err); + goto clean_exit; + } + + /* Workaround for +CEDRXRDP response handling. The AcT-type is handled differently in the + * +CEDRXRDP response, so use of eDRX needs to be determined based on the eDRX value + * parameter. + */ + if (len == 0) { + /* Network provided eDRX value is empty, eDRX is not used. */ + cfg->mode = LTE_LC_LTE_MODE_NONE; + cfg->edrx = 0; + cfg->ptw = 0; + + err = 0; + goto clean_exit; + } + + __ASSERT_NO_MSG(edrx_str != NULL); + strcpy(edrx_str, tmp_buf); + + /* The eDRX value is a multiple of 10.24 seconds, except for the + * special case of idx == 0 for LTE-M, where the value is 5.12 seconds. + * The variable idx is used to map to the entry of index idx in + * Figure 10.5.5.32/3GPP TS 24.008, table for eDRX in S1 mode, and + * note 4 and 5 are taken into account. + */ + idx = strtoul(tmp_buf, NULL, 2); + + /* Get Paging Time Window multiplier for the LTE mode. + * Multiplier is 1.28 s for LTE-M, and 2.56 s for NB-IoT, derived from + * Figure 10.5.5.32/3GPP TS 24.008. + */ + get_ptw_multiplier(cfg->mode, &ptw_multiplier); + + get_edrx_value(cfg->mode, idx, &cfg->edrx); + + len = sizeof(tmp_buf); + + err = at_parser_string_get(&parser, AT_CEDRXP_NW_PTW_INDEX, tmp_buf, &len); + if (err) { + LOG_ERR("Failed to get PTW configuration, error: %d", err); + goto clean_exit; + } + + strcpy(ptw_str, tmp_buf); + + /* Value can be a maximum of 15, as there are 16 entries in the table + * for paging time window (both for LTE-M and NB1). + * We can use assert as only 4 bits can be received and if there would be more, + * the previous at_parser_string_get would fail. + */ + idx = strtoul(tmp_buf, NULL, 2); + __ASSERT_NO_MSG(idx <= 15); + + /* The Paging Time Window is different for LTE-M and NB-IoT: + * - LTE-M: (idx + 1) * 1.28 s + * - NB-IoT (idx + 1) * 2.56 s + */ + idx += 1; + cfg->ptw = idx * ptw_multiplier; + + LOG_DBG("eDRX value for %s: %d.%02d, PTW: %d.%02d", + (cfg->mode == LTE_LC_LTE_MODE_LTEM) ? "LTE-M" : "NB-IoT", (int)cfg->edrx, + (int)(100 * (cfg->edrx - (int)cfg->edrx)), (int)cfg->ptw, + (int)(100 * (cfg->ptw - (int)cfg->ptw))); + +clean_exit: + return err; +} + +static void at_handler_cedrxp(const char *response) +{ + int err; + struct lte_lc_evt evt = {0}; + char edrx_value[LTE_LC_EDRX_VALUE_LEN] = {0}; + char ptw_value[LTE_LC_EDRX_VALUE_LEN] = {0}; + + __ASSERT_NO_MSG(response != NULL); + + LOG_DBG("+CEDRXP notification"); + + err = parse_edrx(response, &evt.edrx_cfg, edrx_value, ptw_value); + if (err) { + LOG_ERR("Can't parse eDRX, error: %d", err); + return; + } + + /* PTW must be requested after eDRX is enabled */ + lte_lc_edrx_values_store(evt.edrx_cfg.mode, edrx_value, ptw_value); + /* Send PTW setting if eDRX is enabled, i.e., we have network mode */ + if (evt.edrx_cfg.mode != LTE_LC_LTE_MODE_NONE) { + k_work_submit_to_queue(work_q_get(), &edrx_ptw_send_work); + } + evt.type = LTE_LC_EVT_EDRX_UPDATE; + + event_handler_list_dispatch(&evt); +} + +int edrx_cfg_get(struct lte_lc_edrx_cfg *edrx_cfg) +{ + int err; + char response[48]; + char edrx_value[LTE_LC_EDRX_VALUE_LEN] = {0}; + char ptw_value[LTE_LC_EDRX_VALUE_LEN] = {0}; + + if (edrx_cfg == NULL) { + return -EINVAL; + } + + err = nrf_modem_at_cmd(response, sizeof(response), "AT+CEDRXRDP"); + if (err) { + LOG_ERR("Failed to request eDRX parameters, error: %d", err); + return -EFAULT; + } + + err = parse_edrx(response, edrx_cfg, edrx_value, ptw_value); + if (err) { + LOG_ERR("Failed to parse eDRX parameters, error: %d", err); + return -EBADMSG; + } + + lte_lc_edrx_values_store(edrx_cfg->mode, edrx_value, ptw_value); + + return 0; +} + +int edrx_ptw_set(enum lte_lc_lte_mode mode, const char *ptw) +{ + char *ptw_value; + + if (mode != LTE_LC_LTE_MODE_LTEM && mode != LTE_LC_LTE_MODE_NBIOT) { + LOG_ERR("LTE mode must be LTE-M or NB-IoT"); + return -EINVAL; + } + + if (ptw != NULL && strlen(ptw) != 4) { + return -EINVAL; + } + + ptw_value = (mode == LTE_LC_LTE_MODE_LTEM) ? requested_ptw_value_ltem + : requested_ptw_value_nbiot; + + if (ptw != NULL) { + strcpy(ptw_value, ptw); + LOG_DBG("PTW set to %s for %s", ptw_value, + (mode == LTE_LC_LTE_MODE_LTEM) ? "LTE-M" : "NB-IoT"); + } else { + *ptw_value = '\0'; + LOG_DBG("PTW use default for %s", + (mode == LTE_LC_LTE_MODE_LTEM) ? "LTE-M" : "NB-IoT"); + } + + return 0; +} + +int edrx_request(bool enable) +{ + int err = 0; + int actt[] = {AT_CEDRXS_ACTT_WB, AT_CEDRXS_ACTT_NB}; + + LOG_DBG("enable=%d, " + "requested_edrx_value_ltem=%s, edrx_value_ltem=%s, " + "requested_ptw_value_ltem=%s, ptw_value_ltem=%s, ", + enable, requested_edrx_value_ltem, edrx_value_ltem, requested_ptw_value_ltem, + ptw_value_ltem); + LOG_DBG("enable=%d, " + "requested_edrx_value_nbiot=%s, edrx_value_nbiot=%s, " + "requested_ptw_value_nbiot=%s, ptw_value_nbiot=%s", + enable, requested_edrx_value_nbiot, edrx_value_nbiot, requested_ptw_value_nbiot, + ptw_value_nbiot); + + requested_edrx_enable = enable; + + if (!enable) { + err = nrf_modem_at_printf("AT+CEDRXS=3"); + if (err) { + LOG_ERR("Failed to disable eDRX, reported error: %d", err); + return -EFAULT; + } + lte_lc_edrx_current_values_clear(); + + return 0; + } + + /* Apply the configurations for both LTE-M and NB-IoT. */ + for (size_t i = 0; i < ARRAY_SIZE(actt); i++) { + char *requested_edrx_value = (actt[i] == AT_CEDRXS_ACTT_WB) + ? requested_edrx_value_ltem + : requested_edrx_value_nbiot; + char *edrx_value = + (actt[i] == AT_CEDRXS_ACTT_WB) ? edrx_value_ltem : edrx_value_nbiot; + + if (strlen(requested_edrx_value) == 4) { + if (strcmp(edrx_value, requested_edrx_value) != 0) { + err = nrf_modem_at_printf("AT+CEDRXS=2,%d,\"%s\"", actt[i], + requested_edrx_value); + } else { + /* If current eDRX value is equal to requested value, set PTW */ + edrx_ptw_send_work_fn(NULL); + } + } else { + err = nrf_modem_at_printf("AT+CEDRXS=2,%d", actt[i]); + } + + if (err) { + LOG_ERR("Failed to enable eDRX, reported error: %d", err); + return -EFAULT; + } + } + + return 0; +} + +int edrx_param_set(enum lte_lc_lte_mode mode, const char *edrx) +{ + char *edrx_value; + + if (mode != LTE_LC_LTE_MODE_LTEM && mode != LTE_LC_LTE_MODE_NBIOT) { + LOG_ERR("LTE mode must be LTE-M or NB-IoT"); + return -EINVAL; + } + + if (edrx != NULL && strlen(edrx) != 4) { + return -EINVAL; + } + + edrx_value = (mode == LTE_LC_LTE_MODE_LTEM) ? requested_edrx_value_ltem + : requested_edrx_value_nbiot; + + if (edrx) { + strcpy(edrx_value, edrx); + LOG_DBG("eDRX set to %s for %s", edrx_value, + (mode == LTE_LC_LTE_MODE_LTEM) ? "LTE-M" : "NB-IoT"); + } else { + *edrx_value = '\0'; + LOG_DBG("eDRX use default for %s", + (mode == LTE_LC_LTE_MODE_LTEM) ? "LTE-M" : "NB-IoT"); + } + + return 0; +} diff --git a/lib/lte_link_control/modules/mdmev.c b/lib/lte_link_control/modules/mdmev.c new file mode 100644 index 00000000000..b8db636c07e --- /dev/null +++ b/lib/lte_link_control/modules/mdmev.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/event_handler_list.h" +#include "modules/mdmev.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +/* MDMEV command parameters */ +#define AT_MDMEV_ENABLE_1 "AT%%MDMEV=1" +#define AT_MDMEV_ENABLE_2 "AT%%MDMEV=2" +#define AT_MDMEV_DISABLE "AT%%MDMEV=0" +#define AT_MDMEV_RESPONSE_PREFIX "%MDMEV: " +#define AT_MDMEV_OVERHEATED "ME OVERHEATED\r\n" +#define AT_MDMEV_BATTERY_LOW "ME BATTERY LOW\r\n" +#define AT_MDMEV_SEARCH_STATUS_1 "SEARCH STATUS 1\r\n" +#define AT_MDMEV_SEARCH_STATUS_2 "SEARCH STATUS 2\r\n" +#define AT_MDMEV_RESET_LOOP "RESET LOOP\r\n" +#define AT_MDMEV_NO_IMEI "NO IMEI\r\n" +#define AT_MDMEV_CE_LEVEL_0 "PRACH CE-LEVEL 0\r\n" +#define AT_MDMEV_CE_LEVEL_1 "PRACH CE-LEVEL 1\r\n" +#define AT_MDMEV_CE_LEVEL_2 "PRACH CE-LEVEL 2\r\n" +#define AT_MDMEV_CE_LEVEL_3 "PRACH CE-LEVEL 3\r\n" + +AT_MONITOR(ltelc_atmon_mdmev, "%MDMEV", at_handler_mdmev); + +int mdmev_parse(const char *at_response, enum lte_lc_modem_evt *modem_evt) +{ + static const char *const event_types[] = { + [LTE_LC_MODEM_EVT_LIGHT_SEARCH_DONE] = AT_MDMEV_SEARCH_STATUS_1, + [LTE_LC_MODEM_EVT_SEARCH_DONE] = AT_MDMEV_SEARCH_STATUS_2, + [LTE_LC_MODEM_EVT_RESET_LOOP] = AT_MDMEV_RESET_LOOP, + [LTE_LC_MODEM_EVT_BATTERY_LOW] = AT_MDMEV_BATTERY_LOW, + [LTE_LC_MODEM_EVT_OVERHEATED] = AT_MDMEV_OVERHEATED, + [LTE_LC_MODEM_EVT_NO_IMEI] = AT_MDMEV_NO_IMEI, + [LTE_LC_MODEM_EVT_CE_LEVEL_0] = AT_MDMEV_CE_LEVEL_0, + [LTE_LC_MODEM_EVT_CE_LEVEL_1] = AT_MDMEV_CE_LEVEL_1, + [LTE_LC_MODEM_EVT_CE_LEVEL_2] = AT_MDMEV_CE_LEVEL_2, + [LTE_LC_MODEM_EVT_CE_LEVEL_3] = AT_MDMEV_CE_LEVEL_3, + }; + + __ASSERT_NO_MSG(at_response != NULL); + __ASSERT_NO_MSG(modem_evt != NULL); + + const char *start_ptr = at_response + sizeof(AT_MDMEV_RESPONSE_PREFIX) - 1; + + for (size_t i = 0; i < ARRAY_SIZE(event_types); i++) { + if (strcmp(event_types[i], start_ptr) == 0) { + LOG_DBG("Occurrence found: %s", event_types[i]); + *modem_evt = i; + + return 0; + } + } + + LOG_DBG("No modem event type found: %s", at_response); + + return -ENODATA; +} + +static void at_handler_mdmev(const char *response) +{ + int err; + struct lte_lc_evt evt = {0}; + + __ASSERT_NO_MSG(response != NULL); + + LOG_DBG("%%MDMEV notification"); + + err = mdmev_parse(response, &evt.modem_evt); + if (err) { + LOG_ERR("Can't parse modem event notification, error: %d", err); + return; + } + + evt.type = LTE_LC_EVT_MODEM_EVENT; + + event_handler_list_dispatch(&evt); +} + +int mdmev_enable(void) +{ + /* First try to enable both warning and informational type events, which is only supported + * by modem firmware versions >= 2.0.0. + * If that fails, try to enable the legacy set of events. + */ + if (nrf_modem_at_printf(AT_MDMEV_ENABLE_2)) { + if (nrf_modem_at_printf(AT_MDMEV_ENABLE_1)) { + return -EFAULT; + } + } + + return 0; +} + +int mdmev_disable(void) +{ + return nrf_modem_at_printf(AT_MDMEV_DISABLE) ? -EFAULT : 0; +} diff --git a/lib/lte_link_control/modules/ncellmeas.c b/lib/lte_link_control/modules/ncellmeas.c new file mode 100644 index 00000000000..f6c8bb19dbf --- /dev/null +++ b/lib/lte_link_control/modules/ncellmeas.c @@ -0,0 +1,791 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/event_handler_list.h" +#include "common/helpers.h" +#include "modules/ncellmeas.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +/* NCELLMEAS notification parameters */ +#define AT_NCELLMEAS_START "AT%%NCELLMEAS" +#define AT_NCELLMEAS_STOP "AT%%NCELLMEASSTOP" +#define AT_NCELLMEAS_STATUS_INDEX 1 +#define AT_NCELLMEAS_STATUS_VALUE_SUCCESS 0 +#define AT_NCELLMEAS_STATUS_VALUE_FAIL 1 +#define AT_NCELLMEAS_STATUS_VALUE_INCOMPLETE 2 +#define AT_NCELLMEAS_CELL_ID_INDEX 2 +#define AT_NCELLMEAS_PLMN_INDEX 3 +#define AT_NCELLMEAS_TAC_INDEX 4 +#define AT_NCELLMEAS_TIMING_ADV_INDEX 5 +#define AT_NCELLMEAS_EARFCN_INDEX 6 +#define AT_NCELLMEAS_PHYS_CELL_ID_INDEX 7 +#define AT_NCELLMEAS_RSRP_INDEX 8 +#define AT_NCELLMEAS_RSRQ_INDEX 9 +#define AT_NCELLMEAS_MEASUREMENT_TIME_INDEX 10 +#define AT_NCELLMEAS_PRE_NCELLS_PARAMS_COUNT 11 +/* The rest of the parameters are in repeating arrays per neighboring cell. + * The indices below refer to their index within such a repeating array. + */ +#define AT_NCELLMEAS_N_EARFCN_INDEX 0 +#define AT_NCELLMEAS_N_PHYS_CELL_ID_INDEX 1 +#define AT_NCELLMEAS_N_RSRP_INDEX 2 +#define AT_NCELLMEAS_N_RSRQ_INDEX 3 +#define AT_NCELLMEAS_N_TIME_DIFF_INDEX 4 +#define AT_NCELLMEAS_N_PARAMS_COUNT 5 +#define AT_NCELLMEAS_N_MAX_ARRAY_SIZE CONFIG_LTE_NEIGHBOR_CELLS_MAX + +#define AT_NCELLMEAS_PARAMS_COUNT_MAX \ + (AT_NCELLMEAS_PRE_NCELLS_PARAMS_COUNT + \ + AT_NCELLMEAS_N_PARAMS_COUNT * CONFIG_LTE_NEIGHBOR_CELLS_MAX) + +#define AT_NCELLMEAS_GCI_CELL_PARAMS_COUNT 12 + +/* Requested NCELLMEAS params */ +static struct lte_lc_ncellmeas_params ncellmeas_params; +/* Sempahore value 1 means ncellmeas is not ongoing, and 0 means it's ongoing. */ +K_SEM_DEFINE(ncellmeas_idle_sem, 1, 1); + +AT_MONITOR(ltelc_atmon_ncellmeas, "%NCELLMEAS", at_handler_ncellmeas); + +/* Counts the frequency of a character in a null-terminated string. */ +static uint32_t get_char_frequency(const char *str, char c) +{ + uint32_t count = 0; + + __ASSERT_NO_MSG(str != NULL); + + do { + if (*str == c) { + count++; + } + } while (*(str++) != '\0'); + + return count; +} + +static uint32_t neighborcell_count_get(const char *at_response) +{ + uint32_t comma_count, ncell_elements, ncell_count; + + __ASSERT_NO_MSG(at_response != NULL); + + comma_count = get_char_frequency(at_response, ','); + if (comma_count < AT_NCELLMEAS_PRE_NCELLS_PARAMS_COUNT) { + return 0; + } + + /* Add one, as there's no comma after the last element. */ + ncell_elements = comma_count - (AT_NCELLMEAS_PRE_NCELLS_PARAMS_COUNT - 1) + 1; + ncell_count = ncell_elements / AT_NCELLMEAS_N_PARAMS_COUNT; + + return ncell_count; +} + +static int parse_ncellmeas_gci(struct lte_lc_ncellmeas_params *params, const char *at_response, + struct lte_lc_cells_info *cells) +{ + struct at_parser parser; + struct lte_lc_ncell *ncells = NULL; + int err, status, tmp_int, len; + int16_t tmp_short; + char tmp_str[7]; + bool incomplete = false; + int curr_index; + size_t i = 0, j = 0, k = 0; + + /* Count the actual number of parameters in the AT response before + * allocating heap for it. This may save quite a bit of heap as the + * worst case scenario is 96 elements. + * 3 is added to account for the parameters that do not have a trailing + * comma. + */ + size_t param_count = get_char_frequency(at_response, ',') + 3; + + __ASSERT_NO_MSG(at_response != NULL); + __ASSERT_NO_MSG(params != NULL); + __ASSERT_NO_MSG(cells != NULL); + __ASSERT_NO_MSG(cells->gci_cells != NULL); + + /* Fill the defaults */ + cells->gci_cells_count = 0; + cells->ncells_count = 0; + cells->current_cell.id = LTE_LC_CELL_EUTRAN_ID_INVALID; + + for (i = 0; i < params->gci_count; i++) { + cells->gci_cells[i].id = LTE_LC_CELL_EUTRAN_ID_INVALID; + cells->gci_cells[i].timing_advance = LTE_LC_CELL_TIMING_ADVANCE_INVALID; + } + + /* + * Response format for GCI search types: + * High level: + * status[, + * GCI_cell_info1,neighbor_count1[,neighbor_cell1_1,neighbor_cell1_2...], + * GCI_cell_info2,neighbor_count2[,neighbor_cell2_1,neighbor_cell2_2...]...] + * + * Detailed: + * %NCELLMEAS: status + * [,,,,,,,,,, + * ,, + * [,,,,,] + * [,,,,,]...], + * ,,,,,,,,, + * ,, + * [,,,,,] + * [,,,,,]...]... + */ + + err = at_parser_init(&parser, at_response); + __ASSERT_NO_MSG(err == 0); + + /* Status code */ + curr_index = AT_NCELLMEAS_STATUS_INDEX; + err = at_parser_num_get(&parser, curr_index, &status); + if (err) { + LOG_DBG("Cannot parse NCELLMEAS status"); + goto clean_exit; + } + + if (status == AT_NCELLMEAS_STATUS_VALUE_FAIL) { + err = 1; + LOG_WRN("NCELLMEAS failed"); + goto clean_exit; + } else if (status == AT_NCELLMEAS_STATUS_VALUE_INCOMPLETE) { + LOG_WRN("NCELLMEAS interrupted; results incomplete"); + } + + /* Go through the cells */ + for (i = 0; curr_index < (param_count - (AT_NCELLMEAS_GCI_CELL_PARAMS_COUNT + 1)) && + i < params->gci_count; + i++) { + struct lte_lc_cell parsed_cell; + bool is_serving_cell; + uint8_t parsed_ncells_count; + + /* */ + curr_index++; + err = string_param_to_int(&parser, curr_index, &tmp_int, 16); + if (err) { + LOG_ERR("Could not parse cell_id, index %d, i %d error: %d", curr_index, i, + err); + goto clean_exit; + } + + if (tmp_int > LTE_LC_CELL_EUTRAN_ID_MAX) { + LOG_WRN("cell_id = %d which is > LTE_LC_CELL_EUTRAN_ID_MAX; " + "marking invalid", + tmp_int); + tmp_int = LTE_LC_CELL_EUTRAN_ID_INVALID; + } + parsed_cell.id = tmp_int; + + /* */ + len = sizeof(tmp_str); + + curr_index++; + err = at_parser_string_get(&parser, curr_index, tmp_str, &len); + if (err) { + LOG_ERR("Could not parse plmn, error: %d", err); + goto clean_exit; + } + + /* Read MNC and store as integer. The MNC starts as the fourth character + * in the string, following three characters long MCC. + */ + err = string_to_int(&tmp_str[3], 10, &parsed_cell.mnc); + if (err) { + LOG_ERR("string_to_int, error: %d", err); + goto clean_exit; + } + + /* Null-terminated MCC, read and store it. */ + tmp_str[3] = '\0'; + + err = string_to_int(tmp_str, 10, &parsed_cell.mcc); + if (err) { + LOG_ERR("string_to_int, error: %d", err); + goto clean_exit; + } + + /* */ + curr_index++; + err = string_param_to_int(&parser, curr_index, &tmp_int, 16); + if (err) { + LOG_ERR("Could not parse tracking_area_code in i %d, error: %d", i, err); + goto clean_exit; + } + parsed_cell.tac = tmp_int; + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &tmp_int); + if (err) { + LOG_ERR("Could not parse timing_advance, error: %d", err); + goto clean_exit; + } + parsed_cell.timing_advance = tmp_int; + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &parsed_cell.timing_advance_meas_time); + if (err) { + LOG_ERR("Could not parse timing_advance_meas_time, error: %d", err); + goto clean_exit; + } + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &parsed_cell.earfcn); + if (err) { + LOG_ERR("Could not parse earfcn, error: %d", err); + goto clean_exit; + } + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &parsed_cell.phys_cell_id); + if (err) { + LOG_ERR("Could not parse phys_cell_id, error: %d", err); + goto clean_exit; + } + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &parsed_cell.rsrp); + if (err) { + LOG_ERR("Could not parse rsrp, error: %d", err); + goto clean_exit; + } + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &parsed_cell.rsrq); + if (err) { + LOG_ERR("Could not parse rsrq, error: %d", err); + goto clean_exit; + } + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &parsed_cell.measurement_time); + if (err) { + LOG_ERR("Could not parse meas_time, error: %d", err); + goto clean_exit; + } + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &tmp_short); + if (err) { + LOG_ERR("Could not parse serving, error: %d", err); + goto clean_exit; + } + is_serving_cell = tmp_short; + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &tmp_short); + if (err) { + LOG_ERR("Could not parse neighbor_count, error: %d", err); + goto clean_exit; + } + parsed_ncells_count = tmp_short; + + if (is_serving_cell) { + int to_be_parsed_ncell_count = 0; + + /* This the current/serving cell. + * In practice the is always 0 for other than + * the serving cell, i.e. no neigbour cell list is available. + * Thus, handle neighbor cells only for the serving cell. + */ + cells->current_cell = parsed_cell; + if (parsed_ncells_count != 0) { + /* Allocate room for the parsed neighbor info. */ + if (parsed_ncells_count > CONFIG_LTE_NEIGHBOR_CELLS_MAX) { + to_be_parsed_ncell_count = CONFIG_LTE_NEIGHBOR_CELLS_MAX; + incomplete = true; + LOG_WRN("Cutting response, because received neigbor cell" + " count is bigger than configured max: %d", + CONFIG_LTE_NEIGHBOR_CELLS_MAX); + + } else { + to_be_parsed_ncell_count = parsed_ncells_count; + } + ncells = k_calloc(to_be_parsed_ncell_count, + sizeof(struct lte_lc_ncell)); + if (ncells == NULL) { + LOG_WRN("Failed to allocate memory for the ncells" + " (continue)"); + continue; + } + cells->neighbor_cells = ncells; + cells->ncells_count = to_be_parsed_ncell_count; + } + + /* Parse neighbors */ + for (j = 0; j < parsed_ncells_count; j++) { + /* If maximum number of cells has been stored, skip the data for + * the remaining ncells to be able to continue from next GCI cell + */ + if (j >= to_be_parsed_ncell_count) { + LOG_WRN("Ignoring ncell"); + curr_index += 5; + continue; + } + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, + &cells->neighbor_cells[j].earfcn); + if (err) { + LOG_ERR("Could not parse n_earfcn, error: %d", err); + goto clean_exit; + } + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, + &cells->neighbor_cells[j].phys_cell_id); + if (err) { + LOG_ERR("Could not parse n_phys_cell_id, error: %d", err); + goto clean_exit; + } + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &tmp_int); + if (err) { + LOG_ERR("Could not parse n_rsrp, error: %d", err); + goto clean_exit; + } + cells->neighbor_cells[j].rsrp = tmp_int; + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, &tmp_int); + if (err) { + LOG_ERR("Could not parse n_rsrq, error: %d", err); + goto clean_exit; + } + cells->neighbor_cells[j].rsrq = tmp_int; + + /* */ + curr_index++; + err = at_parser_num_get(&parser, curr_index, + &cells->neighbor_cells[j].time_diff); + if (err) { + LOG_ERR("Could not parse time_diff, error: %d", err); + goto clean_exit; + } + } + } else { + cells->gci_cells[k] = parsed_cell; + cells->gci_cells_count++; /* Increase count for non-serving GCI cell */ + k++; + } + } + + if (incomplete) { + err = -E2BIG; + LOG_WRN("Buffer is too small; results incomplete: %d", err); + } + +clean_exit: + return err; +} + +static int parse_ncellmeas(const char *at_response, struct lte_lc_cells_info *cells) +{ + int err, status, tmp; + struct at_parser parser; + size_t count = 0; + bool incomplete = false; + + __ASSERT_NO_MSG(at_response != NULL); + __ASSERT_NO_MSG(cells != NULL); + + cells->ncells_count = 0; + cells->current_cell.id = LTE_LC_CELL_EUTRAN_ID_INVALID; + + err = at_parser_init(&parser, at_response); + __ASSERT_NO_MSG(err == 0); + + /* Status code */ + err = at_parser_num_get(&parser, AT_NCELLMEAS_STATUS_INDEX, &status); + if (err) { + goto clean_exit; + } + + if (status == AT_NCELLMEAS_STATUS_VALUE_FAIL) { + err = 1; + LOG_WRN("NCELLMEAS failed"); + goto clean_exit; + } else if (status == AT_NCELLMEAS_STATUS_VALUE_INCOMPLETE) { + LOG_WRN("NCELLMEAS interrupted; results incomplete"); + } + + /* Current cell ID */ + err = string_param_to_int(&parser, AT_NCELLMEAS_CELL_ID_INDEX, &tmp, 16); + if (err) { + goto clean_exit; + } + + if (tmp > LTE_LC_CELL_EUTRAN_ID_MAX) { + tmp = LTE_LC_CELL_EUTRAN_ID_INVALID; + } + cells->current_cell.id = tmp; + + /* PLMN, that is, MCC and MNC */ + err = plmn_param_string_to_mcc_mnc(&parser, AT_NCELLMEAS_PLMN_INDEX, + &cells->current_cell.mcc, &cells->current_cell.mnc); + if (err) { + goto clean_exit; + } + + /* Tracking area code */ + err = string_param_to_int(&parser, AT_NCELLMEAS_TAC_INDEX, &tmp, 16); + if (err) { + goto clean_exit; + } + + cells->current_cell.tac = tmp; + + /* Timing advance */ + err = at_parser_num_get(&parser, AT_NCELLMEAS_TIMING_ADV_INDEX, &tmp); + if (err) { + goto clean_exit; + } + + cells->current_cell.timing_advance = tmp; + + /* EARFCN */ + err = at_parser_num_get(&parser, AT_NCELLMEAS_EARFCN_INDEX, &cells->current_cell.earfcn); + if (err) { + goto clean_exit; + } + + /* Physical cell ID */ + err = at_parser_num_get(&parser, AT_NCELLMEAS_PHYS_CELL_ID_INDEX, + &cells->current_cell.phys_cell_id); + if (err) { + goto clean_exit; + } + + /* RSRP */ + err = at_parser_num_get(&parser, AT_NCELLMEAS_RSRP_INDEX, &tmp); + if (err) { + goto clean_exit; + } + + cells->current_cell.rsrp = tmp; + + /* RSRQ */ + err = at_parser_num_get(&parser, AT_NCELLMEAS_RSRQ_INDEX, &tmp); + if (err) { + goto clean_exit; + } + + cells->current_cell.rsrq = tmp; + + /* Measurement time */ + err = at_parser_num_get(&parser, AT_NCELLMEAS_MEASUREMENT_TIME_INDEX, + &cells->current_cell.measurement_time); + if (err) { + goto clean_exit; + } + + /* Neighbor cell count */ + cells->ncells_count = neighborcell_count_get(at_response); + + /* Starting from modem firmware v1.3.1, timing advance measurement time + * information is added as the last parameter in the response. + */ + size_t ta_meas_time_index = AT_NCELLMEAS_PRE_NCELLS_PARAMS_COUNT + + cells->ncells_count * AT_NCELLMEAS_N_PARAMS_COUNT; + + err = at_parser_cmd_count_get(&parser, &count); + if (err) { + LOG_ERR("Could not get NCELLMEAS param count, " + "potentially malformed notification, error: %d", + err); + goto clean_exit; + } + + if (count > ta_meas_time_index) { + err = at_parser_num_get(&parser, ta_meas_time_index, + &cells->current_cell.timing_advance_meas_time); + if (err) { + goto clean_exit; + } + } else { + cells->current_cell.timing_advance_meas_time = 0; + } + + if (cells->ncells_count == 0) { + goto clean_exit; + } + + __ASSERT_NO_MSG(cells->neighbor_cells != NULL); + + if (cells->ncells_count > CONFIG_LTE_NEIGHBOR_CELLS_MAX) { + cells->ncells_count = CONFIG_LTE_NEIGHBOR_CELLS_MAX; + incomplete = true; + LOG_WRN("Cutting response, because received neigbor cell" + " count is bigger than configured max: %d", + CONFIG_LTE_NEIGHBOR_CELLS_MAX); + } + + /* Neighboring cells */ + for (size_t i = 0; i < cells->ncells_count; i++) { + size_t start_idx = + AT_NCELLMEAS_PRE_NCELLS_PARAMS_COUNT + i * AT_NCELLMEAS_N_PARAMS_COUNT; + + /* EARFCN */ + err = at_parser_num_get(&parser, start_idx + AT_NCELLMEAS_N_EARFCN_INDEX, + &cells->neighbor_cells[i].earfcn); + if (err) { + goto clean_exit; + } + + /* Physical cell ID */ + err = at_parser_num_get(&parser, start_idx + AT_NCELLMEAS_N_PHYS_CELL_ID_INDEX, + &cells->neighbor_cells[i].phys_cell_id); + if (err) { + goto clean_exit; + } + + /* RSRP */ + err = at_parser_num_get(&parser, start_idx + AT_NCELLMEAS_N_RSRP_INDEX, &tmp); + if (err) { + goto clean_exit; + } + + cells->neighbor_cells[i].rsrp = tmp; + + /* RSRQ */ + err = at_parser_num_get(&parser, start_idx + AT_NCELLMEAS_N_RSRQ_INDEX, &tmp); + if (err) { + goto clean_exit; + } + + cells->neighbor_cells[i].rsrq = tmp; + + /* Time difference */ + err = at_parser_num_get(&parser, start_idx + AT_NCELLMEAS_N_TIME_DIFF_INDEX, + &cells->neighbor_cells[i].time_diff); + if (err) { + goto clean_exit; + } + } + + if (incomplete) { + err = -E2BIG; + LOG_WRN("Buffer is too small; results incomplete: %d", err); + } + +clean_exit: + return err; +} + +static void at_handler_ncellmeas_gci(const char *response) +{ + int err; + struct lte_lc_evt evt = {0}; + const char *resp = response; + struct lte_lc_cell *cells = NULL; + + __ASSERT_NO_MSG(response != NULL); + __ASSERT_NO_MSG(ncellmeas_params.gci_count != 0); + + LOG_DBG("%%NCELLMEAS GCI notification parsing starts"); + + cells = k_calloc(ncellmeas_params.gci_count, sizeof(struct lte_lc_cell)); + if (cells == NULL) { + LOG_ERR("Failed to allocate memory for the GCI cells"); + return; + } + + evt.cells_info.gci_cells = cells; + err = parse_ncellmeas_gci(&ncellmeas_params, resp, &evt.cells_info); + LOG_DBG("parse_ncellmeas_gci returned %d", err); + switch (err) { + case -E2BIG: + LOG_WRN("Not all neighbor cells could be parsed. " + "More cells than the configured max count of %d were found", + CONFIG_LTE_NEIGHBOR_CELLS_MAX); + /* Fall through */ + case 0: /* Fall through */ + case 1: + LOG_DBG("Neighbor cell count: %d, GCI cells count: %d", evt.cells_info.ncells_count, + evt.cells_info.gci_cells_count); + evt.type = LTE_LC_EVT_NEIGHBOR_CELL_MEAS; + event_handler_list_dispatch(&evt); + break; + default: + LOG_ERR("Parsing of neighbor cells failed, err: %d", err); + break; + } + + k_free(cells); + k_free(evt.cells_info.neighbor_cells); +} + +static void at_handler_ncellmeas(const char *response) +{ + int err; + struct lte_lc_evt evt = {0}; + + __ASSERT_NO_MSG(response != NULL); + + if (event_handler_list_is_empty()) { + /* No need to parse the response if there is no handler + * to receive the parsed data. + */ + goto exit; + } + + if (ncellmeas_params.search_type > LTE_LC_NEIGHBOR_SEARCH_TYPE_EXTENDED_COMPLETE) { + at_handler_ncellmeas_gci(response); + goto exit; + } + + int ncell_count = neighborcell_count_get(response); + struct lte_lc_ncell *neighbor_cells = NULL; + + LOG_DBG("%%NCELLMEAS notification: neighbor cell count: %d", ncell_count); + + if (ncell_count != 0) { + neighbor_cells = k_calloc(ncell_count, sizeof(struct lte_lc_ncell)); + if (neighbor_cells == NULL) { + LOG_ERR("Failed to allocate memory for neighbor cells"); + goto exit; + } + } + + evt.cells_info.neighbor_cells = neighbor_cells; + + err = parse_ncellmeas(response, &evt.cells_info); + + switch (err) { + case -E2BIG: + LOG_WRN("Not all neighbor cells could be parsed"); + LOG_WRN("More cells than the configured max count of %d were found", + CONFIG_LTE_NEIGHBOR_CELLS_MAX); + /* Fall through */ + case 0: /* Fall through */ + case 1: + evt.type = LTE_LC_EVT_NEIGHBOR_CELL_MEAS; + event_handler_list_dispatch(&evt); + break; + default: + LOG_ERR("Parsing of neighbor cells failed, err: %d", err); + break; + } + + if (neighbor_cells) { + k_free(neighbor_cells); + } +exit: + k_sem_give(&ncellmeas_idle_sem); +} + +int ncellmeas_start(struct lte_lc_ncellmeas_params *params) +{ + int err; + /* lte_lc defaults for the used params */ + struct lte_lc_ncellmeas_params used_params = { + .search_type = LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT, + .gci_count = 0, + }; + + if (params == NULL) { + LOG_DBG("Using default parameters"); + } + LOG_DBG("Search type=%d, gci_count=%d", + params != NULL ? params->search_type : used_params.search_type, + params != NULL ? params->gci_count : used_params.gci_count); + + __ASSERT(!IN_RANGE((int)params, LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT, + LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_COMPLETE), + "Invalid argument, API does not accept enum values directly anymore"); + + if (params != NULL && + (params->search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_DEFAULT || + params->search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_LIGHT || + params->search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_COMPLETE)) { + if (params->gci_count < 2 || params->gci_count > 15) { + LOG_ERR("Invalid GCI count, must be in range 2-15"); + return -EINVAL; + } + } + + if (k_sem_take(&ncellmeas_idle_sem, K_SECONDS(1)) != 0) { + LOG_WRN("Neighbor cell measurement already in progress"); + return -EINPROGRESS; + } + + if (params != NULL) { + used_params = *params; + } + ncellmeas_params = used_params; + + /* Starting from modem firmware v1.3.1, there is an optional parameter to specify + * the type of search. + * If the type is LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT, we therefore use the AT + * command without parameters to avoid error messages for older firmware version. + * Starting from modem firmware v1.3.4, additional CGI search types and + * GCI count are supported. + */ + if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_EXTENDED_LIGHT) { + err = nrf_modem_at_printf("AT%%NCELLMEAS=1"); + } else if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_EXTENDED_COMPLETE) { + err = nrf_modem_at_printf("AT%%NCELLMEAS=2"); + } else if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_DEFAULT) { + err = nrf_modem_at_printf("AT%%NCELLMEAS=3,%d", used_params.gci_count); + } else if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_LIGHT) { + err = nrf_modem_at_printf("AT%%NCELLMEAS=4,%d", used_params.gci_count); + } else if (used_params.search_type == LTE_LC_NEIGHBOR_SEARCH_TYPE_GCI_EXTENDED_COMPLETE) { + err = nrf_modem_at_printf("AT%%NCELLMEAS=5,%d", used_params.gci_count); + } else { + /* Defaulting to use LTE_LC_NEIGHBOR_SEARCH_TYPE_DEFAULT */ + err = nrf_modem_at_printf("AT%%NCELLMEAS"); + } + + if (err) { + LOG_ERR("Sending AT%%NCELLMEAS failed, error: %d", err); + err = -EFAULT; + k_sem_give(&ncellmeas_idle_sem); + } + + return err; +} + +int ncellmeas_cancel(void) +{ + LOG_DBG("Cancelling"); + + int err = nrf_modem_at_printf(AT_NCELLMEAS_STOP); + + if (err) { + err = -EFAULT; + } + + k_sem_give(&ncellmeas_idle_sem); + + return err; +} diff --git a/lib/lte_link_control/modules/periodicsearchconf.c b/lib/lte_link_control/modules/periodicsearchconf.c new file mode 100644 index 00000000000..4195eb93c69 --- /dev/null +++ b/lib/lte_link_control/modules/periodicsearchconf.c @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "modules/periodicsearchconf.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +static int periodicsearchconf_parse(const char *const pattern_str, + struct lte_lc_periodic_search_pattern *pattern) +{ + int err; + int values[5]; + size_t param_count; + + __ASSERT_NO_MSG(pattern_str != NULL); + __ASSERT_NO_MSG(pattern != NULL); + + err = sscanf(pattern_str, "%d,%u,%u,%u,%u,%u", (int *)&pattern->type, &values[0], + &values[1], &values[2], &values[3], &values[4]); + if (err < 1) { + LOG_ERR("Unrecognized pattern type"); + return -EBADMSG; + } + + param_count = err; + + if ((pattern->type == LTE_LC_PERIODIC_SEARCH_PATTERN_RANGE) && (param_count >= 3)) { + /* The 'time_to_final_sleep' parameter is optional and may not always be present. + * If that's the case, there will be only 3 matches, and we need a + * workaround to get the 'pattern_end_point' value. + */ + if (param_count == 3) { + param_count = sscanf(pattern_str, "%*u,%*u,%*u,,%u", &values[3]); + if (param_count != 1) { + LOG_ERR("Could not find 'pattern_end_point' value"); + return -EBADMSG; + } + + values[2] = -1; + } + + pattern->range.initial_sleep = values[0]; + pattern->range.final_sleep = values[1]; + pattern->range.time_to_final_sleep = values[2]; + pattern->range.pattern_end_point = values[3]; + } else if ((pattern->type == LTE_LC_PERIODIC_SEARCH_PATTERN_TABLE) && (param_count >= 2)) { + /* Populate optional parameters only if matched, otherwise set + * to disabled, -1. + */ + pattern->table.val_1 = values[0]; + pattern->table.val_2 = param_count > 2 ? values[1] : -1; + pattern->table.val_3 = param_count > 3 ? values[2] : -1; + pattern->table.val_4 = param_count > 4 ? values[3] : -1; + pattern->table.val_5 = param_count > 5 ? values[4] : -1; + } else { + LOG_DBG("No valid pattern found"); + return -EBADMSG; + } + + return 0; +} + +static char * +periodicsearchconf_pattern_get(char *const buf, size_t buf_size, + const struct lte_lc_periodic_search_pattern *const pattern) +{ + int len = 0; + + __ASSERT_NO_MSG(buf != NULL); + __ASSERT_NO_MSG(pattern != NULL); + + if (pattern->type == LTE_LC_PERIODIC_SEARCH_PATTERN_RANGE) { + /* Range format: + * ",,,[], + * " + */ + if (pattern->range.time_to_final_sleep != -1) { + len = snprintk(buf, buf_size, "\"0,%u,%u,%u,%u\"", + pattern->range.initial_sleep, pattern->range.final_sleep, + pattern->range.time_to_final_sleep, + pattern->range.pattern_end_point); + } else { + len = snprintk(buf, buf_size, "\"0,%u,%u,,%u\"", + pattern->range.initial_sleep, pattern->range.final_sleep, + pattern->range.pattern_end_point); + } + } else if (pattern->type == LTE_LC_PERIODIC_SEARCH_PATTERN_TABLE) { + /* Table format: ",[,][,][,][,]". */ + if (pattern->table.val_2 == -1) { + len = snprintk(buf, buf_size, "\"1,%u\"", pattern->table.val_1); + } else if (pattern->table.val_3 == -1) { + len = snprintk(buf, buf_size, "\"1,%u,%u\"", pattern->table.val_1, + pattern->table.val_2); + } else if (pattern->table.val_4 == -1) { + len = snprintk(buf, buf_size, "\"1,%u,%u,%u\"", pattern->table.val_1, + pattern->table.val_2, pattern->table.val_3); + } else if (pattern->table.val_5 == -1) { + len = snprintk(buf, buf_size, "\"1,%u,%u,%u,%u\"", pattern->table.val_1, + pattern->table.val_2, pattern->table.val_3, + pattern->table.val_4); + } else { + len = snprintk(buf, buf_size, "\"1,%u,%u,%u,%u,%u\"", pattern->table.val_1, + pattern->table.val_2, pattern->table.val_3, + pattern->table.val_4, pattern->table.val_5); + } + } else { + LOG_ERR("Unrecognized periodic search pattern type"); + buf[0] = '\0'; + } + + if (len >= buf_size) { + LOG_ERR("Encoding periodic search pattern failed. Too small buffer (%d/%d)", len, + buf_size); + buf[0] = '\0'; + } + + return buf; +} + +int periodicsearchconf_set(const struct lte_lc_periodic_search_cfg *const cfg) +{ + int err; + char pattern_buf[4][40]; + + if (!cfg || (cfg->pattern_count == 0) || (cfg->pattern_count > 4)) { + return -EINVAL; + } + + /* Command syntax: + * AT%PERIODICSEARCHCONF=[,,,, + * [,][,][,]] + */ + + err = nrf_modem_at_printf( + "AT%%PERIODICSEARCHCONF=0," /* Write mode */ + "%hu," /* */ + "%hu," /* */ + "%hu," /* */ + "%s%s" /* */ + "%s%s" /* */ + "%s%s" /* */ + "%s", /* */ + cfg->loop, cfg->return_to_pattern, cfg->band_optimization, + /* Pattern 1 */ + periodicsearchconf_pattern_get(pattern_buf[0], sizeof(pattern_buf[0]), + &cfg->patterns[0]), + /* Pattern 2, if configured */ + cfg->pattern_count > 1 ? "," : "", + cfg->pattern_count > 1 + ? periodicsearchconf_pattern_get(pattern_buf[1], sizeof(pattern_buf[1]), + &cfg->patterns[1]) + : "", + /* Pattern 3, if configured */ + cfg->pattern_count > 2 ? "," : "", + cfg->pattern_count > 2 + ? periodicsearchconf_pattern_get(pattern_buf[2], sizeof(pattern_buf[2]), + &cfg->patterns[2]) + : "", + /* Pattern 4, if configured */ + cfg->pattern_count > 3 ? "," : "", + cfg->pattern_count > 3 + ? periodicsearchconf_pattern_get(pattern_buf[3], sizeof(pattern_buf[3]), + &cfg->patterns[3]) + : ""); + if (err < 0) { + /* Failure to send the AT command. */ + LOG_ERR("AT command failed, returned error code: %d", err); + return -EFAULT; + } else if (err > 0) { + /* The modem responded with "ERROR". */ + LOG_ERR("The modem rejected the configuration, err: %d", err); + return -EBADMSG; + } + + return 0; +} + +int periodicsearchconf_get(struct lte_lc_periodic_search_cfg *const cfg) +{ + + int err; + char pattern_buf[4][40]; + uint16_t loop_tmp; + + if (!cfg) { + return -EINVAL; + } + + /* Response format: + * %PERIODICSEARCHCONF=,,, + * [,][,][,] + */ + + err = nrf_modem_at_scanf("AT%PERIODICSEARCHCONF=1", + "%%PERIODICSEARCHCONF: " + "%hu," /* */ + "%hu," /* */ + "%hu," /* */ + "\"%40[^\"]\"," /* */ + "\"%40[^\"]\"," /* */ + "\"%40[^\"]\"," /* */ + "\"%40[^\"]\"", /* */ + &loop_tmp, &cfg->return_to_pattern, &cfg->band_optimization, + pattern_buf[0], pattern_buf[1], pattern_buf[2], pattern_buf[3]); + if (err == -NRF_EBADMSG) { + return -ENOENT; + } else if (err < 0) { + return -EFAULT; + } else if (err < 4) { + /* Not all parameters and at least one pattern found */ + return -EBADMSG; + } + + cfg->loop = loop_tmp; + + /* Pattern count is matched parameters minus 3 for loop, return_to_pattern + * and band_optimization. + */ + cfg->pattern_count = err - 3; + + LOG_DBG("Pattern 1: %s", pattern_buf[0]); + + err = periodicsearchconf_parse(pattern_buf[0], &cfg->patterns[0]); + if (err) { + LOG_ERR("Failed to parse periodic search pattern"); + return err; + } + + if (cfg->pattern_count >= 2) { + LOG_DBG("Pattern 2: %s", pattern_buf[1]); + + err = periodicsearchconf_parse(pattern_buf[1], &cfg->patterns[1]); + if (err) { + LOG_ERR("Failed to parse periodic search pattern"); + return err; + } + } + + if (cfg->pattern_count >= 3) { + LOG_DBG("Pattern 3: %s", pattern_buf[2]); + + err = periodicsearchconf_parse(pattern_buf[2], &cfg->patterns[2]); + if (err) { + LOG_ERR("Failed to parse periodic search pattern"); + return err; + } + } + + if (cfg->pattern_count == 4) { + LOG_DBG("Pattern 4: %s", pattern_buf[3]); + + err = periodicsearchconf_parse(pattern_buf[3], &cfg->patterns[3]); + if (err) { + LOG_ERR("Failed to parse periodic search pattern"); + return err; + } + } + + return 0; +} + +int periodicsearchconf_clear(void) +{ + int err; + + err = nrf_modem_at_printf("AT%%PERIODICSEARCHCONF=2"); + if (err < 0) { + return -EFAULT; + } else if (err > 0) { + return -EBADMSG; + } + + return 0; +} + +int periodicsearchconf_request(void) +{ + return nrf_modem_at_printf("AT%%PERIODICSEARCHCONF=3") ? -EFAULT : 0; +} diff --git a/lib/lte_link_control/modules/psm.c b/lib/lte_link_control/modules/psm.c new file mode 100644 index 00000000000..72a57f4d5e0 --- /dev/null +++ b/lib/lte_link_control/modules/psm.c @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/event_handler_list.h" +#include "modules/psm.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +static void psm_get_work_fn(struct k_work *work_item); +K_WORK_DEFINE(psm_get_work, psm_get_work_fn); + +/* Different values in the T3324 lookup table. */ +#define T3324_LOOKUP_DIFFERENT_VALUES 3 +/* Different values in the T3412 extended lookup table. */ +#define T3412_EXT_LOOKUP_DIFFERENT_VALUES 7 +/* Maximum value for the timer value field of the timer information elements in both + * T3324 and T3412 extended lookup tables. + */ +#define TIMER_VALUE_MAX 31 + +/* String format that can be used in printf-style functions for printing a byte as a bit field. */ +#define BYTE_TO_BINARY_STRING_FORMAT "%c%c%c%c%c%c%c%c" +/* Arguments for a byte in a bit field string representation. */ +#define BYTE_TO_BINARY_STRING_ARGS(byte) \ + ((byte)&0x80 ? '1' : '0'), ((byte)&0x40 ? '1' : '0'), ((byte)&0x20 ? '1' : '0'), \ + ((byte)&0x10 ? '1' : '0'), ((byte)&0x08 ? '1' : '0'), ((byte)&0x04 ? '1' : '0'), \ + ((byte)&0x02 ? '1' : '0'), ((byte)&0x01 ? '1' : '0') + +/* Lookup table for T3412-extended timer used for periodic TAU. Unit is seconds. + * Ref: GPRS Timer 3 in 3GPP TS 24.008 Table 10.5.163a. + */ +static const uint32_t t3412_ext_lookup[8] = {600, 3600, 36000, 2, 30, 60, 1152000, 0}; + +/* Lookup table for T3412 (legacy) timer used for periodic TAU. Unit is seconds. + * Ref: GPRS Timer in 3GPP TS 24.008 Table 10.5.172. + */ +static const uint32_t t3412_lookup[8] = {2, 60, 360, 60, 60, 60, 60, 0}; + +/* Lookup table for T3324 timer used for PSM active time in seconds. + * Ref: GPRS Timer 2 IE in 3GPP TS 24.008 Table 10.5.163. + */ +static const uint32_t t3324_lookup[8] = {2, 60, 360, 60, 60, 60, 60, 0}; + +/* Requested PSM RAT setting */ +static char requested_psm_param_rat[9] = CONFIG_LTE_PSM_REQ_RAT; +/* Requested PSM RPTAU setting */ +static char requested_psm_param_rptau[9] = CONFIG_LTE_PSM_REQ_RPTAU; +/* Request PSM to be disabled and timers set to default values */ +static const char psm_disable[] = "AT+CPSMS="; + +/* Internal enums */ + +enum feaconf_oper { + FEACONF_OPER_WRITE = 0, + FEACONF_OPER_READ = 1, + FEACONF_OPER_LIST = 2 +}; + +enum feaconf_feat { + FEACONF_FEAT_PROPRIETARY_PSM = 0 +}; + +static int feaconf_write(enum feaconf_feat feat, bool state) +{ + return nrf_modem_at_printf("AT%%FEACONF=%d,%d,%u", FEACONF_OPER_WRITE, feat, state); +} + +void psm_evt_update_send(struct lte_lc_psm_cfg *psm_cfg) +{ + static struct lte_lc_psm_cfg prev_psm_cfg; + struct lte_lc_evt evt = {0}; + + /* PSM configuration update event */ + if ((psm_cfg->tau != prev_psm_cfg.tau) || + (psm_cfg->active_time != prev_psm_cfg.active_time)) { + evt.type = LTE_LC_EVT_PSM_UPDATE; + + memcpy(&prev_psm_cfg, psm_cfg, sizeof(struct lte_lc_psm_cfg)); + memcpy(&evt.psm_cfg, psm_cfg, sizeof(struct lte_lc_psm_cfg)); + event_handler_list_dispatch(&evt); + } +} + +static void psm_get_work_fn(struct k_work *work_item) +{ + int err; + struct lte_lc_psm_cfg psm_cfg = {.active_time = -1, .tau = -1}; + + err = psm_get(&psm_cfg.tau, &psm_cfg.active_time); + if (err) { + if (err != -EBADMSG) { + LOG_ERR("Failed to get PSM information"); + } + return; + } + + psm_evt_update_send(&psm_cfg); +} + +static int psm_encode_timer(char *encoded_timer_str, uint32_t requested_value, + const uint32_t *lookup_table, uint8_t lookup_table_size) +{ + /* Timer unit and timer value refer to the terminology used in 3GPP 24.008 + * Ch. 10.5.7.4a and Ch. 10.5.7.3. for bits 6 to 8 and bits 1 to 5, respectively + */ + + /* Lookup table index to the currently selected timer unit */ + uint32_t selected_index = -1; + /* Currently calculated value for the timer, which tries to match the requested value */ + uint32_t selected_value = -1; + /* Currently selected timer value */ + uint32_t selected_timer_value = -1; + /* Timer unit and timer value encoded as an integer which will be converted to a string */ + uint8_t encoded_value_int = 0; + + __ASSERT_NO_MSG(encoded_timer_str != NULL); + __ASSERT_NO_MSG(lookup_table != NULL); + + /* Search a value that is as close as possible to the requested value + * rounding it up to the closest possible value + */ + for (int i = 0; i < lookup_table_size; i++) { + uint32_t current_timer_value = requested_value / lookup_table[i]; + + if (requested_value % lookup_table[i] > 0) { + /* Round up the time when it's not divisible by the timer unit */ + current_timer_value++; + } + + /* Current timer unit is so small that timer value range is not enough */ + if (current_timer_value > TIMER_VALUE_MAX) { + continue; + } + + uint32_t current_value = lookup_table[i] * current_timer_value; + + /* Use current timer unit if current value is closer to requested value + * than currently selected values + */ + if (selected_value == -1 || + current_value - requested_value < selected_value - requested_value) { + selected_value = current_value; + selected_index = i; + selected_timer_value = current_timer_value; + } + } + + if (selected_index != -1) { + LOG_DBG("requested_value=%d, selected_value=%d, selected_timer_unit=%d, " + "selected_timer_value=%d, selected_index=%d", + requested_value, selected_value, lookup_table[selected_index], + selected_timer_value, selected_index); + + /* Selected index (timer unit) is in bits 6 to 8 */ + encoded_value_int = (selected_index << 5) | selected_timer_value; + sprintf(encoded_timer_str, BYTE_TO_BINARY_STRING_FORMAT, + BYTE_TO_BINARY_STRING_ARGS(encoded_value_int)); + } else { + LOG_ERR("requested_value=%d is too big to be represented by the timer encoding", + requested_value); + return -EINVAL; + } + + return 0; +} + +static int psm_encode(char *tau_ext_str, char *active_time_str, int rptau, int rat) +{ + int ret = 0; + + LOG_DBG("TAU: %d sec, active time: %d sec", rptau, rat); + + __ASSERT_NO_MSG(tau_ext_str != NULL); + __ASSERT_NO_MSG(active_time_str != NULL); + + if (rptau >= 0) { + ret = psm_encode_timer(tau_ext_str, rptau, t3412_ext_lookup, + T3412_EXT_LOOKUP_DIFFERENT_VALUES); + } else { + *tau_ext_str = '\0'; + LOG_DBG("Using modem default value for RPTAU"); + } + + if (rat >= 0) { + ret |= psm_encode_timer(active_time_str, rat, t3324_lookup, + T3324_LOOKUP_DIFFERENT_VALUES); + } else { + *active_time_str = '\0'; + LOG_DBG("Using modem default value for RAT"); + } + + return ret; +} + +int psm_param_set(const char *rptau, const char *rat) +{ + if ((rptau != NULL && strlen(rptau) != 8) || (rat != NULL && strlen(rat) != 8)) { + return -EINVAL; + } + + if (rptau != NULL) { + strcpy(requested_psm_param_rptau, rptau); + LOG_DBG("RPTAU set to %s", requested_psm_param_rptau); + } else { + *requested_psm_param_rptau = '\0'; + LOG_DBG("Using modem default value for RPTAU"); + } + + if (rat != NULL) { + strcpy(requested_psm_param_rat, rat); + LOG_DBG("RAT set to %s", requested_psm_param_rat); + } else { + *requested_psm_param_rat = '\0'; + LOG_DBG("Using modem default value for RAT"); + } + + return 0; +} + +int psm_param_set_seconds(int rptau, int rat) +{ + int ret; + + ret = psm_encode(requested_psm_param_rptau, requested_psm_param_rat, rptau, rat); + + if (ret != 0) { + *requested_psm_param_rptau = '\0'; + *requested_psm_param_rat = '\0'; + } + + LOG_DBG("RPTAU=%d (%s), RAT=%d (%s), ret=%d", rptau, requested_psm_param_rptau, rat, + requested_psm_param_rat, ret); + + return ret; +} + +int psm_req(bool enable) +{ + int err; + + LOG_DBG("enable=%d, tau=%s, rat=%s", enable, requested_psm_param_rptau, + requested_psm_param_rat); + + if (enable) { + if (strlen(requested_psm_param_rptau) == 8 && + strlen(requested_psm_param_rat) == 8) { + err = nrf_modem_at_printf("AT+CPSMS=1,,,\"%s\",\"%s\"", + requested_psm_param_rptau, + requested_psm_param_rat); + } else if (strlen(requested_psm_param_rptau) == 8) { + err = nrf_modem_at_printf("AT+CPSMS=1,,,\"%s\"", requested_psm_param_rptau); + } else if (strlen(requested_psm_param_rat) == 8) { + err = nrf_modem_at_printf("AT+CPSMS=1,,,,\"%s\"", requested_psm_param_rat); + } else { + err = nrf_modem_at_printf("AT+CPSMS=1"); + } + } else { + err = nrf_modem_at_printf(psm_disable); + } + + if (err) { + LOG_ERR("nrf_modem_at_printf failed, reported error: %d", err); + return -EFAULT; + } + + return 0; +} + +int psm_get(int *tau, int *active_time) +{ + int err; + struct lte_lc_psm_cfg psm_cfg; + struct at_parser parser; + char active_time_str[9] = {0}; + char tau_ext_str[9] = {0}; + char tau_legacy_str[9] = {0}; + int len; + int reg_status = 0; + static char response[160] = {0}; + + if ((tau == NULL) || (active_time == NULL)) { + return -EINVAL; + } + + /* Format of XMONITOR AT command response: + * %XMONITOR: ,[,,,,,,, + * ,,,,,, + * ,] + * We need to parse the three last parameters, Active-Time, Periodic-TAU-ext and + * Periodic-TAU. + */ + + response[0] = '\0'; + + err = nrf_modem_at_cmd(response, sizeof(response), "AT%%XMONITOR"); + if (err) { + LOG_ERR("AT command failed, error: %d", err); + return -EFAULT; + } + + err = at_parser_init(&parser, response); + __ASSERT_NO_MSG(err == 0); + + /* Check registration status */ + err = at_parser_num_get(&parser, 1, ®_status); + if (err) { + LOG_ERR("AT command parsing failed, error: %d", err); + return -EBADMSG; + } else if (reg_status != LTE_LC_NW_REG_REGISTERED_HOME && + reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING) { + LOG_WRN("No PSM parameters because device not registered, status: %d", reg_status); + return -EBADMSG; + } + + /* */ + len = sizeof(active_time_str); + err = at_parser_string_get(&parser, 14, active_time_str, &len); + if (err) { + LOG_ERR("AT command parsing failed, error: %d", err); + return -EBADMSG; + } + + /* */ + len = sizeof(tau_ext_str); + err = at_parser_string_get(&parser, 15, tau_ext_str, &len); + if (err) { + LOG_ERR("AT command parsing failed, error: %d", err); + return -EBADMSG; + } + + /* */ + len = sizeof(tau_legacy_str); + err = at_parser_string_get(&parser, 16, tau_legacy_str, &len); + if (err) { + LOG_ERR("AT command parsing failed, error: %d", err); + return -EBADMSG; + } + + err = psm_parse(active_time_str, tau_ext_str, tau_legacy_str, &psm_cfg); + if (err) { + LOG_ERR("Failed to parse PSM configuration, error: %d", err); + return -EBADMSG; + } + + *tau = psm_cfg.tau; + *active_time = psm_cfg.active_time; + + LOG_DBG("TAU: %d sec, active time: %d sec", *tau, *active_time); + + return 0; +} + +int psm_proprietary_req(bool enable) +{ + if (feaconf_write(FEACONF_FEAT_PROPRIETARY_PSM, enable) != 0) { + return -EFAULT; + } + + return 0; +} + +int psm_parse(const char *active_time_str, const char *tau_ext_str, const char *tau_legacy_str, + struct lte_lc_psm_cfg *psm_cfg) +{ + char unit_str[4] = {0}; + size_t unit_str_len = sizeof(unit_str) - 1; + size_t lut_idx; + uint32_t timer_unit, timer_value; + + __ASSERT_NO_MSG(active_time_str != NULL); + __ASSERT_NO_MSG(tau_ext_str != NULL); + __ASSERT_NO_MSG(psm_cfg != NULL); + + if (strlen(active_time_str) != 8 || strlen(tau_ext_str) != 8 || + (tau_legacy_str != NULL && strlen(tau_legacy_str) != 8)) { + return -EINVAL; + } + + /* Parse T3412-extended (periodic TAU) timer */ + memcpy(unit_str, tau_ext_str, unit_str_len); + + lut_idx = strtoul(unit_str, NULL, 2); + __ASSERT_NO_MSG(lut_idx < ARRAY_SIZE(t3412_ext_lookup)); + + timer_unit = t3412_ext_lookup[lut_idx]; + timer_value = strtoul(tau_ext_str + unit_str_len, NULL, 2); + psm_cfg->tau = timer_unit ? timer_unit * timer_value : -1; + + /* If T3412-extended is disabled, periodic TAU is reported using the T3412 legacy timer + * if the caller requests for it + */ + if (psm_cfg->tau == -1 && tau_legacy_str != NULL) { + memcpy(unit_str, tau_legacy_str, unit_str_len); + + lut_idx = strtoul(unit_str, NULL, 2); + __ASSERT_NO_MSG(lut_idx < ARRAY_SIZE(t3412_lookup)); + + timer_unit = t3412_lookup[lut_idx]; + if (timer_unit == 0) { + /* TAU must be reported either using T3412-extended or T3412 (legacy) + * timer, so the timer unit is expected to be valid. + */ + LOG_ERR("Expected valid T3412 timer unit"); + return -EINVAL; + } + timer_value = strtoul(tau_legacy_str + unit_str_len, NULL, 2); + psm_cfg->tau = timer_unit * timer_value; + } + + /* Parse active time */ + memcpy(unit_str, active_time_str, unit_str_len); + + lut_idx = strtoul(unit_str, NULL, 2); + __ASSERT_NO_MSG(lut_idx < ARRAY_SIZE(t3324_lookup)); + + timer_unit = t3324_lookup[lut_idx]; + timer_value = strtoul(active_time_str + unit_str_len, NULL, 2); + psm_cfg->active_time = timer_unit ? timer_unit * timer_value : -1; + + LOG_DBG("TAU: %d sec, active time: %d sec", psm_cfg->tau, psm_cfg->active_time); + + return 0; +} + +struct k_work *psm_work_get(void) +{ + return &psm_get_work; +} diff --git a/lib/lte_link_control/modules/rai.c b/lib/lte_link_control/modules/rai.c new file mode 100644 index 00000000000..59a2ca5feee --- /dev/null +++ b/lib/lte_link_control/modules/rai.c @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/event_handler_list.h" +#include "common/helpers.h" +#include "modules/rai.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +/* RAI notification parameters */ +#define AT_RAI_RESPONSE_PREFIX "%RAI" +#define AT_RAI_PARAMS_COUNT_MAX 5 +#define AT_RAI_CELL_ID_INDEX 1 +#define AT_RAI_PLMN_INDEX 2 +#define AT_RAI_AS_INDEX 3 +#define AT_RAI_CP_INDEX 4 + +AT_MONITOR(ltelc_atmon_rai, "%RAI", at_handler_rai); + +static int parse_rai(const char *at_response, struct lte_lc_rai_cfg *rai_cfg) +{ + struct at_parser parser; + int err; + int tmp_int; + + err = at_parser_init(&parser, at_response); + __ASSERT_NO_MSG(err == 0); + + /* Cell Id */ + err = string_param_to_int(&parser, AT_RAI_CELL_ID_INDEX, &tmp_int, 16); + if (err) { + LOG_ERR("Could not parse cell_id, error: %d", err); + goto clean_exit; + } + + if (tmp_int > LTE_LC_CELL_EUTRAN_ID_MAX) { + LOG_ERR("Invalid cell ID: %d", tmp_int); + err = -EBADMSG; + goto clean_exit; + } + rai_cfg->cell_id = tmp_int; + + /* PLMN, that is, MCC and MNC */ + err = plmn_param_string_to_mcc_mnc( + &parser, AT_RAI_PLMN_INDEX, &rai_cfg->mcc, &rai_cfg->mnc); + if (err) { + goto clean_exit; + } + + /* AS RAI configuration */ + err = at_parser_num_get(&parser, AT_RAI_AS_INDEX, &tmp_int); + if (err) { + LOG_ERR("Could not get AS RAI, error: %d", err); + goto clean_exit; + } + rai_cfg->as_rai = tmp_int; + + /* CP RAI configuration */ + err = at_parser_num_get(&parser, AT_RAI_CP_INDEX, &tmp_int); + if (err) { + LOG_ERR("Could not get CP RAI, error: %d", err); + goto clean_exit; + } + rai_cfg->cp_rai = tmp_int; + +clean_exit: + return err; +} + +static void at_handler_rai(const char *response) +{ + int err; + struct lte_lc_evt evt = {0}; + + __ASSERT_NO_MSG(response != NULL); + + LOG_DBG("%%RAI notification"); + + err = parse_rai(response, &evt.rai_cfg); + if (err) { + LOG_ERR("Can't parse RAI notification, error: %d", err); + return; + } + + evt.type = LTE_LC_EVT_RAI_UPDATE; + + event_handler_list_dispatch(&evt); +} + +int rai_set(void) +{ + int err; + + if (IS_ENABLED(CONFIG_LTE_RAI_REQ)) { + LOG_DBG("Enabling RAI with notifications"); + } else { + LOG_DBG("Disabling RAI"); + } + + err = nrf_modem_at_printf("AT%%RAI=%d", IS_ENABLED(CONFIG_LTE_RAI_REQ) ? 2 : 0); + if (err) { + if (IS_ENABLED(CONFIG_LTE_RAI_REQ)) { + LOG_DBG("Failed to enable RAI with notifications so trying without them"); + /* If AT%RAI=2 failed, modem might not support it so using older API */ + err = nrf_modem_at_printf("AT%%RAI=1"); + } + if (err) { + LOG_ERR("Failed to configure RAI, err %d", err); + return -EFAULT; + } + } + + return err; +} diff --git a/lib/lte_link_control/modules/redmob.c b/lib/lte_link_control/modules/redmob.c new file mode 100644 index 00000000000..187d81b215d --- /dev/null +++ b/lib/lte_link_control/modules/redmob.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "modules/redmob.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +int redmob_get(enum lte_lc_reduced_mobility_mode *mode) +{ + int ret; + uint16_t mode_tmp; + + if (mode == NULL) { + return -EINVAL; + } + + ret = nrf_modem_at_scanf("AT%REDMOB?", "%%REDMOB: %hu", &mode_tmp); + if (ret != 1) { + LOG_ERR("AT command failed, nrf_modem_at_scanf() returned error: %d", ret); + return -EFAULT; + } + + *mode = mode_tmp; + + return 0; +} + +int redmob_set(enum lte_lc_reduced_mobility_mode mode) +{ + int ret = nrf_modem_at_printf("AT%%REDMOB=%d", mode); + + if (ret) { + /* Failure to send the AT command. */ + LOG_ERR("AT command failed, returned error code: %d", ret); + return -EFAULT; + } + + return 0; +} diff --git a/lib/lte_link_control/modules/xfactoryreset.c b/lib/lte_link_control/modules/xfactoryreset.c new file mode 100644 index 00000000000..62e676ec0fc --- /dev/null +++ b/lib/lte_link_control/modules/xfactoryreset.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include "modules/xfactoryreset.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +int xfactoryreset_reset(enum lte_lc_factory_reset_type type) +{ + return nrf_modem_at_printf("AT%%XFACTORYRESET=%d", type) ? -EFAULT : 0; +} diff --git a/lib/lte_link_control/modules/xmodemsleep.c b/lib/lte_link_control/modules/xmodemsleep.c new file mode 100644 index 00000000000..9a05924f1a0 --- /dev/null +++ b/lib/lte_link_control/modules/xmodemsleep.c @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/event_handler_list.h" +#include "modules/xmodemsleep.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +/* XMODEMSLEEP command parameters. */ +#define AT_XMODEMSLEEP_SUB "AT%%XMODEMSLEEP=1,%d,%d" +#define AT_XMODEMSLEEP_PARAMS_COUNT_MAX 4 +#define AT_XMODEMSLEEP_TYPE_INDEX 1 +#define AT_XMODEMSLEEP_TIME_INDEX 2 + +AT_MONITOR(ltelc_atmon_xmodemsleep, "%XMODEMSLEEP", at_handler_xmodemsleep); + +static int parse_xmodemsleep(const char *at_response, struct lte_lc_modem_sleep *modem_sleep) +{ + int err; + struct at_parser parser; + uint16_t type; + size_t count = 0; + + __ASSERT_NO_MSG(at_response != NULL); + __ASSERT_NO_MSG(modem_sleep != NULL); + + err = at_parser_init(&parser, at_response); + __ASSERT_NO_MSG(err == 0); + + err = at_parser_num_get(&parser, AT_XMODEMSLEEP_TYPE_INDEX, &type); + if (err) { + LOG_ERR("Could not get mode sleep type, error: %d", err); + goto clean_exit; + } + modem_sleep->type = type; + + err = at_parser_cmd_count_get(&parser, &count); + if (err) { + LOG_ERR("Could not get XMODEMSLEEP param count, " + "potentially malformed notification, error: %d", + err); + goto clean_exit; + } + + if (count < AT_XMODEMSLEEP_PARAMS_COUNT_MAX - 1) { + modem_sleep->time = -1; + goto clean_exit; + } + + err = at_parser_num_get(&parser, AT_XMODEMSLEEP_TIME_INDEX, &modem_sleep->time); + if (err) { + LOG_ERR("Could not get time until next modem sleep, error: %d", err); + goto clean_exit; + } + +clean_exit: + return err; +} + +static void at_handler_xmodemsleep(const char *response) +{ + int err; + struct lte_lc_evt evt = {0}; + + __ASSERT_NO_MSG(response != NULL); + + LOG_DBG("%%XMODEMSLEEP notification"); + + err = parse_xmodemsleep(response, &evt.modem_sleep); + if (err) { + LOG_ERR("Can't parse modem sleep pre-warning notification, error: %d", err); + return; + } + + /* Link controller only supports PSM, RF inactivity, limited service, flight mode + * and proprietary PSM modem sleep types. + */ + if ((evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_PSM) && + (evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_RF_INACTIVITY) && + (evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_LIMITED_SERVICE) && + (evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_FLIGHT_MODE) && + (evt.modem_sleep.type != LTE_LC_MODEM_SLEEP_PROPRIETARY_PSM)) { + return; + } + + /* Propagate the appropriate event depending on the parsed time parameter. */ + if (evt.modem_sleep.time == CONFIG_LTE_LC_MODEM_SLEEP_PRE_WARNING_TIME_MS) { + evt.type = LTE_LC_EVT_MODEM_SLEEP_EXIT_PRE_WARNING; + } else if (evt.modem_sleep.time == 0) { + LTE_LC_TRACE(LTE_LC_TRACE_MODEM_SLEEP_EXIT); + + evt.type = LTE_LC_EVT_MODEM_SLEEP_EXIT; + } else { + LTE_LC_TRACE(LTE_LC_TRACE_MODEM_SLEEP_ENTER); + + evt.type = LTE_LC_EVT_MODEM_SLEEP_ENTER; + } + + event_handler_list_dispatch(&evt); +} + +int xmodemsleep_notifications_enable(void) +{ + int err; + + if (IS_ENABLED(CONFIG_LTE_LC_MODEM_SLEEP_NOTIFICATIONS)) { + /* %XMODEMSLEEP notifications subscribe */ + err = nrf_modem_at_printf(AT_XMODEMSLEEP_SUB, + CONFIG_LTE_LC_MODEM_SLEEP_PRE_WARNING_TIME_MS, + CONFIG_LTE_LC_MODEM_SLEEP_NOTIFICATIONS_THRESHOLD_MS); + if (err) { + LOG_WRN("Enabling modem sleep notifications failed, error: %d", err); + LOG_WRN("Modem sleep notifications require nRF9160 modem >= v1.3.0"); + return err; + } + } + + return 0; +} diff --git a/lib/lte_link_control/modules/xsystemmode.c b/lib/lte_link_control/modules/xsystemmode.c new file mode 100644 index 00000000000..37157b962b9 --- /dev/null +++ b/lib/lte_link_control/modules/xsystemmode.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "modules/xsystemmode.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +#define AT_XSYSTEMMODE_READ "AT%XSYSTEMMODE?" + +/* The indices are for the set command. Add 1 for the read command indices. */ +#define AT_XSYSTEMMODE_READ_LTEM_INDEX 1 +#define AT_XSYSTEMMODE_READ_NBIOT_INDEX 2 +#define AT_XSYSTEMMODE_READ_GPS_INDEX 3 +#define AT_XSYSTEMMODE_READ_PREFERENCE_INDEX 4 + +/* Internal system mode value used when CONFIG_LTE_NETWORK_MODE_DEFAULT is enabled. */ +#define LTE_LC_SYSTEM_MODE_DEFAULT 0xff + +#define SYS_MODE_PREFERRED \ + (IS_ENABLED(CONFIG_LTE_NETWORK_MODE_LTE_M) ? LTE_LC_SYSTEM_MODE_LTEM \ + : IS_ENABLED(CONFIG_LTE_NETWORK_MODE_NBIOT) ? LTE_LC_SYSTEM_MODE_NBIOT \ + : IS_ENABLED(CONFIG_LTE_NETWORK_MODE_LTE_M_GPS) ? LTE_LC_SYSTEM_MODE_LTEM_GPS \ + : IS_ENABLED(CONFIG_LTE_NETWORK_MODE_NBIOT_GPS) ? LTE_LC_SYSTEM_MODE_NBIOT_GPS \ + : IS_ENABLED(CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT) ? LTE_LC_SYSTEM_MODE_LTEM_NBIOT \ + : IS_ENABLED(CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT_GPS) ? LTE_LC_SYSTEM_MODE_LTEM_NBIOT_GPS \ + : LTE_LC_SYSTEM_MODE_DEFAULT) + +/* The preferred system mode to use when connecting to LTE network. Can be changed by calling + * lte_lc_system_mode_set(). + * + * extern in lte_lc_modem_hooks.c + */ +enum lte_lc_system_mode lte_lc_sys_mode = SYS_MODE_PREFERRED; +/* System mode preference to set when configuring system mode. Can be changed by calling + * lte_lc_system_mode_set(). + * + * extern in lte_lc_modem_hooks.c + */ +enum lte_lc_system_mode_preference lte_lc_sys_mode_pref = CONFIG_LTE_MODE_PREFERENCE_VALUE; + +/* Parameters to be passed using AT%XSYSTEMMMODE=, */ +static const char *const system_mode_params[] = { + [LTE_LC_SYSTEM_MODE_LTEM] = "1,0,0", + [LTE_LC_SYSTEM_MODE_NBIOT] = "0,1,0", + [LTE_LC_SYSTEM_MODE_GPS] = "0,0,1", + [LTE_LC_SYSTEM_MODE_LTEM_GPS] = "1,0,1", + [LTE_LC_SYSTEM_MODE_NBIOT_GPS] = "0,1,1", + [LTE_LC_SYSTEM_MODE_LTEM_NBIOT] = "1,1,0", + [LTE_LC_SYSTEM_MODE_LTEM_NBIOT_GPS] = "1,1,1", +}; + +/* LTE preference to be passed using AT%XSYSTEMMMODE=, */ +static const char system_mode_preference[] = { + /* No LTE preference, automatically selected by the modem. */ + [LTE_LC_SYSTEM_MODE_PREFER_AUTO] = '0', + /* LTE-M has highest priority. */ + [LTE_LC_SYSTEM_MODE_PREFER_LTEM] = '1', + /* NB-IoT has highest priority. */ + [LTE_LC_SYSTEM_MODE_PREFER_NBIOT] = '2', + /* Equal priority, but prefer LTE-M. */ + [LTE_LC_SYSTEM_MODE_PREFER_LTEM_PLMN_PRIO] = '3', + /* Equal priority, but prefer NB-IoT. */ + [LTE_LC_SYSTEM_MODE_PREFER_NBIOT_PLMN_PRIO] = '4', +}; + +int xsystemmode_mode_set(enum lte_lc_system_mode mode, + enum lte_lc_system_mode_preference preference) +{ + int err; + + switch (mode) { + case LTE_LC_SYSTEM_MODE_LTEM: + case LTE_LC_SYSTEM_MODE_LTEM_GPS: + case LTE_LC_SYSTEM_MODE_NBIOT: + case LTE_LC_SYSTEM_MODE_NBIOT_GPS: + case LTE_LC_SYSTEM_MODE_GPS: + case LTE_LC_SYSTEM_MODE_LTEM_NBIOT: + case LTE_LC_SYSTEM_MODE_LTEM_NBIOT_GPS: + break; + default: + LOG_ERR("Invalid system mode requested: %d", mode); + return -EINVAL; + } + + switch (preference) { + case LTE_LC_SYSTEM_MODE_PREFER_AUTO: + case LTE_LC_SYSTEM_MODE_PREFER_LTEM: + case LTE_LC_SYSTEM_MODE_PREFER_NBIOT: + case LTE_LC_SYSTEM_MODE_PREFER_LTEM_PLMN_PRIO: + case LTE_LC_SYSTEM_MODE_PREFER_NBIOT_PLMN_PRIO: + break; + default: + LOG_ERR("Invalid LTE preference requested: %d", preference); + return -EINVAL; + } + + err = nrf_modem_at_printf("AT%%XSYSTEMMODE=%s,%c", system_mode_params[mode], + system_mode_preference[preference]); + if (err) { + LOG_ERR("Could not send AT command, error: %d", err); + return -EFAULT; + } + + lte_lc_sys_mode = mode; + lte_lc_sys_mode_pref = preference; + + LOG_DBG("System mode set to %d, preference %d", lte_lc_sys_mode, lte_lc_sys_mode_pref); + + return 0; +} + +int xsystemmode_mode_get(enum lte_lc_system_mode *mode, + enum lte_lc_system_mode_preference *preference) +{ + int err; + int mode_bitmask = 0; + int ltem_mode = 0; + int nbiot_mode = 0; + int gps_mode = 0; + int mode_preference = 0; + + if (mode == NULL) { + return -EINVAL; + } + + /* It's expected to have all 4 arguments matched */ + err = nrf_modem_at_scanf(AT_XSYSTEMMODE_READ, "%%XSYSTEMMODE: %d,%d,%d,%d", <em_mode, + &nbiot_mode, &gps_mode, &mode_preference); + if (err != 4) { + LOG_ERR("Failed to get system mode, error: %d", err); + return -EFAULT; + } + + mode_bitmask = (ltem_mode ? BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX) : 0) | + (nbiot_mode ? BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX) : 0) | + (gps_mode ? BIT(AT_XSYSTEMMODE_READ_GPS_INDEX) : 0); + + switch (mode_bitmask) { + case BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX): + *mode = LTE_LC_SYSTEM_MODE_LTEM; + break; + case BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX): + *mode = LTE_LC_SYSTEM_MODE_NBIOT; + break; + case BIT(AT_XSYSTEMMODE_READ_GPS_INDEX): + *mode = LTE_LC_SYSTEM_MODE_GPS; + break; + case (BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX) | BIT(AT_XSYSTEMMODE_READ_GPS_INDEX)): + *mode = LTE_LC_SYSTEM_MODE_LTEM_GPS; + break; + case (BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX) | BIT(AT_XSYSTEMMODE_READ_GPS_INDEX)): + *mode = LTE_LC_SYSTEM_MODE_NBIOT_GPS; + break; + case (BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX) | BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX)): + *mode = LTE_LC_SYSTEM_MODE_LTEM_NBIOT; + break; + case (BIT(AT_XSYSTEMMODE_READ_LTEM_INDEX) | BIT(AT_XSYSTEMMODE_READ_NBIOT_INDEX) | + BIT(AT_XSYSTEMMODE_READ_GPS_INDEX)): + *mode = LTE_LC_SYSTEM_MODE_LTEM_NBIOT_GPS; + break; + default: + LOG_ERR("Invalid system mode, assuming parsing error"); + return -EFAULT; + } + + /* Get LTE preference. */ + if (preference != NULL) { + switch (mode_preference) { + case 0: + *preference = LTE_LC_SYSTEM_MODE_PREFER_AUTO; + break; + case 1: + *preference = LTE_LC_SYSTEM_MODE_PREFER_LTEM; + break; + case 2: + *preference = LTE_LC_SYSTEM_MODE_PREFER_NBIOT; + break; + case 3: + *preference = LTE_LC_SYSTEM_MODE_PREFER_LTEM_PLMN_PRIO; + break; + case 4: + *preference = LTE_LC_SYSTEM_MODE_PREFER_NBIOT_PLMN_PRIO; + break; + default: + LOG_ERR("Unsupported LTE preference: %d", mode_preference); + return -EFAULT; + } + } + + return 0; +} diff --git a/lib/lte_link_control/modules/xt3412.c b/lib/lte_link_control/modules/xt3412.c new file mode 100644 index 00000000000..54415094a24 --- /dev/null +++ b/lib/lte_link_control/modules/xt3412.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/event_handler_list.h" +#include "modules/xt3412.h" + +LOG_MODULE_DECLARE(lte_lc, CONFIG_LTE_LINK_CONTROL_LOG_LEVEL); + +AT_MONITOR(ltelc_atmon_xt3412, "%XT3412", at_handler_xt3412); + +/* XT3412 command parameters */ +#define AT_XT3412_SUB "AT%%XT3412=1,%d,%d" +#define AT_XT3412_TIME_INDEX 1 +#define T3412_MAX 35712000000 + +static int parse_xt3412(const char *at_response, uint64_t *time) +{ + int err; + struct at_parser parser; + + __ASSERT_NO_MSG(at_response != NULL); + __ASSERT_NO_MSG(time != NULL); + + err = at_parser_init(&parser, at_response); + __ASSERT_NO_MSG(err == 0); + + /* Get the remaining time of T3412 from the response */ + err = at_parser_num_get(&parser, AT_XT3412_TIME_INDEX, time); + if (err) { + if (err == -ERANGE) { + err = -EINVAL; + } + LOG_ERR("Could not get time until next TAU, error: %d", err); + goto clean_exit; + } + + if ((*time > T3412_MAX) || *time < 0) { + LOG_WRN("Parsed time parameter not within valid range"); + err = -EINVAL; + } + +clean_exit: + return err; +} + +static void at_handler_xt3412(const char *response) +{ + int err; + struct lte_lc_evt evt = {0}; + + __ASSERT_NO_MSG(response != NULL); + + LOG_DBG("%%XT3412 notification"); + + err = parse_xt3412(response, &evt.time); + if (err) { + LOG_ERR("Can't parse TAU pre-warning notification, error: %d", err); + return; + } + + if (evt.time != CONFIG_LTE_LC_TAU_PRE_WARNING_TIME_MS) { + /* Only propagate TAU pre-warning notifications when the received time + * parameter is the duration of the set pre-warning time. + */ + return; + } + + evt.type = LTE_LC_EVT_TAU_PRE_WARNING; + + event_handler_list_dispatch(&evt); +} + +int xt3412_notifications_enable(void) +{ + int err; + + if (IS_ENABLED(CONFIG_LTE_LC_TAU_PRE_WARNING_NOTIFICATIONS)) { + err = nrf_modem_at_printf(AT_XT3412_SUB, CONFIG_LTE_LC_TAU_PRE_WARNING_TIME_MS, + CONFIG_LTE_LC_TAU_PRE_WARNING_THRESHOLD_MS); + if (err) { + LOG_WRN("Enabling TAU pre-warning notifications failed, error: %d", err); + LOG_WRN("TAU pre-warning notifications require nRF9160 modem >= v1.3.0"); + return err; + } + } + + return 0; +} diff --git a/samples/cellular/battery/prj.conf b/samples/cellular/battery/prj.conf index ecdb83616e3..0005f49490d 100644 --- a/samples/cellular/battery/prj.conf +++ b/samples/cellular/battery/prj.conf @@ -19,6 +19,7 @@ CONFIG_NRF_MODEM_LIB=y # LTE link control CONFIG_LTE_LINK_CONTROL=y +CONFIG_LTE_LC_CONEVAL=y # Stacks and heaps CONFIG_HEAP_MEM_POOL_SIZE=1024 diff --git a/samples/cellular/location/prj.conf b/samples/cellular/location/prj.conf index d562ab93fee..c3cd418b9cb 100644 --- a/samples/cellular/location/prj.conf +++ b/samples/cellular/location/prj.conf @@ -36,6 +36,7 @@ CONFIG_LTE_LINK_CONTROL=y CONFIG_LTE_EDRX_REQ=y # Request PSM active time of 8 seconds. CONFIG_LTE_PSM_REQ_RAT="00000100" +CONFIG_LTE_LC_NCELLMEAS=y # AT Host library - Used to send AT commands directy from an UART terminal and to allow # integration with nRF Connect for Desktop LTE Link monitor application. diff --git a/samples/cellular/modem_shell/prj.conf b/samples/cellular/modem_shell/prj.conf index 282b7ff49d1..081a67d6859 100644 --- a/samples/cellular/modem_shell/prj.conf +++ b/samples/cellular/modem_shell/prj.conf @@ -119,6 +119,10 @@ CONFIG_LTE_LC_TAU_PRE_WARNING_THRESHOLD_MS=10240 CONFIG_LTE_LC_MODEM_SLEEP_NOTIFICATIONS_THRESHOLD_MS=10240 # Maximum number of neighbor cells for neighbor cell measurement CONFIG_LTE_NEIGHBOR_CELLS_MAX=17 +CONFIG_LTE_LC_REDMOB=y +CONFIG_LTE_LC_PERIODICSEARCHCONF=y +CONFIG_LTE_LC_NCELLMEAS=y +CONFIG_LTE_LC_CONEVAL=y CONFIG_DATE_TIME=y diff --git a/samples/cellular/nrf_cloud_multi_service/prj.conf b/samples/cellular/nrf_cloud_multi_service/prj.conf index badde160d8a..8ca0bc4b94e 100644 --- a/samples/cellular/nrf_cloud_multi_service/prj.conf +++ b/samples/cellular/nrf_cloud_multi_service/prj.conf @@ -105,6 +105,7 @@ CONFIG_DATE_TIME=y # LTE link control - used by PGPS and main application CONFIG_LTE_LINK_CONTROL=y +CONFIG_LTE_LC_NCELLMEAS=y # Settings - used by nRF Cloud library and PGPS CONFIG_SETTINGS=y diff --git a/samples/cellular/nrf_cloud_rest_cell_location/prj.conf b/samples/cellular/nrf_cloud_rest_cell_location/prj.conf index 4d9066f189c..71c7ce6b303 100644 --- a/samples/cellular/nrf_cloud_rest_cell_location/prj.conf +++ b/samples/cellular/nrf_cloud_rest_cell_location/prj.conf @@ -33,6 +33,7 @@ CONFIG_CAF_BUTTONS_POLARITY_INVERSED=y # Modem/LTE Link CONFIG_LTE_LINK_CONTROL=y +CONFIG_LTE_LC_NCELLMEAS=y # Modem info CONFIG_MODEM_INFO=y diff --git a/tests/lib/location/prj.conf b/tests/lib/location/prj.conf index 153a4193ae5..fe4bb4bba07 100644 --- a/tests/lib/location/prj.conf +++ b/tests/lib/location/prj.conf @@ -29,3 +29,4 @@ CONFIG_MOCK_NRF_MODEM_AT=y CONFIG_LOG=n CONFIG_LOCATION_LOG_LEVEL_DBG=n CONFIG_LTE_LINK_CONTROL_LOG_LEVEL_DBG=n +CONFIG_LTE_LC_NCELLMEAS=y diff --git a/tests/lib/lte_lc_api/prj.conf b/tests/lib/lte_lc_api/prj.conf index a106909b024..2967875c2f5 100644 --- a/tests/lib/lte_lc_api/prj.conf +++ b/tests/lib/lte_lc_api/prj.conf @@ -25,3 +25,14 @@ CONFIG_MOCK_NRF_MODEM_AT_SCANF_VARGS_STR_SIZE=32 #CONFIG_LTE_LINK_CONTROL_LOG_LEVEL_DBG=y CONFIG_STACK_SENTINEL=y CONFIG_ENTROPY_GENERATOR=y + +CONFIG_LTE_LC_CONEVAL=y +CONFIG_LTE_LC_EDRX=y +CONFIG_LTE_LC_NCELLMEAS=y +CONFIG_LTE_LC_PERIODICSEARCHCONF=y +CONFIG_LTE_LC_PSM=y +CONFIG_LTE_LC_RAI=y +CONFIG_LTE_LC_REDMOB=y +CONFIG_LTE_LC_XFACTORYRESET=y +CONFIG_LTE_LC_XMODEMSLEEP=y +CONFIG_LTE_LC_XT3412=y