nRF9160: nRF Cloud MQTT multi-service

This sample is a minimal, error tolerant, integrated demonstration of the nrf_cloud, location, and AT Host libraries. It demonstrates how you can use these libraries together to support Firmware-Over-The-Air (FOTA), location services, modem AT commands over UART, and periodic sensor samples in your nRF Cloud-enabled application. It also demonstrates how to implement error tolerance in your cellular applications without relying on reboot loops.

Requirements

The sample supports the following development kits:

Hardware platforms

PCA

Board name

Build target

nRF9160 DK

PCA10090

nrf9160dk_nrf9160

nrf9160dk_nrf9160_ns

Thingy:91

PCA20035

thingy91_nrf9160

thingy91_nrf9160_ns

Features

This sample implements or demonstrates the following features:

Structure and theory of operation

This sample is separated into a number of smaller functional units. The top level functional unit and entry point for the sample is the src/main.c file. This file starts three threads, each with a distinct function:

Connection management loop

The connection management loop performs the following four primary tasks:

  1. Configures and activates the cellular modem.

  2. Starts a connection to the LTE network.

  3. Maintains a connection to nRF Cloud.

  4. Re-establishes or retries all lost or failed connections.

The modem only needs to be set up and connected to LTE once. If LTE is lost, the modem automatically works to re-establish it.

The connection to nRF Cloud must be maintained manually by the application. A significant portion of the src/connection.c file is dedicated to this task.

This file also maintains a number of Zephyr Events tracking the state of the LTE and nRF Cloud connections. The connection management loop relies on these events to control its progression.

The connection management loop must conform to the nRF Cloud connection process. This is mostly handled by the nRF Cloud library, with exception to the following behavior: When a device is first being associated with an nRF Cloud account, the nRF Cloud MQTT API will send a user association request notification to the device. Upon receiving this notification, the device must wait for a user association success notification, and then manually disconnect from and reconnect to nRF Cloud. Notifications of user association success are sent for every subsequent connection after this as well, so the device must only disconnect and reconnect if it previously received a user association request notification. This behavior is handled by the NRF_CLOUD_EVT_USER_ASSOCIATION_REQUEST and NRF_CLOUD_EVT_USER_ASSOCIATED cases inside the cloud_event_handler() function in the src/connection.c file.

The application must also manually reset its connection to nRF Cloud whenever LTE is lost. Otherwise, the nRF Cloud library will deadlock. The LTE_LC_EVT_NW_REG_STATUS case inside the lte_event_handler() function performs this task.

Device message queue

Any thread may submit device messages to the device message queue, where they are stored until a working connection to nRF Cloud is established. Once this happens, the message thread transmits all enqueued device messages, one at a time and in fast succession, to nRF Cloud. If an enqueued message fails to send, it will be sent back to the queue and tried again later. If more than CONFIG_MAX_CONSECUTIVE_SEND_FAILURES messages fail to send in a row, the connection to nRF Cloud is reset and re-established after a short delay. The transmission pauses again whenever connection to nRF Cloud is lost. Management of the device message queue is implemented entirely in the src/connection.c file.

Application thread and main application loop

The application thread is implemented in the src/application.c file, and is responsible for the high-level behavior of this sample. It performs the following major tasks:

  • Establishes periodic position tracking (which the Location library performs).

  • Periodically samples temperature data (using the src/temperature.c file).

  • Constructs timestamped sensor sample and location device messages using cJSON.

  • Sends sensor sample and location device messages to the Device message queue.

Note

periodic location tracking is handled by the Location library once it has been requested, whereas temperature samples are individually requested by the Main Application Loop.

FOTA

The FOTA update support is almost entirely implemented by enabling the CONFIG_NRF_CLOUD_FOTA option (which is implicitly enabled by CONFIG_NRF_CLOUD_MQTT).

However, even with CONFIG_NRF_CLOUD_FOTA enabled, applications must still reboot themselves manually after FOTA download completion, and must still update their Device Shadow to reflect FOTA support.

Reboot after download completion is handled by the src/fota_support.c file, triggered by a call from the src/connection.c file.

Device Shadow updates are performed in the src/connection.c file.

In a real-world setting, these two behaviors could be directly implemented in the src/connection.c file. In this sample, they are separated for clarity.

Temperature sensing

