diff --git a/applications/asset_tracker_v2/overlay-lwm2m.conf b/applications/asset_tracker_v2/overlay-lwm2m.conf index e14d143165dc..a66f4292aaf1 100644 --- a/applications/asset_tracker_v2/overlay-lwm2m.conf +++ b/applications/asset_tracker_v2/overlay-lwm2m.conf @@ -45,6 +45,7 @@ CONFIG_LWM2M_RD_CLIENT_STOP_POLLING_AT_IDLE=y CONFIG_LWM2M_QUEUE_MODE_UPTIME=30 # Configure PSM mode +CONFIG_LTE_LC_PSM_MODULE=y CONFIG_LTE_PSM_REQ=y # Request periodic TAU of 3600 seconds (60 minutes) CONFIG_LTE_PSM_REQ_RPTAU="00000110" @@ -56,6 +57,7 @@ CONFIG_LTE_PSM_REQ_RPTAU="00000110" CONFIG_LTE_PSM_REQ_RAT="00001111" # Request eDRX mode +CONFIG_LTE_LC_EDRX_MODULE=y CONFIG_LTE_EDRX_REQ=y # Requested eDRX cycle length for LTE-M and NB-IoT @@ -76,6 +78,7 @@ CONFIG_LTE_PTW_VALUE_NBIOT="0000" # Get notification before Tracking Area Update (TAU). Notification triggers registration # update and TAU will be sent with the update which decreases power consumption. +CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE=y CONFIG_LTE_LC_TAU_PRE_WARNING_NOTIFICATIONS=y # Optimize powersaving by using tickless mode in LwM2M engine diff --git a/applications/asset_tracker_v2/prj.conf b/applications/asset_tracker_v2/prj.conf index e9aa78506f17..7c6e966c1456 100644 --- a/applications/asset_tracker_v2/prj.conf +++ b/applications/asset_tracker_v2/prj.conf @@ -46,6 +46,11 @@ CONFIG_LTE_PSM_REQ=y CONFIG_LTE_PSM_REQ_RPTAU="11000001" ### 20 seconds active time. CONFIG_LTE_PSM_REQ_RAT="00001010" +### Enable required modules +CONFIG_LTE_LC_EDRX_MODULE=y +CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE=y +CONFIG_LTE_LC_PSM_MODULE=y +CONFIG_LTE_LC_MODEM_SLEEP_MODULE=y # Settings - Used to store real-time device configuration to flash. CONFIG_SETTINGS=y diff --git a/doc/nrf/libraries/modem/lte_lc.rst b/doc/nrf/libraries/modem/lte_lc.rst index 5ed1420bf1c0..da84f2589ad8 100644 --- a/doc/nrf/libraries/modem/lte_lc.rst +++ b/doc/nrf/libraries/modem/lte_lc.rst @@ -20,6 +20,9 @@ Configuration To enable the library, set the Kconfig option :kconfig:option:`CONFIG_LTE_LINK_CONTROL` to ``y`` in the project configuration file :file:`prj.conf`. +.. note:: + By default, the library enables only the core features related to the network connectivity. + Establishing an LTE connection ============================== @@ -50,18 +53,11 @@ The following block of code shows how you can use the API to establish an LTE co k_sem_give(<e_connected); break; - case LTE_LC_EVT_PSM_UPDATE: - case LTE_LC_EVT_EDRX_UPDATE: case LTE_LC_EVT_RRC_UPDATE: case LTE_LC_EVT_CELL_UPDATE: case LTE_LC_EVT_LTE_MODE_UPDATE: - case LTE_LC_EVT_TAU_PRE_WARNING: - case LTE_LC_EVT_NEIGHBOR_CELL_MEAS: - case LTE_LC_EVT_MODEM_SLEEP_EXIT_PRE_WARNING: - case LTE_LC_EVT_MODEM_SLEEP_EXIT: - case LTE_LC_EVT_MODEM_SLEEP_ENTER: case LTE_LC_EVT_MODEM_EVENT: - /* Handle LTE events */ + /* Handle LTE events that are enabled by default. */ break; default: @@ -87,17 +83,72 @@ The following block of code shows how you can use the API to establish an LTE co } The code block demonstrates how you can use the library to asynchronously set up an LTE connection. -For more information on the callback events received in :c:type:`lte_lc_evt_handler_t` and data associated with each event, see the documentation on :c:struct:`lte_lc_evt`. -The following list mentions some of the information that can be extracted from the received callback events: +Additionally, to enable specific functionalities and receive specific events from the library, you must enable the corresponding modules through their respective Kconfig options: + +Connection Parameters Evaluation: + Use the :kconfig:option:`CONFIG_LTE_LC_CONN_EVAL_MODULE` Kconfig option to enable the following functionality related to Connection Parameters Evaluation: + + * :c:func:`lte_lc_conn_eval_params_get` + +eDRX (Extended Discontinuous Reception): + Use the :kconfig:option:`CONFIG_LTE_LC_EDRX_MODULE` Kconfig option to enable all the following functionalities related to eDRX: + + * :c:enumerator:`LTE_LC_EVT_EDRX_UPDATE` events + * :c:func:`lte_lc_ptw_set` + * :c:func:`lte_lc_edrx_param_set` + * :c:func:`lte_lc_edrx_req` + * :c:func:`lte_lc_edrx_get` + * :kconfig:option:`CONFIG_LTE_EDRX_REQ` + +Neighboring Cell Measurements: + Use the :kconfig:option:`CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE` Kconfig option to enable all the following functionalities related to Neighboring Cell Measurements: + + * :c:enumerator:`LTE_LC_EVT_NEIGHBOR_CELL_MEAS` events + * :c:func:`lte_lc_neighbor_cell_measurement_cancel` + * :c:func:`lte_lc_neighbor_cell_measurement` + +Periodic Search Configuration: + Use the :kconfig:option:`CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE` Kconfig option to enable all the following functionalities related to Periodic Search Configuration: + + * :c:func:`lte_lc_periodic_search_request` + * :c:func:`lte_lc_periodic_search_clear` + * :c:func:`lte_lc_periodic_search_get` + * :c:func:`lte_lc_periodic_search_set` -* Network registration status -* PSM parameters -* eDRX parameters -* RRC connection state -* Cell information -* TAU pre-warning notifications -* Modem sleep notifications +PSM (Power Saving Mode): + Use the :kconfig:option:`CONFIG_LTE_LC_PSM_MODULE` Kconfig option to enable all the following functionalities related to PSM: + + * :c:enumerator:`LTE_LC_EVT_PSM_UPDATE` events + * :c:func:`lte_lc_psm_param_set` + * :c:func:`lte_lc_psm_param_set_seconds` + * :c:func:`lte_lc_psm_req` + * :c:func:`lte_lc_psm_get` + * :c:func:`lte_lc_proprietary_psm_req` + * :kconfig:option:`CONFIG_LTE_PSM_REQ` + +Release Assistance Indication (RAI): + Use the :kconfig:option:`CONFIG_LTE_LC_RAI_MODULE` Kconfig option to enable the following functionalities related to RAI: + + * :c:enumerator:`LTE_LC_EVT_RAI_UPDATE` events + * :kconfig:option:`CONFIG_LTE_RAI_REQ` + +Modem Sleep Notifications: + Use the :kconfig:option:`CONFIG_LTE_LC_MODEM_SLEEP_MODULE` Kconfig option to enable all the following functionalities related to Modem Sleep Notifications: + + * :c:enumerator:`LTE_LC_EVT_MODEM_SLEEP_EXIT_PRE_WARNING` events + * :c:enumerator:`LTE_LC_EVT_MODEM_SLEEP_ENTER` events + * :c:enumerator:`LTE_LC_EVT_MODEM_SLEEP_EXIT` events + * :kconfig:option:`CONFIG_LTE_LC_MODEM_SLEEP_NOTIFICATIONS` + +Tracking Area Update (TAU) Pre-warning: + Use the :kconfig:option:`CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE` Kconfig option to enable the following functionalities related to TAU Pre-warning: + + * :c:enumerator:`LTE_LC_EVT_TAU_PRE_WARNING` events + * :kconfig:option:`CONFIG_LTE_LC_TAU_PRE_WARNING_NOTIFICATIONS` + +For more information on the callback events received in :c:type:`lte_lc_evt_handler_t` and data associated with each event, see the documentation on :c:struct:`lte_lc_evt`. +For more information on the functions and data associated with each, refer to the API documentation. .. note:: Some of the functionalities might not be compatible with certain modem firmware versions. @@ -108,7 +159,12 @@ The following list mentions some of the information that can be extracted from t Enabling power-saving features ============================== -The recommended way of enabling power saving features is to use the Kconfig options :kconfig:option:`CONFIG_LTE_PSM_REQ` and :kconfig:option:`CONFIG_LTE_EDRX_REQ`. +To enable power-saving features, use the following options: + +* :kconfig:option:`CONFIG_LTE_LC_PSM_MODULE` +* :kconfig:option:`CONFIG_LTE_LC_EDRX_MODULE` +* :kconfig:option:`CONFIG_LTE_LC_PSM_REQ` +* :kconfig:option:`CONFIG_LTE_LC_EDRX_REQ` PSM and eDRX can also be requested at run time using the :c:func:`lte_lc_psm_req` and :c:func:`lte_lc_edrx_req` function calls. However, calling the functions during modem initialization can lead to conflicts with the value set by the Kconfig options. @@ -131,6 +187,7 @@ Connection pre-evaluation Modem firmware version 1.3.0 and higher supports connection a pre-evaluation feature that allows the application to get information about a cell that is likely to be used for an RRC connection. Based on the parameters received in the function call, the application can decide whether to send application data or not. +To enable this module, use the :kconfig:option:`CONFIG_LTE_LC_CONN_EVAL_MODULE` Kconfig option. The function :c:func:`lte_lc_conn_eval_params_get` populates a structure of type :c:struct:`lte_lc_conn_eval_params` that includes information on the current consumption cost by the data transmission when utilizing the given cell. The following code snippet shows a basic implementation of :c:func:`lte_lc_conn_eval_params_get`: @@ -177,8 +234,10 @@ For instance, TAU pre-warning notifications can be used to schedule data transfe Modem sleep notifications can be used to schedule processing in the same operational window as the modem to limit the overall computation time of the nRF91 Series SiP. -To enable modem sleep and TAU pre-warning notifications, enable the following options: +To enable modem sleep and TAU pre-warning notifications, use the following options: +* :kconfig:option:`CONFIG_LTE_LC_MODEM_SLEEP_MODULE` +* :kconfig:option:`CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE` * :kconfig:option:`CONFIG_LTE_LC_MODEM_SLEEP_NOTIFICATIONS` * :kconfig:option:`CONFIG_LTE_LC_TAU_PRE_WARNING_NOTIFICATIONS` diff --git a/doc/nrf/nrf.doxyfile.in b/doc/nrf/nrf.doxyfile.in index 358e62635e3c..2dd34ad75c06 100644 --- a/doc/nrf/nrf.doxyfile.in +++ b/doc/nrf/nrf.doxyfile.in @@ -2464,7 +2464,15 @@ PREDEFINED = __DOXYGEN__ \ "CONFIG_ZIGBEE_ROLE_END_DEVICE=y" \ "CONFIG_ZIGBEE_FACTORY_RESET=y" \ "CONFIG_NRF_CLOUD_GATEWAY=y" \ - "CONFIG_BT_CENTRAL" + "CONFIG_BT_CENTRAL" \ + "CONFIG_LTE_LC_CONN_EVAL_MODULE=y" \ + "CONFIG_LTE_LC_EDRX_MODULE=y" \ + "CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE=y" \ + "CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE=y" \ + "CONFIG_LTE_LC_PSM_MODULE=y" \ + "CONFIG_LTE_LC_RAI_MODULE=y" \ + "CONFIG_LTE_LC_MODEM_SLEEP_MODULE=y" \ + "CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE=y" # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/doc/nrf/releases_and_maturity/migration/migration_guide_2.8.rst b/doc/nrf/releases_and_maturity/migration/migration_guide_2.8.rst index bdac23da5824..7c3b2d687cd5 100644 --- a/doc/nrf/releases_and_maturity/migration/migration_guide_2.8.rst +++ b/doc/nrf/releases_and_maturity/migration/migration_guide_2.8.rst @@ -139,6 +139,71 @@ LTE link control library Use the :kconfig:option:`CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT` or :kconfig:option:`CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT_GPS` Kconfig option instead. In addition, you can control the priority between LTE-M and NB-IoT using the :kconfig:option:`CONFIG_LTE_MODE_PREFERENCE` Kconfig option. + * The library has been reorganized into modules that are enabled via their respective Kconfig options. + This change requires the following updates: + + * If your application uses: + + * :c:func:`lte_lc_conn_eval_params_get` + + You must use the new :kconfig:option:`CONFIG_LTE_LC_CONN_EVAL_MODULE` Kconfig option. + + * If your application uses: + + * :c:enumerator:`LTE_LC_EVT_EDRX_UPDATE` + * :c:func:`lte_lc_ptw_set` + * :c:func:`lte_lc_edrx_param_set` + * :c:func:`lte_lc_edrx_req` + * :c:func:`lte_lc_edrx_get` + * :kconfig:option:`CONFIG_LTE_EDRX_REQ` + + You must use the new :kconfig:option:`CONFIG_LTE_LC_EDRX_MODULE` Kconfig option. + + * If your application uses: + + * :c:enumerator:`LTE_LC_EVT_NEIGHBOR_CELL_MEAS` + * :c:func:`lte_lc_neighbor_cell_measurement_cancel` + * :c:func:`lte_lc_neighbor_cell_measurement` + + You must use the new :kconfig:option:`CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE` Kconfig option. + + * If your application uses: + + * :c:func:`lte_lc_periodic_search_request` + * :c:func:`lte_lc_periodic_search_clear` + * :c:func:`lte_lc_periodic_search_get` + * :c:func:`lte_lc_periodic_search_set` + + You must use the new :kconfig:option:`CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE` Kconfig option. + + * If your application uses: + + * :c:enumerator:`LTE_LC_EVT_PSM_UPDATE` + * :c:func:`lte_lc_psm_param_set` + * :c:func:`lte_lc_psm_param_set_seconds` + * :c:func:`lte_lc_psm_req` + * :c:func:`lte_lc_psm_get` + * :c:func:`lte_lc_proprietary_psm_req` + * :kconfig:option:`CONFIG_LTE_PSM_REQ` + + You must use the new :kconfig:option:`CONFIG_LTE_LC_PSM_MODULE` Kconfig option. + + * If your application uses: + + * :c:enumerator:`LTE_LC_EVT_MODEM_SLEEP_EXIT_PRE_WARNING` + * :c:enumerator:`LTE_LC_EVT_MODEM_SLEEP_ENTER` + * :c:enumerator:`LTE_LC_EVT_MODEM_SLEEP_EXIT` + * :kconfig:option:`CONFIG_LTE_LC_MODEM_SLEEP_NOTIFICATIONS` + + You must use the new :kconfig:option:`CONFIG_LTE_LC_MODEM_SLEEP_MODULE` Kconfig option. + + * If your application uses: + + * :c:enumerator:`LTE_LC_EVT_TAU_PRE_WARNING` + * :kconfig:option:`CONFIG_LTE_LC_TAU_PRE_WARNING_NOTIFICATIONS` + + You must use the new :kconfig:option:`CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE` Kconfig option. + AT command parser ----------------- diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index 52fa11d65ee0..df29a0d9f9e4 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -853,24 +853,18 @@ Modem libraries * :ref:`lte_lc_readme` library: - * Removed: - - * The :c:func:`lte_lc_init` function. - All instances of this function can be removed without any additional actions. - * The :c:func:`lte_lc_deinit` function. - Use the :c:func:`lte_lc_power_off` function instead. - * The :c:func:`lte_lc_init_and_connect` function. - Use the :c:func:`lte_lc_connect` function instead. - * The :c:func:`lte_lc_init_and_connect_async` function. - Use the :c:func:`lte_lc_connect_async` function instead. - * The ``CONFIG_LTE_NETWORK_USE_FALLBACK`` Kconfig option. - Use the :kconfig:option:`CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT` or :kconfig:option:`CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT_GPS` Kconfig option instead. - In addition, you can control the priority between LTE-M and NB-IoT using the :kconfig:option:`CONFIG_LTE_MODE_PREFERENCE` Kconfig option. - - * Deprecated the :c:macro:`LTE_LC_ON_CFUN` macro. - Use the :c:macro:`NRF_MODEM_LIB_ON_CFUN` macro instead. + * Added: - * Added a new :c:enumerator:`LTE_LC_EVT_RAI_UPDATE` event that is enabled with the :kconfig:option:`CONFIG_LTE_RAI_REQ` Kconfig option. + * The :kconfig:option:`CONFIG_LTE_LC_CONN_EVAL_MODULE` Kconfig option to enable the Connection Parameters Evaluation module. + * The :kconfig:option:`CONFIG_LTE_LC_EDRX_MODULE` Kconfig option to enable the eDRX module. + * The :kconfig:option:`CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE` Kconfig option to enable the Neighboring Cell Measurements module. + * The :kconfig:option:`CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE` Kconfig option to enable the Periodic Search Configuration module. + * The :kconfig:option:`CONFIG_LTE_LC_PSM_MODULE` Kconfig option to enable the PSM module. + * The :kconfig:option:`CONFIG_LTE_LC_RAI_MODULE` Kconfig option to enable the RAI module. + * The :kconfig:option:`CONFIG_LTE_LC_MODEM_SLEEP_MODULE` Kconfig option to enable the Modem Sleep Notifications module. + * The :kconfig:option:`CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE` Kconfig option to enable the TAU Pre-warning module. + * The :c:enumerator:`LTE_LC_EVT_RAI_UPDATE` event that is enabled with the :kconfig:option:`CONFIG_LTE_RAI_REQ` Kconfig option. + This requires the :kconfig:option:`CONFIG_LTE_LC_RAI_MODULE` Kconfig option to be enabled. * Updated: @@ -884,6 +878,35 @@ Modem libraries Refer to the :ref:`migration guide ` for more details. * The :c:enum:`lte_lc_reduced_mobility_mode` type has been deprecated. Refer to the :ref:`migration guide ` for more details. + * The library was reorganized into modules that are enabled through their respective Kconfig options. + By default, the library now enables only the core features related to the network connectivity. + This reorganization reduces flash memory consumption for applications that only use the core features of the library related to network connectivity. + For more information on how to update your project, see the :ref:`migration guide `. + + * The :c:func:`lte_lc_conn_eval_params_get` function now requires the new :kconfig:option:`CONFIG_LTE_LC_CONN_EVAL_MODULE` Kconfig option to be enabled. + * The :c:enumerator:`LTE_LC_EVT_EDRX_UPDATE` event and the :c:func:`lte_lc_ptw_set`, :c:func:`lte_lc_edrx_param_set`, :c:func:`lte_lc_edrx_req`, and :c:func:`lte_lc_edrx_get` functions require the new :kconfig:option:`CONFIG_LTE_LC_EDRX_MODULE` Kconfig option to be enabled. + * The :c:enumerator:`LTE_LC_EVT_NEIGHBOR_CELL_MEAS` event and the :c:func:`lte_lc_neighbor_cell_measurement_cancel`, and :c:func:`lte_lc_neighbor_cell_measurement` functions require the new :kconfig:option:`CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE` Kconfig option to be enabled. + * The :c:func:`lte_lc_periodic_search_request`, :c:func:`lte_lc_periodic_search_clear`, :c:func:`lte_lc_periodic_search_get`, and :c:func:`lte_lc_periodic_search_set` functions require the new :kconfig:option:`CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE` Kconfig option to be enabled. + * The :c:enumerator:`LTE_LC_EVT_PSM_UPDATE` event and the :c:func:`lte_lc_psm_param_set`, :c:func:`lte_lc_psm_param_set_seconds`, :c:func:`lte_lc_psm_req`, :c:func:`lte_lc_psm_get`, and :c:func:`lte_lc_proprietary_psm_req` functions require the new :kconfig:option:`CONFIG_LTE_LC_PSM_MODULE` Kconfig option to be enabled. + * The :c:enumerator:`LTE_LC_EVT_MODEM_SLEEP_EXIT_PRE_WARNING`, :c:enumerator:`LTE_LC_EVT_MODEM_SLEEP_ENTER`, and :c:enumerator:`LTE_LC_EVT_MODEM_SLEEP_EXIT` events require the new :kconfig:option:`CONFIG_LTE_LC_MODEM_SLEEP_MODULE` Kconfig option to be enabled. + * The :c:enumerator:`LTE_LC_EVT_TAU_PRE_WARNING` event requires the new :kconfig:option:`CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE` Kconfig option to be enabled. + + * Deprecated the :c:macro:`LTE_LC_ON_CFUN` macro. + Use the :c:macro:`NRF_MODEM_LIB_ON_CFUN` macro instead. + + * Removed: + + * The :c:func:`lte_lc_init` function. + All instances of this function can be removed without any additional actions. + * The :c:func:`lte_lc_deinit` function. + Use the :c:func:`lte_lc_power_off` function instead. + * The :c:func:`lte_lc_init_and_connect` function. + Use the :c:func:`lte_lc_connect` function instead. + * The :c:func:`lte_lc_init_and_connect_async` function. + Use the :c:func:`lte_lc_connect_async` function instead. + * The ``CONFIG_LTE_NETWORK_USE_FALLBACK`` Kconfig option. + Use the :kconfig:option:`CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT` or :kconfig:option:`CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT_GPS` Kconfig option instead. + In addition, you can control the priority between LTE-M and NB-IoT using the :kconfig:option:`CONFIG_LTE_MODE_PREFERENCE` Kconfig option. * :ref:`lib_location` library: diff --git a/include/modem/lte_lc.h b/include/modem/lte_lc.h index abb42a32db97..cbbadb994a6a 100644 --- a/include/modem/lte_lc.h +++ b/include/modem/lte_lc.h @@ -212,23 +212,27 @@ enum lte_lc_evt_type { * The associated payload is the @c lte_lc_evt.nw_reg_status member of type * @ref lte_lc_nw_reg_status in the event. */ - LTE_LC_EVT_NW_REG_STATUS, + LTE_LC_EVT_NW_REG_STATUS = 0, +#if defined(CONFIG_LTE_LC_PSM_MODULE) /** * PSM parameters provided by the network. * * The associated payload is the @c lte_lc_evt.psm_cfg member of type * @ref lte_lc_psm_cfg in the event. */ - LTE_LC_EVT_PSM_UPDATE, + LTE_LC_EVT_PSM_UPDATE = 1, +#endif /* CONFIG_LTE_LC_PSM_MODULE */ +#if defined(CONFIG_LTE_LC_EDRX_MODULE) /** * eDRX parameters provided by the network. * * The associated payload is the @c lte_lc_evt.edrx_cfg member of type * @ref lte_lc_edrx_cfg in the event. */ - LTE_LC_EVT_EDRX_UPDATE, + LTE_LC_EVT_EDRX_UPDATE = 2, +#endif /* CONFIG_LTE_LC_EDRX_MODULE */ /** * RRC connection state. @@ -236,7 +240,7 @@ enum lte_lc_evt_type { * The associated payload is the @c lte_lc_evt.rrc_mode member of type * @ref lte_lc_rrc_mode in the event. */ - LTE_LC_EVT_RRC_UPDATE, + LTE_LC_EVT_RRC_UPDATE = 3, /** * Current cell. @@ -245,7 +249,7 @@ enum lte_lc_evt_type { * @ref lte_lc_cell in the event. Only the @c lte_lc_cell.tac and @c lte_lc_cell.id * members are populated in the event payload. The rest are expected to be zero. */ - LTE_LC_EVT_CELL_UPDATE, + LTE_LC_EVT_CELL_UPDATE = 4, /** * Current LTE mode. @@ -258,8 +262,9 @@ enum lte_lc_evt_type { * The associated payload is the @c lte_lc_evt.lte_mode member of type * @ref lte_lc_lte_mode in the event. */ - LTE_LC_EVT_LTE_MODE_UPDATE, + LTE_LC_EVT_LTE_MODE_UPDATE = 5, +#if defined(CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE) /** * Tracking Area Update pre-warning. * @@ -270,16 +275,20 @@ enum lte_lc_evt_type { * * The associated payload is the @c lte_lc_evt.time member of type @c uint64_t in the event. */ - LTE_LC_EVT_TAU_PRE_WARNING, + LTE_LC_EVT_TAU_PRE_WARNING = 6, +#endif /* CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE */ +#if defined(CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE) /** * Neighbor cell measurement results. * * The associated payload is the @c lte_lc_evt.cells_info member of type * @ref lte_lc_cells_info in the event. */ - LTE_LC_EVT_NEIGHBOR_CELL_MEAS, + LTE_LC_EVT_NEIGHBOR_CELL_MEAS = 7, +#endif /* CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE */ +#if defined(CONFIG_LTE_LC_MODEM_SLEEP_MODULE) /** * Modem sleep pre-warning. * @@ -290,7 +299,7 @@ enum lte_lc_evt_type { * @ref lte_lc_modem_sleep in the event. The @c lte_lc_modem_sleep.time parameter indicates * the time until modem exits sleep. */ - LTE_LC_EVT_MODEM_SLEEP_EXIT_PRE_WARNING, + LTE_LC_EVT_MODEM_SLEEP_EXIT_PRE_WARNING = 8, /** * Modem exited from sleep. @@ -298,7 +307,7 @@ enum lte_lc_evt_type { * The associated payload is the @c lte_lc_evt.modem_sleep member of type * @ref lte_lc_modem_sleep in the event. */ - LTE_LC_EVT_MODEM_SLEEP_EXIT, + LTE_LC_EVT_MODEM_SLEEP_EXIT = 9, /** * Modem entered sleep. @@ -307,7 +316,8 @@ enum lte_lc_evt_type { * @ref lte_lc_modem_sleep in the event. The @c lte_lc_modem_sleep.time parameter indicates * the duration of the sleep. */ - LTE_LC_EVT_MODEM_SLEEP_ENTER, + LTE_LC_EVT_MODEM_SLEEP_ENTER = 10, +#endif /* CONFIG_LTE_LC_MODEM_SLEEP_MODULE */ /** * Information about modem operation. @@ -315,8 +325,9 @@ enum lte_lc_evt_type { * The associated payload is the @c lte_lc_evt.modem_evt member of type * @ref lte_lc_modem_evt in the event. */ - LTE_LC_EVT_MODEM_EVENT, + LTE_LC_EVT_MODEM_EVENT = 11, +#if defined(CONFIG_LTE_LC_RAI_MODULE) /** * Information about RAI (Release Assistance Indication) configuration. * @@ -325,7 +336,8 @@ enum lte_lc_evt_type { * * @note This event is only supported by modem firmware versions >= 2.0.2. */ - LTE_LC_EVT_RAI_UPDATE, + LTE_LC_EVT_RAI_UPDATE = 12, +#endif /* CONFIG_LTE_LC_RAI_MODULE */ }; /** RRC connection state. */ @@ -1215,11 +1227,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_MODULE) /** Payload for event @ref LTE_LC_EVT_PSM_UPDATE. */ struct lte_lc_psm_cfg psm_cfg; +#endif /* CONFIG_LTE_LC_PSM_MODULE */ +#if defined(CONFIG_LTE_LC_EDRX_MODULE) /** Payload for event @ref LTE_LC_EVT_EDRX_UPDATE. */ struct lte_lc_edrx_cfg edrx_cfg; +#endif /* CONFIG_LTE_LC_EDRX_MODULE */ /** Payload for event @ref LTE_LC_EVT_CELL_UPDATE. */ struct lte_lc_cell cell; @@ -1227,11 +1243,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_MODEM_SLEEP_MODULE) /** * 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 /* CONFIG_LTE_LC_MODEM_SLEEP_MODULE */ /** Payload for event @ref LTE_LC_EVT_MODEM_EVENT. */ enum lte_lc_modem_evt modem_evt; @@ -1243,11 +1261,15 @@ struct lte_lc_evt { */ uint64_t time; +#if defined(CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE) /** Payload for event @ref LTE_LC_EVT_NEIGHBOR_CELL_MEAS. */ struct lte_lc_cells_info cells_info; +#endif /* CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE */ +#if defined(CONFIG_LTE_LC_RAI_MODULE) /** Payload for event @ref LTE_LC_EVT_RAI_UPDATE. */ struct lte_lc_rai_cfg rai_cfg; +#endif /* CONFIG_LTE_LC_RAI_MODULE */ }; }; @@ -1279,7 +1301,6 @@ int lte_lc_deregister_handler(lte_lc_evt_handler_t handler); /** * Connect to LTE network. * - * * @note After initialization, the system mode will be set to the default mode selected with Kconfig * and LTE preference set to automatic selection. * @@ -1341,6 +1362,8 @@ int lte_lc_normal(void); * For encoding of the variables, see nRF AT Commands Reference Guide, 3GPP 27.007 Ch. 7.38., and * 3GPP 24.008 Ch. 10.5.7.4a and Ch. 10.5.7.3. * + * @note Requires @kconfig{CONFIG_LTE_LC_PSM_MODULE} to be enabled. + * * @param[in] rptau * @parblock * Requested periodic TAU as a null-terminated 8 character long bit field string. @@ -1401,6 +1424,8 @@ int lte_lc_psm_param_set(const char *rptau, const char *rat); * For more information about the encodings, see the description of the * lte_lc_psm_param_set() function. * + * @note Requires @kconfig{CONFIG_LTE_LC_PSM_MODULE} to be enabled. + * * @param[in] rptau * @parblock * Requested periodic TAU in seconds as a non-negative integer. Range 0 - 35712000 s. @@ -1443,6 +1468,8 @@ int lte_lc_psm_param_set_seconds(int rptau, int rat); * conflicts may arise with the value set by @kconfig{CONFIG_LTE_PSM_REQ} if it is called * during modem initialization. * + * @note Requires @kconfig{CONFIG_LTE_LC_PSM_MODULE} to be enabled. + * * @param[in] enable @c true to enable PSM, @c false to disable PSM. * * @retval 0 if successful. @@ -1453,6 +1480,8 @@ int lte_lc_psm_req(bool enable); /** * Get the current PSM (Power Saving Mode) configuration. * + * @note Requires @kconfig{CONFIG_LTE_LC_PSM_MODULE} to be enabled. + * * @param[out] tau Periodic TAU interval in seconds. A non-negative integer. * @param[out] active_time Active time in seconds. A non-negative integer, * or @c -1 if PSM is deactivated. @@ -1486,6 +1515,8 @@ int lte_lc_psm_get(int *tau, int *active_time); * * @note This feature is only supported by modem firmware versions >= v2.0.0. * + * @note Requires @kconfig{CONFIG_LTE_LC_PSM_MODULE} to be enabled. + * * @retval 0 if successful. * @retval -EFAULT if AT command failed. */ @@ -1502,6 +1533,8 @@ int lte_lc_proprietary_psm_req(bool enable); * * For reference to which values can be set, see subclause 10.5.5.32 of 3GPP TS 24.008. * + * @note Requires @kconfig{CONFIG_LTE_LC_EDRX_MODULE} to be enabled. + * * @param[in] mode LTE mode to which the PTW value applies. * @param[in] ptw Paging Time Window value as a null-terminated string. * Set to @c NULL to use modem's default value. @@ -1519,6 +1552,8 @@ int lte_lc_ptw_set(enum lte_lc_lte_mode mode, const char *ptw); * * For reference see 3GPP 27.007 Ch. 7.40. * + * @note Requires @kconfig{CONFIG_LTE_LC_EDRX_MODULE} to be enabled. + * * @param[in] mode LTE mode to which the eDRX value applies. * @param[in] edrx eDRX value as a null-terminated string. * Set to @c NULL to use modem's default value. @@ -1544,6 +1579,8 @@ int lte_lc_edrx_param_set(enum lte_lc_lte_mode mode, const char *edrx); * noted that conflicts may arise with the value set by @kconfig{CONFIG_LTE_EDRX_REQ} if it is * called during modem initialization. * + * @note Requires @kconfig{CONFIG_LTE_LC_EDRX_MODULE} to be enabled. + * * @param[in] enable @c true to enable eDRX, @c false to disable eDRX. * * @retval 0 if successful. @@ -1556,6 +1593,8 @@ int lte_lc_edrx_req(bool enable); * * @param[out] edrx_cfg eDRX configuration. * + * @note Requires @kconfig{CONFIG_LTE_LC_EDRX_MODULE} to be enabled. + * * @retval 0 if successful. * @retval -EINVAL if input argument was invalid. * @retval -EFAULT if AT command failed. @@ -1645,6 +1684,8 @@ int lte_lc_lte_mode_get(enum lte_lc_lte_mode *mode); * * @note This feature is only supported by modem firmware versions >= 1.3.0. * + * @note Requires @kconfig{CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE} to be enabled. + * * @param[in] params Search type parameters or @c NULL to initiate a measurement with the default * parameters. See @ref lte_lc_ncellmeas_params for more information. * @@ -1658,6 +1699,8 @@ int lte_lc_neighbor_cell_measurement(struct lte_lc_ncellmeas_params *params); /** * Cancel an ongoing neighbor cell measurement. * + * @note Requires @kconfig{CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE} to be enabled. + * * @retval 0 if neighbor cell measurement was cancelled. * @retval -EFAULT if AT command failed. */ @@ -1669,6 +1712,8 @@ int lte_lc_neighbor_cell_measurement_cancel(void); * Connection evaluation parameters can be used to determine the energy efficiency of data * transmission before the actual data transmission. * + * @note Requires @kconfig{CONFIG_LTE_LC_CONN_EVAL_MODULE} to be enabled. + * * @param[out] params Connection evaluation parameters. * * @return Zero on success, negative errno code if the API call fails, and a positive error @@ -1719,6 +1764,8 @@ int lte_lc_modem_events_disable(void); * "nRF91 AT Commands - Command Reference Guide" for more information and in-depth explanations of * periodic search configuration. * + * @note Requires @kconfig{CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE} to be enabled. + * * @param[in] cfg Periodic search configuration. * * @retval 0 if the configuration was successfully sent to the modem. @@ -1731,6 +1778,8 @@ int lte_lc_periodic_search_set(const struct lte_lc_periodic_search_cfg *const cf /** * Get the configured periodic search parameters. * + * @note Requires @kconfig{CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE} to be enabled. + * * @param[out] cfg Periodic search configuration. * * @retval 0 if a configuration was found and populated to the provided pointer. @@ -1745,6 +1794,8 @@ int lte_lc_periodic_search_get(struct lte_lc_periodic_search_cfg *const cfg); /** * Clear the configured periodic search parameters. * + * @note Requires @kconfig{CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE} to be enabled. + * * @retval 0 if the configuration was cleared. * @retval -EFAULT if an AT command could not be sent to the modem. * @retval -EBADMSG if the modem responded with an error to an AT command. @@ -1757,6 +1808,8 @@ int lte_lc_periodic_search_clear(void); * This can be used for example when modem is in sleep state between periodic searches. The search * is performed only when the modem is in sleep state between periodic searches. * + * @note Requires @kconfig{CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE} to be enabled. + * * @retval 0 if the search request was successfully delivered to the modem. * @retval -EFAULT if an AT command could not be sent to the modem. */ diff --git a/lib/location/Kconfig b/lib/location/Kconfig index a701a9ac56cf..887920cb2606 100644 --- a/lib/location/Kconfig +++ b/lib/location/Kconfig @@ -13,12 +13,16 @@ config LOCATION_METHOD_GNSS bool "Allow GNSS to be used for obtaining the location" depends on NRF_MODEM_LIB depends on LTE_LINK_CONTROL + imply LTE_LC_NEIGHBOR_CELL_MEAS_MODULE + imply LTE_LC_PSM_MODULE + imply LTE_LC_MODEM_SLEEP_MODULE default y config LOCATION_METHOD_CELLULAR bool "Allow cellular positioning to be used for obtaining the location" depends on NRF_MODEM_LIB depends on LTE_LINK_CONTROL + imply LTE_LC_NEIGHBOR_CELL_MEAS_MODULE default y config LOCATION_METHOD_WIFI diff --git a/lib/lte_link_control/CMakeLists.txt b/lib/lte_link_control/CMakeLists.txt index 38b9dd7a8da2..230d1e84f987 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 a8d32564221c..40752aff5b1c 100644 --- a/lib/lte_link_control/Kconfig +++ b/lib/lte_link_control/Kconfig @@ -4,6 +4,9 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +# Note: the naming of the Kconfig options is inconsistent in this file as a result of gradual +# development. It will be fixed in a future version. + menuconfig LTE_LINK_CONTROL bool "LTE link control library" select AT_PARSER @@ -11,6 +14,34 @@ menuconfig LTE_LINK_CONTROL if LTE_LINK_CONTROL +# Modules: these Kconfig options enable specific features of the library. + +config LTE_LC_CONN_EVAL_MODULE + bool "Connection Parameters Evaluation module" + +config LTE_LC_EDRX_MODULE + bool "Extended Discountinuous Reception (eDRX) module" + +config LTE_LC_NEIGHBOR_CELL_MEAS_MODULE + bool "Neighboring Cell Measurements module" + +config LTE_LC_PERIODIC_SEARCH_MODULE + bool "Periodic Search Configuration module" + +config LTE_LC_PSM_MODULE + bool "Power Saving Mode (PSM) module" + +config LTE_LC_RAI_MODULE + bool "Release Assistance Indication (RAI) module" + +config LTE_LC_MODEM_SLEEP_MODULE + bool "Modem Sleep module" + +config LTE_LC_TAU_PRE_WARNING_MODULE + bool "Tracking Area Update (TAU) module" + +# End of modules Kconfig options + config LTE_SHELL bool "Enable LTE shell commands" default y @@ -59,6 +90,8 @@ config LTE_UNLOCK_PLMN help Disable PLMN lock for network selection. +if LTE_LC_PSM_MODULE + config LTE_PSM_REQ bool "Enable PSM request" help @@ -124,6 +157,10 @@ config LTE_PROPRIETARY_PSM_REQ must be enabled using CONFIG_LTE_PSM_REQ or lte_lc_psm_req(). Refer to the AT command guide for guidance and limitations of this feature. +endif # LTE_LC_PSM_MODULE + +if LTE_LC_EDRX_MODULE + config LTE_EDRX_REQ bool "Enable eDRX request" help @@ -178,6 +215,8 @@ config LTE_PTW_VALUE_NBIOT value that is configured, and it's usually best to let the modem use default PTW values. +endif # LTE_LC_EDRX_MODULE + choice LTE_NETWORK_MODE prompt "Select network mode" default LTE_NETWORK_MODE_LTE_M_NBIOT_GPS @@ -266,6 +305,8 @@ config LTE_MODE_PREFERENCE_VALUE default 3 if LTE_MODE_PREFERENCE_LTE_M_PLMN_PRIO default 4 if LTE_MODE_PREFERENCE_NBIOT_PLMN_PRIO +if LTE_LC_RAI_MODULE + config LTE_RAI_REQ bool "Release Assistance Indication (RAI) request" help @@ -273,12 +314,16 @@ config LTE_RAI_REQ the RAI socket option (SO_RAI) to inform the modem when no more data is expected and the RRC connection can be released. +endif # LTE_LC_RAI_MODULE + config LTE_NETWORK_TIMEOUT int "Time period to attempt establishing connection" default 600 help Time period in seconds to attempt establishing an LTE link, before timing out. +if LTE_LC_TAU_PRE_WARNING_MODULE + config LTE_LC_TAU_PRE_WARNING_NOTIFICATIONS bool "Get notifications prior to Tracking Area Updates" help @@ -301,6 +346,10 @@ config LTE_LC_TAU_PRE_WARNING_THRESHOLD_MS Minimum value of the given T3412 timer in milliseconds that will trigger TAU pre-warnings. +endif # LTE_LC_TAU_PRE_WARNING_MODULE + +if LTE_LC_NEIGHBOR_CELL_MEAS_MODULE + config LTE_NEIGHBOR_CELLS_MAX int "Max number of neighbor cells" range 1 17 @@ -314,6 +363,10 @@ config LTE_NEIGHBOR_CELLS_MAX cells, so there's a trade-off between heap requirements and the risk of not being able to parse all neighbor cell information. +endif # LTE_LC_NEIGHBOR_CELL_MEAS_MODULE + +if LTE_LC_MODEM_SLEEP_MODULE + config LTE_LC_MODEM_SLEEP_NOTIFICATIONS bool "Modem sleep notifications" help @@ -336,6 +389,8 @@ config LTE_LC_MODEM_SLEEP_NOTIFICATIONS_THRESHOLD_MS Minimum value of the duration of the scheduled modem sleep in milliseconds that triggers a notification. +endif # LTE_LC_MODEM_SLEEP_MODULE + config LTE_LC_TRACE bool "LTE link control tracing" help diff --git a/lib/lte_link_control/common/CMakeLists.txt b/lib/lte_link_control/common/CMakeLists.txt new file mode 100644 index 000000000000..0333506c94a3 --- /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 000000000000..35b6957782ef --- /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 000000000000..a29b90e935aa --- /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 000000000000..0b0d954a5585 --- /dev/null +++ b/lib/lte_link_control/common/work_q.c @@ -0,0 +1,28 @@ +/* + * 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); + +static struct k_work_q work_q; + +void 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); +} + +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 000000000000..f47c081f7ae1 --- /dev/null +++ b/lib/lte_link_control/include/common/event_handler_list.h @@ -0,0 +1,32 @@ +/* + * 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 + +/* Add the handler in the event handler list if not already present. */ +int event_handler_list_handler_append(lte_lc_evt_handler_t handler); + +/* Remove the handler from the event handler list if present. */ +int event_handler_list_handler_remove(lte_lc_evt_handler_t handler); + +/* Dispatch events for the registered event handlers. */ +void event_handler_list_dispatch(const struct lte_lc_evt *const evt); + +/* Test if the handler list is empty. */ +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 000000000000..2ca75cb54e9d --- /dev/null +++ b/lib/lte_link_control/include/common/helpers.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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 + +/* Converts integer as string to integer type. */ +int string_to_int(const char *str_buf, int base, int *output); + +/* Converts integer on string format to integer type. */ +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. */ +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 000000000000..8280d236943e --- /dev/null +++ b/lib/lte_link_control/include/common/work_q.h @@ -0,0 +1,26 @@ +/* + * 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 + +/* Starts the work queue. */ +void work_q_start(void); + +/* Returns a pointer to the work queue. */ +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 000000000000..0134882ebae2 --- /dev/null +++ b/lib/lte_link_control/include/modules/cereg.h @@ -0,0 +1,32 @@ +/* + * 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 + +/* Get the current network registration status. */ +int cereg_status_get(enum lte_lc_nw_reg_status *status); + +/* Get the currently active LTE mode. */ +int cereg_mode_get(enum lte_lc_lte_mode *mode); + +/* Connect to LTE network. */ +int cereg_lte_connect(bool blocking); + +/* Enable notifications. */ +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 000000000000..ab5597348bfb --- /dev/null +++ b/lib/lte_link_control/include/modules/cfun.h @@ -0,0 +1,26 @@ +/* + * 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 + +/* Get the modem's functional mode. */ +int cfun_mode_get(enum lte_lc_func_mode *mode); + +/* Set the modem's functional 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 000000000000..8172e57c56d2 --- /dev/null +++ b/lib/lte_link_control/include/modules/coneval.h @@ -0,0 +1,23 @@ +/* + * 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 + +/* Get connection evaluation parameters. */ +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 000000000000..a0f7c0f3e755 --- /dev/null +++ b/lib/lte_link_control/include/modules/cscon.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef CSCON_H__ +#define CSCON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Enable notifications. */ +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 000000000000..cfd57aab70b8 --- /dev/null +++ b/lib/lte_link_control/include/modules/edrx.h @@ -0,0 +1,32 @@ +/* + * 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 + +/* Get the eDRX parameters currently provided by the network. */ +int edrx_cfg_get(struct lte_lc_edrx_cfg *edrx_cfg); + +/* Set the Paging Time Window (PTW) value to be used with eDRX. */ +int edrx_ptw_set(enum lte_lc_lte_mode mode, const char *ptw); + +/* Set the requested eDRX value. */ +int edrx_param_set(enum lte_lc_lte_mode mode, const char *edrx); + +/* Request modem to enable or disable use of 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 000000000000..20cae155e068 --- /dev/null +++ b/lib/lte_link_control/include/modules/mdmev.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef MDMEV_H__ +#define MDMEV_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Enable modem domain events. */ +int mdmev_enable(void); + +/* Disable modem domain events. */ +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 000000000000..e5dcab1af4b3 --- /dev/null +++ b/lib/lte_link_control/include/modules/ncellmeas.h @@ -0,0 +1,26 @@ +/* + * 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 + +/* Initiate a neighbor cell measurement. */ +int ncellmeas_start(struct lte_lc_ncellmeas_params *params); + +/* Cancel an ongoing neighbor cell measurement. */ +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 000000000000..166b934525bd --- /dev/null +++ b/lib/lte_link_control/include/modules/periodicsearchconf.h @@ -0,0 +1,32 @@ +/* + * 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 + +/* Configure periodic searches. */ +int periodicsearchconf_set(const struct lte_lc_periodic_search_cfg *const cfg); + +/* Get the configured periodic search parameters. */ +int periodicsearchconf_get(struct lte_lc_periodic_search_cfg *const cfg); + +/* Clear the configured periodic search parameters. */ +int periodicsearchconf_clear(void); + +/* Request an extra search. */ +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 000000000000..0ba43f70514d --- /dev/null +++ b/lib/lte_link_control/include/modules/psm.h @@ -0,0 +1,45 @@ +/* + * 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 + +/* Set modem PSM parameters. */ +int psm_param_set(const char *rptau, const char *rat); + +/* Set modem PSM parameters. */ +int psm_param_set_seconds(int rptau, int rat); + +/* Request modem to enable or disable Power Saving Mode (PSM). */ +int psm_req(bool enable); + +/* Request modem to enable or disable proprietary Power Saving Mode (PSM). */ +int psm_proprietary_req(bool enable); + +/* Get the current PSM (Power Saving Mode) configuration. */ +int psm_get(int *tau, int *active_time); + +/* Send PSM configuration event update. */ +void psm_evt_update_send(struct lte_lc_psm_cfg *psm_cfg); + +/* Parse PSM parameters. */ +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); + +/* Get PSM work task. */ +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 000000000000..d61cd45a8fb9 --- /dev/null +++ b/lib/lte_link_control/include/modules/rai.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef RAI_H__ +#define RAI_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Configure RAI. */ +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 000000000000..8bddd80bce92 --- /dev/null +++ b/lib/lte_link_control/include/modules/redmob.h @@ -0,0 +1,34 @@ +/* + * 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 + +/** + * Read the current reduced mobility mode. + * + * @deprecated since v2.8.0. + */ +int redmob_get(enum lte_lc_reduced_mobility_mode *mode); + +/** + * Set reduced mobility mode. + * + * @deprecated since v2.8.0. + */ +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 000000000000..14fe368edfd3 --- /dev/null +++ b/lib/lte_link_control/include/modules/xfactoryreset.h @@ -0,0 +1,27 @@ +/* + * 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 + +/** + * Reset modem to factory settings. + * + * @deprecated since v2.8.0. + */ +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 000000000000..b0bf056a1fd2 --- /dev/null +++ b/lib/lte_link_control/include/modules/xmodemsleep.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef XMODEMSLEEP_H__ +#define XMODEMSLEEP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Enable notifications. */ +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 000000000000..2271a9ea2bab --- /dev/null +++ b/lib/lte_link_control/include/modules/xsystemmode.h @@ -0,0 +1,28 @@ +/* + * 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 + +/* Set the modem's system mode and LTE preference. */ +int xsystemmode_mode_set(enum lte_lc_system_mode mode, + enum lte_lc_system_mode_preference preference); + +/* Get the modem's system mode and LTE 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 000000000000..7311f0bf88f9 --- /dev/null +++ b/lib/lte_link_control/include/modules/xt3412.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef XT3412_H__ +#define XT3412_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Enable notifications. */ +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 34030a33c36d..a841d364534a 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/cereg.h" +#include "modules/cfun.h" +#include "modules/coneval.h" +#include "modules/cscon.h" +#include "modules/edrx.h" +#include "modules/mdmev.h" +#include "modules/ncellmeas.h" +#include "modules/periodicsearchconf.h" +#include "modules/psm.h" +#include "modules/redmob.h" +#include "modules/xfactoryreset.h" +#include "modules/xmodemsleep.h" +#include "modules/xsystemmode.h" +#include "modules/xt3412.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,1088 +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); + work_q_start(); return 0; } diff --git a/lib/lte_link_control/lte_lc_helpers.c b/lib/lte_link_control/lte_lc_helpers.c deleted file mode 100644 index 62bd688a48a1..000000000000 --- a/lib/lte_link_control/lte_lc_helpers.c +++ /dev/null @@ -1,1622 +0,0 @@ -/* - * Copyright (c) 2021 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "lte_lc_helpers.h" - -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_find_node(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 Test if the handler list is empty. */ -bool event_handler_list_is_empty(void) -{ - return sys_slist_is_empty(&handler_list); -} - -/**@brief Add the handler in the event handler list if not already present. */ -int event_handler_list_append_handler(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_find_node(&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_remove_handler(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_find_node(&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); -} - - -/* Converts integer on string format to integer type. - * Returns zero on success, otherwise negative error on failure. - */ -static 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; -} - -/* Converts PLMN string to integer type MCC and MNC. - * Returns zero on success, otherwise negative error on failure. - */ -static 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; -} - -/* 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; -} - -/* 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; -} - -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; -} - -/* Parses eDRX parameters from a +CEDRXS notification or a +CEDRXRDP response. */ -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; -} - -/* 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 - -/* 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}; - -/* 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}; - -/* 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') - -static int encode_psm_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; -} - -int encode_psm(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 = encode_psm_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 |= encode_psm_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 parse_psm(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; -} - -/**@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. - */ -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; -} - -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 */ - - /* 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 = parse_psm(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; - -clean_exit: - return err; -} - -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; -} - -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; -} - -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; -} - -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; - int16_t tmp_short; - 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; - - /* , that is, MCC and MNC */ - curr_index++; - err = plmn_param_string_to_mcc_mnc( - &parser, curr_index, &parsed_cell.mcc, &parsed_cell.mnc); - if (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; -} - -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; -} - -int parse_mdmev(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; -} - -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; -} - -char *periodic_search_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 parse_periodic_search_pattern(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; -} - -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/lte_lc_helpers.h b/lib/lte_link_control/lte_lc_helpers.h deleted file mode 100644 index d7bee5400a2f..000000000000 --- a/lib/lte_link_control/lte_lc_helpers.h +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (c) 2021 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#include -#include -#include -#include -#include -#include - -#define AT_CFUN_READ "AT+CFUN?" -#define AT_CEREG_5 "AT+CEREG=5" -#define AT_CEREG_READ "AT+CEREG?" -#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 -#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 - -/* CEDRXS command parameters */ -#define AT_CEDRXS_MODE_INDEX -#define AT_CEDRXS_ACTT_WB 4 -#define AT_CEDRXS_ACTT_NB 5 - -/* 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 - -/* CSCON command parameters */ -#define AT_CSCON_RRC_MODE_INDEX 1 -#define AT_CSCON_READ_RRC_MODE_INDEX 2 - -/* XT3412 command parameters */ -#define AT_XT3412_SUB "AT%%XT3412=1,%d,%d" -#define AT_XT3412_TIME_INDEX 1 -#define T3412_MAX 35712000000 - -/* 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 - -/* 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 - -/* 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 - -/* 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" - -/* 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 - -/* @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. - */ -int parse_rrc_mode(const char *at_response, - enum lte_lc_rrc_mode *mode, - size_t mode_index); - -/* @brief Parses an AT command response and returns the current eDRX configuration. - * - * @note It is assumed that the network only reports valid eDRX values when - * in each mode (LTE-M and NB1). There is no sanity check of these values. - * - * @param[in] at_response Pointer to buffer with AT response. - * @param[in] cfg Pointer to where the eDRX configuration is stored. - * @param[out] edrx_str eDRX value as a string. Must be 5 characters long buffer. - * @param[out] ptw_str PTW as a string. Must be 5 characters long buffer. - * - * @return Zero on success or (negative) error code otherwise. - */ -int parse_edrx(const char *at_response, struct lte_lc_edrx_cfg *cfg, char *edrx_str, char *ptw_str); - -/* @brief Parses PSM configuration from periodic TAU timer and active time strings. - * - * @param active_time_str Pointer to active time string. - * @param tau_ext_str Pointer to TAU (T3412 extended) string. - * @param tau_legacy_str Pointer to TAU (T3412) string. - * @param psm_cfg Pointer to PSM configuraion struct where the parsed values - * are stored. - * - * @retval 0 if PSM configuration was successfully parsed. - * @retval -EINVAL if parsing failed. - */ -int parse_psm(const char *active_time_str, const char *tau_ext_str, - const char *tau_legacy_str, struct lte_lc_psm_cfg *psm_cfg); - -/* @brief Encode Periodic TAU timer and active time strings. - * - * @param[out] tau_ext_str TAU (T3412 extended) string. Must be at least 9 bytes. - * @param[out] active_time_str Active time string buffer. Must be at least 9 bytes. - * @param rptau[in] Requested Periodic TAU value to be encoded. - * @param rat[in] Requested active time value to be encoded. - * - * @retval 0 if PSM configuration was successfully parsed. - * @retval -EINVAL if parsing failed. - */ -int encode_psm(char *tau_ext_str, char *active_time_str, int rptau, int rat); - -/* @brief Parses a +CEREG notification and returns network registration status, - * cell information, LTE mode and PSM configuration. The function always - * initializes the return values. The destination pointers must be non-NULL. - * - * @param[in] at_response AT notification. - * @param[out] reg_status Registration status. - * @param[out] cell Cell information. - * @param[out] lte_mode LTE mode. - * @param[out] psm_cfg PSM configuration. - * - * @return Zero on success or (negative) error code otherwise. - */ -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); - -/* @brief Parses an XT3412 response and extracts the time until next TAU. - * - * @param at_response Pointer to buffer with AT response. - * @param time Pointer to integer that the time until next TAU will be written to. - * - * @return Zero on success or (negative) error code otherwise. - */ -int parse_xt3412(const char *at_response, uint64_t *time); - -/* @brief Get the number of neighboring cells reported in an NCELLMEAS response. - * - * @param at_response Pointer to buffer with AT response to parse. - * - * @return The number of neighbor cells found in the response. - */ -uint32_t neighborcell_count_get(const char *at_response); - -/* @brief Parses an NCELLMEAS notification and stores neighboring cell - * information in a struct. - * - * 18446744073709551614 is the maximum value for timing_advance_meas_time and - * measurement_time in @ref lte_lc_cells_info. - * This value could be represented with uint64_t but cannot be stored by at_parser, - * which internally uses int64_t value for all integers. - * Hence, the maximum value for these fields is represented by 63 bits and is - * 9223372036854775807, which still represents millions of years. - * - * @param at_response AT response. - * @param cells Neighbor cell structure. - * The current cell information is valid if the current cell ID is - * not set to LTE_LC_CELL_EUTRAN_ID_INVALID. - * - * @return Zero on success or (negative) error code otherwise. - * @retval 1 Measurement failure. - * @retval -E2BIG The static buffers set by CONFIG_LTE_NEIGHBOR_CELLS_MAX - * are too small for the modem response. The associated data is still valid, - * but not complete. - */ -int parse_ncellmeas(const char *at_response, struct lte_lc_cells_info *cells); - -/* @brief Parses a NCELLMEAS notification for GCI search types, and stores neighboring cell - * and measured GCI cell information in a struct. - * - * 18446744073709551614 is the maximum value for timing_advance_meas_time and - * measurement_time in @ref lte_lc_cells_info. - * This value could be represented with uint64_t but cannot be stored by at_parser, - * which internally uses int64_t value for all integers. - * Hence, the maximum value for these fields is represented by 63 bits and is - * 9223372036854775807, which still represents millions of years. - * - * @param params Neighbor cell measurement parameters. - * @param at_response AT response. - * @param cells Neighbor cell structure. - * The current cell information is valid if the current cell ID is - * not set to LTE_LC_CELL_EUTRAN_ID_INVALID. - * - * @return Zero on success or (negative) error code otherwise. - * @retval 1 Measurement failure. - * @retval -E2BIG The static buffers set by CONFIG_LTE_NEIGHBOR_CELLS_MAX - * are too small for the modem response. The associated data is still valid, - * but not complete. - */ -int parse_ncellmeas_gci(struct lte_lc_ncellmeas_params *params, - const char *at_response, struct lte_lc_cells_info *cells); - -/* @brief Parses an XMODEMSLEEP response and extracts the sleep type and time. - * - * @note If the time parameter -1 after this API call, time shall be considered infinite. - * - * @param at_response Pointer to buffer with AT response. - * @param modem_sleep Pointer to a structure holding modem sleep data. - * - * @return Zero on success or (negative) error code otherwise. - */ -int parse_xmodemsleep(const char *at_response, struct lte_lc_modem_sleep *modem_sleep); - -/* @brief Parses a CONEVAL response and populates a struct with parameters from the response. - * - * @param at_response Pointer to buffer with AT response. - * @param params Pointer to a structure that will be populated with CONEVAL parameters. - * - * @return Zero on success, negative errno code if the API call fails, and a positive error - * code if the API call succeeds but connection evalution fails due to modem/network related - * reasons. - * - * @retval 0 Evaluation succeeded. - * @retval 1 Evaluation failed, no cell available. - * @retval 2 Evaluation failed, UICC not available. - * @retval 3 Evaluation failed, only barred cells available. - * @retval 4 Evaluation failed, radio busy (e.g GNSS activity) - * @retval 5 Evaluation failed, aborted due to higher priority operation. - * @retval 6 Evaluation failed, UE not registered to network. - * @retval 7 Evaluation failed, Unspecified. - */ -int parse_coneval(const char *at_response, struct lte_lc_conn_eval_params *params); - -/* @brief Parses an MDMEV response and populates an enum with the corresponding - * modem event type. - * - * @param at_response Pointer to buffer with AT response. - * @param modem_evt Pointer to enum to hold modem event. - * - * @return Zero on success, negative errno code on failure. - * - * @retval 0 Parsing succeeded. - * @retval -EIO If the AT response is not a valid MDMEV response. - * @retval -ENODATA If no modem event type was found in the AT response. - */ -int parse_mdmev(const char *at_response, enum lte_lc_modem_evt *modem_evt); - -/* @brief Parse a RAI response and extract RAI configuration. - * - * @param at_response AT response. - * @param rai_cfg RAI configuration. - * - * @return Zero on success, negative errno code on failure. - */ -int parse_rai(const char *at_response, struct lte_lc_rai_cfg *rai_cfg); - -/* @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_append_handler(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_remove_handler(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); - -/* @brief Convert string to integer with a chosen base. - * - * @param str_buf Pointer to null-terminated string. - * @param base The base to use when converting the string. - * @param output Pointer to an integer where the result is stored. - * - * @retval 0 if conversion was successful. - * @retval -ENODATA if conversion failed. - */ -int string_to_int(const char *str_buf, int base, int *output); - -/* @brief Get periodic search pattern string to be used in AT%PERIODICSEARCHCONF from - * a pattern struct. - * - * @param buf Buffer to store the string. - * @param buf_size Size of the provided buffer. - * @param pattern Pointer to pattern struct. - * - * @return Pointer to the buffer where the pattern string is stored. - */ -char *periodic_search_pattern_get(char *const buf, size_t buf_size, - const struct lte_lc_periodic_search_pattern *const pattern); - -/* @brief Parse a periodic search pattern from an AT%PERIODICSEARCHCONF response - * and populate a pattern struct with the result. - * The pattern string is expected to be without quotation marks and null-terminated. - * - * @param pattern_str Pointer to pattern string. - * @param pattern Pointer to storage for the parsed pattern. - * - * @retval 0 if parsing was successful. - * @retval -EBADMSG if pattern could not be parsed. - */ -int parse_periodic_search_pattern(const char *const pattern_str, - struct lte_lc_periodic_search_pattern *pattern); - -/* @brief Set RAI based on @kconfig{CONFIG_LTE_RAI_REQ}. - * - * If enabling of RAI is requested, AT%RAI=2 is used to order unsolicited RAI notifications. - * If setting that fails, AT%RAI=1 is used. - * - * @retval 0 Setting RAI succeeded. - * @retval -EFAULT Setting RAI failed. - */ -int rai_set(void); diff --git a/lib/lte_link_control/lte_lc_modem_hooks.c b/lib/lte_link_control/lte_lc_modem_hooks.c index 2c52de004d97..8e59e60c7b97 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_MODULE) 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_MODULE) 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_MODULE) /* 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 000000000000..fb8871c603cd --- /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(redmob.c) +zephyr_library_sources(xfactoryreset.c) +zephyr_library_sources(xsystemmode.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_CONN_EVAL_MODULE coneval.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_EDRX_MODULE edrx.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE ncellmeas.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_PSM_MODULE psm.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_RAI_MODULE rai.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE periodicsearchconf.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_MODEM_SLEEP_MODULE xmodemsleep.c) +zephyr_library_sources_ifdef(CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE xt3412.c) diff --git a/lib/lte_link_control/modules/Kconfig b/lib/lte_link_control/modules/Kconfig new file mode 100644 index 000000000000..decacc8d0851 --- /dev/null +++ b/lib/lte_link_control/modules/Kconfig @@ -0,0 +1,29 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config LTE_LC_CONN_EVAL_MODULE + bool "Connection Parameters Evaluation module" + +config LTE_LC_EDRX_MODULE + bool "Extended Discountinuous Reception (eDRX) module" + +config LTE_LC_NEIGHBOR_CELL_MEAS_MODULE + bool "Neighboring Cell Measurements module" + +config LTE_LC_PERIODIC_SEARCH_MODULE + bool "Periodic Search Configuration module" + +config LTE_LC_PSM_MODULE + bool "Power Saving Mode (PSM) module" + +config LTE_LC_RAI_MODULE + bool "Release Assistance Indication (RAI) module" + +config LTE_LC_MODEM_SLEEP_MODULE + bool "Modem Sleep module" + +config LTE_LC_TAU_PRE_WARNING_MODULE + bool "Tracking Area Update (TAU) module" diff --git a/lib/lte_link_control/modules/cereg.c b/lib/lte_link_control/modules/cereg.c new file mode 100644 index 000000000000..741d6aa2dce9 --- /dev/null +++ b/lib/lte_link_control/modules/cereg.c @@ -0,0 +1,495 @@ +/* + * 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; +} + +#if defined(CONFIG_UNITY) +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) +#else +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) +#endif /* CONFIG_UNITY */ +{ + 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_MODULE) + /* 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_MODULE) + 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 000000000000..1e77cebab104 --- /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_MODEM_SLEEP_MODULE) + err = xmodemsleep_notifications_enable(); + if (err) { + return err; + } +#endif + +#if defined(CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE) + 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 000000000000..f4ebf5a7a8b3 --- /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 000000000000..b029b6228bd9 --- /dev/null +++ b/lib/lte_link_control/modules/cscon.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/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. + */ +#if defined(CONFIG_UNITY) +int parse_rrc_mode(const char *at_response, enum lte_lc_rrc_mode *mode, size_t mode_index) +#else +static int parse_rrc_mode(const char *at_response, enum lte_lc_rrc_mode *mode, size_t mode_index) +#endif /* CONFIG_UNITY */ +{ + 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 000000000000..6a85aa50c5b9 --- /dev/null +++ b/lib/lte_link_control/modules/edrx.c @@ -0,0 +1,471 @@ +/* + * 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 + +/* Length for eDRX and PTW values */ +#define LTE_LC_EDRX_VALUE_LEN 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; +/* 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]; + +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 /* CONFIG_UNITY */ +{ + 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; +} + +#if defined(CONFIG_UNITY) +int parse_edrx(const char *at_response, struct lte_lc_edrx_cfg *cfg, char *edrx_str, + char *ptw_str) +#else +/* 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) +#endif /* CONFIG_UNITY */ +{ + 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 000000000000..de6345444ebd --- /dev/null +++ b/lib/lte_link_control/modules/mdmev.c @@ -0,0 +1,116 @@ +/* + * 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); + +#if defined(CONFIG_UNITY) +int mdmev_parse(const char *at_response, enum lte_lc_modem_evt *modem_evt) +#else +static int mdmev_parse(const char *at_response, enum lte_lc_modem_evt *modem_evt) +#endif /* CONFIG_UNITY */ +{ + 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 000000000000..f6c8bb19dbf0 --- /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 000000000000..9402ae8d1812 --- /dev/null +++ b/lib/lte_link_control/modules/periodicsearchconf.c @@ -0,0 +1,304 @@ +/* + * 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); + +#if defined(CONFIG_UNITY) +int periodicsearchconf_parse(const char *const pattern_str, + struct lte_lc_periodic_search_pattern *pattern) +#else +static int periodicsearchconf_parse(const char *const pattern_str, + struct lte_lc_periodic_search_pattern *pattern) +#endif /* CONFIG_UNITY */ +{ + 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; +} + +#if defined(CONFIG_UNITY) +char * +periodicsearchconf_pattern_get(char *const buf, size_t buf_size, + const struct lte_lc_periodic_search_pattern *const pattern) +#else +static char * +periodicsearchconf_pattern_get(char *const buf, size_t buf_size, + const struct lte_lc_periodic_search_pattern *const pattern) +#endif /* CONFIG_UNITY */ +{ + 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 000000000000..f5ade395ded6 --- /dev/null +++ b/lib/lte_link_control/modules/psm.c @@ -0,0 +1,445 @@ +/* + * 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; +} + +#if defined(CONFIG_UNITY) +int psm_encode(char *tau_ext_str, char *active_time_str, int rptau, int rat) +#else +static int psm_encode(char *tau_ext_str, char *active_time_str, int rptau, int rat) +#endif /* CONFIG_UNITY */ +{ + 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 000000000000..5dfec3e57e86 --- /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 000000000000..187d81b215d6 --- /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 000000000000..62e676ec0fcd --- /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 000000000000..e7dde40a3b76 --- /dev/null +++ b/lib/lte_link_control/modules/xmodemsleep.c @@ -0,0 +1,136 @@ +/* + * 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); + +#if defined(CONFIG_UNITY) +int parse_xmodemsleep(const char *at_response, struct lte_lc_modem_sleep *modem_sleep) +#else +static int parse_xmodemsleep(const char *at_response, struct lte_lc_modem_sleep *modem_sleep) +#endif /* CONFIG_UNITY */ +{ + 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 000000000000..37157b962b9a --- /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 000000000000..bcff09be9b80 --- /dev/null +++ b/lib/lte_link_control/modules/xt3412.c @@ -0,0 +1,105 @@ +/* + * 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 + +#if defined(CONFIG_UNITY) +int parse_xt3412(const char *at_response, uint64_t *time) +#else +static int parse_xt3412(const char *at_response, uint64_t *time) +#endif /* CONFIG_UNITY */ +{ + 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/modules/memfault-firmware-sdk/Kconfig b/modules/memfault-firmware-sdk/Kconfig index 320344151643..3b8137b8b480 100644 --- a/modules/memfault-firmware-sdk/Kconfig +++ b/modules/memfault-firmware-sdk/Kconfig @@ -157,6 +157,8 @@ config MEMFAULT_NCS_LTE_METRICS bool "Collect LTE metrics" select LTE_LC_TRACE depends on LTE_LINK_CONTROL + depends on LTE_LC_EDRX_MODULE + depends on LTE_LC_PSM_MODULE depends on MODEM_INFO help Capture LTE metrics while the application is running. Supported diff --git a/samples/cellular/battery/prj.conf b/samples/cellular/battery/prj.conf index ecdb83616e3b..38f05f94e5c5 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_CONN_EVAL_MODULE=y # Stacks and heaps CONFIG_HEAP_MEM_POOL_SIZE=1024 diff --git a/samples/cellular/gnss/prj.conf b/samples/cellular/gnss/prj.conf index a1d0670cf91d..07cf1599b0cf 100644 --- a/samples/cellular/gnss/prj.conf +++ b/samples/cellular/gnss/prj.conf @@ -20,8 +20,10 @@ CONFIG_GNSS_SAMPLE_ASSISTANCE_NRF_CLOUD=n # LTE Link Control CONFIG_LTE_LINK_CONTROL=y # Request eDRX from the network +CONFIG_LTE_LC_EDRX_MODULE=y CONFIG_LTE_EDRX_REQ=y # PSM requested periodic TAU 8 hours +CONFIG_LTE_LC_PSM_MODULE=y CONFIG_LTE_PSM_REQ_RPTAU="00101000" # PSM requested active time 6 seconds CONFIG_LTE_PSM_REQ_RAT="00000011" diff --git a/samples/cellular/gnss/sample.yaml b/samples/cellular/gnss/sample.yaml index b650e9f3352f..5178480e9496 100644 --- a/samples/cellular/gnss/sample.yaml +++ b/samples/cellular/gnss/sample.yaml @@ -26,6 +26,7 @@ tests: - CONFIG_GNSS_SAMPLE_NMEA_ONLY=n - CONFIG_GNSS_SAMPLE_MODE_PERIODIC=n - CONFIG_GNSS_SAMPLE_ASSISTANCE_MINIMAL=n + - CONFIG_LTE_LC_EDRX_MODULE=y - CONFIG_LTE_EDRX_REQ=y - CONFIG_FPU=y - CONFIG_GNSS_SAMPLE_REFERENCE_LATITUDE="61.49375330" @@ -49,6 +50,7 @@ tests: - CONFIG_GNSS_SAMPLE_NMEA_ONLY=y - CONFIG_GNSS_SAMPLE_MODE_PERIODIC=n - CONFIG_GNSS_SAMPLE_ASSISTANCE_MINIMAL=n + - CONFIG_LTE_LC_EDRX_MODULE=y - CONFIG_LTE_EDRX_REQ=y - CONFIG_FPU=y - CONFIG_GNSS_SAMPLE_REFERENCE_LATITUDE="61.49375330" @@ -97,6 +99,7 @@ tests: - CONFIG_GNSS_SAMPLE_NMEA_ONLY=n - CONFIG_GNSS_SAMPLE_MODE_PERIODIC=n - CONFIG_GNSS_SAMPLE_ASSISTANCE_MINIMAL=n + - CONFIG_LTE_LC_EDRX_MODULE=y - CONFIG_LTE_EDRX_REQ=y - CONFIG_FPU=y - CONFIG_GNSS_SAMPLE_REFERENCE_LATITUDE="61.49375330" diff --git a/samples/cellular/location/prj.conf b/samples/cellular/location/prj.conf index d562ab93feef..ca66887131db 100644 --- a/samples/cellular/location/prj.conf +++ b/samples/cellular/location/prj.conf @@ -33,7 +33,9 @@ CONFIG_NET_SOCKETS_OFFLOAD=y # LTE link control CONFIG_LTE_LINK_CONTROL=y +CONFIG_LTE_LC_EDRX_MODULE=y CONFIG_LTE_EDRX_REQ=y +CONFIG_LTE_LC_PSM_MODULE=y # Request PSM active time of 8 seconds. CONFIG_LTE_PSM_REQ_RAT="00000100" diff --git a/samples/cellular/lwm2m_client/overlay-aggressive-psm.conf b/samples/cellular/lwm2m_client/overlay-aggressive-psm.conf index e2aa9be693df..854994c76307 100644 --- a/samples/cellular/lwm2m_client/overlay-aggressive-psm.conf +++ b/samples/cellular/lwm2m_client/overlay-aggressive-psm.conf @@ -15,7 +15,9 @@ CONFIG_LTE_PSM_REQ_RAT="00000101" # Enable PSM mode even when network would not allow it. # This requires modem firmware v2.x. +CONFIG_LTE_LC_PSM_MODULE=y CONFIG_LTE_PROPRIETARY_PSM_REQ=y # Enable Sleep event's from modem for indicate sleep +CONFIG_LTE_LC_MODEM_SLEEP_MODULE=y CONFIG_LTE_LC_MODEM_SLEEP_NOTIFICATIONS=y diff --git a/samples/cellular/lwm2m_client/prj.conf b/samples/cellular/lwm2m_client/prj.conf index 800cf3aa8054..e6a3a9831fb2 100644 --- a/samples/cellular/lwm2m_client/prj.conf +++ b/samples/cellular/lwm2m_client/prj.conf @@ -124,6 +124,7 @@ CONFIG_LWM2M_UPDATE_PERIOD=5400 CONFIG_LWM2M_SECONDS_TO_UPDATE_EARLY=60 # Configure PSM mode +CONFIG_LTE_LC_PSM_MODULE=y CONFIG_LTE_PSM_REQ=y # Request periodic TAU of 12 hours (same as lifetime) CONFIG_LTE_PSM_REQ_RPTAU="00101100" @@ -135,6 +136,7 @@ CONFIG_LTE_PSM_REQ_RPTAU="00101100" CONFIG_LTE_PSM_REQ_RAT="00001111" # Request eDRX mode +CONFIG_LTE_LC_EDRX_MODULE=y CONFIG_LTE_EDRX_REQ=y # Requested eDRX cycle length for LTE-M and Nb-IoT @@ -155,6 +157,7 @@ CONFIG_LTE_PTW_VALUE_NBIOT="0000" # Get notification before Tracking Area Update (TAU). Notification triggers registration # update and TAU will be sent with the update which decreases power consumption. +CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE=y CONFIG_LTE_LC_TAU_PRE_WARNING_NOTIFICATIONS=y # Optimize powersaving by using tickless mode in LwM2M engine diff --git a/samples/cellular/modem_shell/prj.conf b/samples/cellular/modem_shell/prj.conf index c3ae023889bf..ace599ba5ce8 100644 --- a/samples/cellular/modem_shell/prj.conf +++ b/samples/cellular/modem_shell/prj.conf @@ -119,6 +119,15 @@ 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 +# Enable required modules +CONFIG_LTE_LC_CONN_EVAL_MODULE=y +CONFIG_LTE_LC_EDRX_MODULE=y +CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE=y +CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE=y +CONFIG_LTE_LC_PSM_MODULE=y +CONFIG_LTE_LC_RAI_MODULE=y +CONFIG_LTE_LC_MODEM_SLEEP_MODULE=y +CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE=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 5da8ae42eff0..ee6070d01158 100644 --- a/samples/cellular/nrf_cloud_multi_service/prj.conf +++ b/samples/cellular/nrf_cloud_multi_service/prj.conf @@ -106,6 +106,8 @@ CONFIG_DATE_TIME=y # LTE link control - used by PGPS and main application CONFIG_LTE_LINK_CONTROL=y +CONFIG_LTE_LC_EDRX_MODULE=y +CONFIG_LTE_LC_PSM_MODULE=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 4d9066f189c3..d1a5cc3ac8eb 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_NEIGHBOR_CELL_MEAS_MODULE=y # Modem info CONFIG_MODEM_INFO=y diff --git a/samples/cellular/udp/prj.conf b/samples/cellular/udp/prj.conf index 32b3547da707..9307ea553abf 100644 --- a/samples/cellular/udp/prj.conf +++ b/samples/cellular/udp/prj.conf @@ -37,3 +37,8 @@ CONFIG_UDP_EDRX_ENABLE=n ## RAI CONFIG_UDP_RAI_ENABLE=y + +## Enable required LTE link control modules +CONFIG_LTE_LC_EDRX_MODULE=y +CONFIG_LTE_LC_PSM_MODULE=y +CONFIG_LTE_LC_RAI_MODULE=y diff --git a/subsys/net/lib/lwm2m_client_utils/Kconfig b/subsys/net/lib/lwm2m_client_utils/Kconfig index bfde6868dc94..c39499cd8ef9 100644 --- a/subsys/net/lib/lwm2m_client_utils/Kconfig +++ b/subsys/net/lib/lwm2m_client_utils/Kconfig @@ -7,6 +7,10 @@ menuconfig LWM2M_CLIENT_UTILS bool "LWM2M client utilities library" select LWM2M if !ZTEST + imply LTE_LC_EDRX_MODULE + imply LTE_LC_NEIGHBOR_CELL_MEAS_MODULE + imply LTE_LC_PSM_MODULE + imply LTE_LC_TAU_PRE_WARNING_MODULE if LWM2M_CLIENT_UTILS diff --git a/tests/lib/location/Kconfig b/tests/lib/location/Kconfig index c68800604b26..34579e7b9a56 100644 --- a/tests/lib/location/Kconfig +++ b/tests/lib/location/Kconfig @@ -12,6 +12,9 @@ config LOCATION_METHOD_GNSS bool "Internal" #depends on NRF_MODEM_LIB depends on LTE_LINK_CONTROL + imply LTE_LC_NEIGHBOR_CELL_MEAS_MODULE + imply LTE_LC_PSM_MODULE + imply LTE_LC_MODEM_SLEEP_MODULE default y help Redefinition to disable modemlib requirement from the tests as we want to mock it. diff --git a/tests/lib/location/prj.conf b/tests/lib/location/prj.conf index 153a4193ae58..0de270346eb1 100644 --- a/tests/lib/location/prj.conf +++ b/tests/lib/location/prj.conf @@ -13,6 +13,9 @@ CONFIG_LOCATION_TEST_AGNSS=y CONFIG_LOCATION=y CONFIG_LTE_LINK_CONTROL=y +CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE=y +CONFIG_LTE_LC_PSM_MODULE=y +CONFIG_LTE_LC_MODEM_SLEEP_MODULE=y CONFIG_LOCATION_METHOD_CELLULAR=y CONFIG_LOCATION_METHOD_WIFI=y diff --git a/tests/lib/lte_lc_api/prj.conf b/tests/lib/lte_lc_api/prj.conf index a106909b024b..41df1965ca61 100644 --- a/tests/lib/lte_lc_api/prj.conf +++ b/tests/lib/lte_lc_api/prj.conf @@ -25,3 +25,12 @@ 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_CONN_EVAL_MODULE=y +CONFIG_LTE_LC_EDRX_MODULE=y +CONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE=y +CONFIG_LTE_LC_PERIODIC_SEARCH_MODULE=y +CONFIG_LTE_LC_PSM_MODULE=y +CONFIG_LTE_LC_RAI_MODULE=y +CONFIG_LTE_LC_MODEM_SLEEP_MODULE=y +CONFIG_LTE_LC_TAU_PRE_WARNING_MODULE=y diff --git a/tests/subsys/net/lib/lwm2m_client_utils/CMakeLists.txt b/tests/subsys/net/lib/lwm2m_client_utils/CMakeLists.txt index ac18685afa80..f9216633e624 100644 --- a/tests/subsys/net/lib/lwm2m_client_utils/CMakeLists.txt +++ b/tests/subsys/net/lib/lwm2m_client_utils/CMakeLists.txt @@ -46,6 +46,10 @@ set(options -DCONFIG_LWM2M_CLIENT_UTILS_RAI -DCONFIG_LWM2M_ENGINE_DEFAULT_LIFETIME=43200 -DCONFIG_LWM2M_CLIENT_UTILS_DTLS_CON_MANAGEMENT + -DCONFIG_LTE_LC_EDRX_MODULE=1 + -DCONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE=1 + -DCONFIG_LTE_LC_PSM_MODULE=1 + -DCONFIG_LTE_LC_TAU_PRE_WARNING_MODULE=1 ) target_compile_options(app diff --git a/tests/subsys/net/lib/lwm2m_fota_utils/CMakeLists.txt b/tests/subsys/net/lib/lwm2m_fota_utils/CMakeLists.txt index 4f4d20dbffd5..faceb800e949 100644 --- a/tests/subsys/net/lib/lwm2m_fota_utils/CMakeLists.txt +++ b/tests/subsys/net/lib/lwm2m_fota_utils/CMakeLists.txt @@ -42,6 +42,14 @@ set(options -DCONFIG_LWM2M_VERSION_1_0=y -DCONFIG_LWM2M_ENGINE_DEFAULT_LIFETIME=43200 -D_POSIX_C_SOURCE=200809L + -DCONFIG_LTE_LC_CONN_EVAL_MODULE=1 + -DCONFIG_LTE_LC_EDRX_MODULE=1 + -DCONFIG_LTE_LC_NEIGHBOR_CELL_MEAS_MODULE=1 + -DCONFIG_LTE_LC_PERIODIC_SEARCH_MODULE=1 + -DCONFIG_LTE_LC_PSM_MODULE=1 + -DCONFIG_LTE_LC_RAI_MODULE=1 + -DCONFIG_LTE_LC_MODEM_SLEEP_MODULE=1 + -DCONFIG_LTE_LC_TAU_PRE_WARNING_MODULE=1 ) target_compile_options(app