Temperature sensing is mostly implemented in the src/temperature.c file. This includes generation of false temperature readings on the Nordic nRF9160 DK, which does not have a built-in temperature sensor.

Using the built-in temperature sensor of the Nordic Thingy:91 requires a devicetree overlay file, namely the boards/thingy91_nrf9160_ns.overlay file, as well as enabling the Kconfig options CONFIG_SENSOR and CONFIG_BME680. The devicetree overlay file is automatically applied during compilation whenever the thingy91_nrf9160_ns target is selected. The required Kconfig options are implicitly enabled by CONFIG_TEMP_DATA_USE_SENSOR.

Location tracking

All matters concerning location tracking are handled in the src/location_tracking.c file. For the most part, this amounts to setting up a periodic location request, and then passing the results to a callback configured by the src/application.c file. Additionally, the following two tasks which must be implemented:

  • The Location library requires assisted GPS (A-GPS) data to have the fastest possible time-to-first-fix. The library requests this data automatically, but has no way to automatically receive the response. Therefore, all MQTT messages received from nRF Cloud must be be passed directly through to the nRF Cloud A-GPS library to be checked for A-GPS data. The location_agps_data_handler() function handles this task, and MQTT messages are sent directly to it from the src/connection.c file.

  • The GNSS module cannot obtain a fix without the antenna first being properly configured. This configuration is performed by the gnss_antenna_configure() function.

Test counter

You can enable a test counter by enabling the CONFIG_TEST_COUNTER option. Every time a sensor sample is sent, this counter is incremented by one, and its current value is sent to nRF Cloud. A plot of the value of the counter over time is automatically shown in the nRF Cloud portal. This plot is useful for tracking, visualizing, and debugging connection loss, resets, and re-establishment behavior.

Device message formatting

This sample constructs JSON-based device messages using cJSON.

While any valid JSON string can be sent as a device message, and accepted and stored by nRF Cloud, there are some pre-designed message structures, known as schemas. The nRF Cloud portal knows how to interpret these schemas. These schemas are described in nRF Cloud application protocols for long-range devices. The device messages constructed in the src/application.c file all adhere to the general message schema. GNSS and temperature data device messages conform to the gps and temp deviceToCloud schemas respectively.

Configuration

See Configuring your application for information about how to permanently or temporarily change the configuration.

Configuration options

Set the following configuration options for the sample:

CONFIG_POWER_SAVING_MODE_ENABLE - Enable Power Saving Mode (PSM)

Requests Power Saving Mode from cellular network when enabled.

CONFIG_LTE_INIT_RETRY_TIMEOUT_SECONDS - LTE initialization retry timeout

Sets the number of seconds between each LTE modem initialization retry.

CONFIG_CLOUD_CONNECTION_RETRY_TIMEOUT_SECONDS - Cloud connection retry timeout (seconds)

Sets the cloud connection retry timeout in seconds.

CONFIG_CLOUD_CONNECTION_REESTABLISH_DELAY_SECONDS - Cloud connection re-establishment delay (seconds)

Sets the connection re-establishment delay in seconds. When the connection to nRF Cloud has been reset, wait for this amount of time before a new attempt to connect.

CONFIG_CLOUD_READY_TIMEOUT_SECONDS - Cloud readiness timeout (seconds)

Sets the cloud readiness timeout in seconds. If the connection to nRF Cloud does not become ready within this timeout, the sample will reset its connection and try again.

CONFIG_DATE_TIME_ESTABLISHMENT_TIMEOUT_SECONDS - Modem date and time establishment timeout (seconds)

Sets the timeout for modem date and time establishment (in seconds). The sample waits for this number of seconds for the modem to determine the current date and time before giving up and moving on.

CONFIG_APPLICATION_THREAD_STACK_SIZE - Application Thread Stack Size (bytes)

Sets the stack size (in bytes) for the application thread of the sample.

CONFIG_CONNECTION_THREAD_STACK_SIZE - Connection Thread Stack Size (bytes)

Sets the stack size (in bytes) for the connection thread of the sample.

CONFIG_MESSAGE_THREAD_STACK_SIZE - Message Queue Thread Stack Size (bytes)

Sets the stack size (in bytes) for the message queue processing thread of the sample.

CONFIG_MAX_OUTGOING_MESSAGES - Outgoing message maximum

Sets the maximum number of messages that can be in the outgoing message queue. Messages submitted past this limit will not be enqueued.

CONFIG_MAX_CONSECUTIVE_SEND_FAILURES - Max outgoing consecutive send failures

Sets the maximum number of device messages which may fail to send before a connection reset and cooldown is triggered.

CONFIG_CONSECUTIVE_SEND_FAILURE_COOLDOWN_SECONDS - Cooldown after max consecutive send failures exceeded (seconds)

Sets the cooldown time (in seconds) after the maximum number of consecutive send failures is exceeded. If a connection reset is triggered by too many failed device messages, the sample waits for this long (in seconds) before trying again.

CONFIG_SENSOR_SAMPLE_INTERVAL_SECONDS - Sensor sampling interval (seconds)

Sets the time to wait between each temperature sensor sample.

Note

Decreasing the sensor sampling interval too much leads to more frequent use of the LTE connection, which can prevent GNSS from obtaining a fix. This is because GNSS can operate only when the LTE connection is not active. Every time a sensor sample is sent, it interrupts any attempted GNSS fix. The exact time required to obtain a GNSS fix will vary depending on satellite visibility, time since last fix, the type of antenna used, and other environmental factors. In good conditions, and with A-GPS data, one minute is a reasonable interval for reliably getting a location estimate. This allows using long enough value for CONFIG_GNSS_FIX_TIMEOUT_SECONDS, while still leaving enough time for falling back to cellular positioning if needed.

The default sensor sampling interval, 60 seconds, will quickly consume cellular data, and should not be used in a production environment. Instead, consider using a less frequent interval, and if necessary, combining multiple sensor samples into a single device message , or combining multiple device messages using the d2c/bulk device message topic.

If you significantly increase the sampling interval, you must keep the CONFIG_MQTT_KEEPALIVE short to avoid the carrier silently closing the MQTT connection due to inactivity, which could result in dropped device messages. The exact maximum value depends on your carrier. In general, a few minutes or less should work well.

CONFIG_GNSS_ANTENNA_ONBOARD - Select the onboard GNSS antenna (default)

This option enables the onboard GNSS antenna.

CONFIG_GNSS_ANTENNA_EXTERNAL - Select an external GNSS antenna

This option enables the external GNSS antenna.

CONFIG_GNSS_AT_MAGPIO - AT%XMAGPIO command

Sets the AT%XMAGPIO command to be sent to the GNSS module. This command must be sent for GNSS to function properly.

A reasonable value is automatically selected for this option based on the build target. You should never have to mess with it yourself unless you are using a non-standard board or custom GNSS antenna. See nRF9160 SiP pin configuration for more details.

CONFIG_GNSS_AT_COEX0 - AT%XCOEX0 command

Sets the AT%XCOEX0 command to be sent to the GNSS module. This command must be sent for GNSS to function properly.

A reasonable value is automatically selected for this option based on the build target and selected antenna configuration. You should never have to mess with it yourself unless you are using a non-standard board or custom GNSS antenna. See nRF9160 SiP pin configuration for more details.

CONFIG_GNSS_FIX_TIMEOUT_SECONDS - GNSS fix timeout (seconds)

Sets the GNSS fix timeout in seconds. On each location sample, try for this long to achieve a GNSS fix before falling back to cellular positioning.

CONFIG_LOCATION_SAMPLE_INTERVAL_SECONDS - Location sampling interval (seconds)

Sets the location sampling interval in seconds.

CONFIG_TEMP_DATA_USE_SENSOR - Use genuine temperature data

Sets whether to take genuine temperature measurements from a connected BME680 sensor, or just simulate sensor data.

CONFIG_TEST_COUNTER - Enable test counter

Sets whether the test counter is enabled.

Building and running

This sample can be found under samples/nrf9160/nrf_cloud_mqtt_multi_service in the nRF Connect SDK folder structure.

See Building and programming an application for information about how to build and program the application.

The sample is configured to compile and run as a non-secure application on nRF91’s Cortex-M33. Therefore, it automatically includes the Secure Partition Manager that prepares the required peripherals to be available for the application.

You can also configure it to use TF-M instead of Secure Partition Manager.

Dependencies

This sample uses the following nRF Connect SDK libraries and drivers:

It uses the following sdk-nrfxlib library: