From a7789f6c6953009924dc5a04dbd4b4dda97feaa2 Mon Sep 17 00:00:00 2001 From: Pete Skeggs Date: Wed, 14 Jun 2023 16:22:57 -0700 Subject: [PATCH] samples: cellular: nrf_cloud_coap_client: Add new sample This nRF Cloud CoAP client demonstrates using FOTA, messaging, cellular positioning, A-GPS, and P-GPS over CoAP. Get modem info IMEI and mfw. Init the modem, which waits for LTE connection. Look up server IP. Init the client, including looking up device IP with modem info. Send real cell pos parameters, receive, decode, and display location. Send cell pos back as fake GNSS PVT data. Send fake temperature sensor data. Receive and process MODEM and APP FOTA types. Signed-off-by: Pete Skeggs --- .../nrf_cloud_coap_client/CMakeLists.txt | 18 + .../cellular/nrf_cloud_coap_client/Kconfig | 72 +++ .../nrf9160dk_with_nrf7002ek.overlay | 19 + .../nrf_cloud_coap_client/overlay-debug.conf | 41 ++ .../overlay-nrf7002ek-wifi-scan-only.conf | 51 ++ .../overlay-offload-tls.conf | 19 + .../cellular/nrf_cloud_coap_client/prj.conf | 114 ++++ .../nrf_cloud_coap_client/prj_qemu_x86.conf | 31 + .../nrf_cloud_coap_client/sample.yaml | 19 + .../nrf_cloud_coap_client/src/handle_fota.c | 533 ++++++++++++++++ .../nrf_cloud_coap_client/src/handle_fota.h | 14 + .../cellular/nrf_cloud_coap_client/src/main.c | 573 ++++++++++++++++++ .../nrf_cloud_coap_client/src/scan_wifi.c | 168 +++++ .../nrf_cloud_coap_client/src/scan_wifi.h | 17 + 14 files changed, 1689 insertions(+) create mode 100644 samples/cellular/nrf_cloud_coap_client/CMakeLists.txt create mode 100644 samples/cellular/nrf_cloud_coap_client/Kconfig create mode 100644 samples/cellular/nrf_cloud_coap_client/nrf9160dk_with_nrf7002ek.overlay create mode 100644 samples/cellular/nrf_cloud_coap_client/overlay-debug.conf create mode 100644 samples/cellular/nrf_cloud_coap_client/overlay-nrf7002ek-wifi-scan-only.conf create mode 100644 samples/cellular/nrf_cloud_coap_client/overlay-offload-tls.conf create mode 100644 samples/cellular/nrf_cloud_coap_client/prj.conf create mode 100644 samples/cellular/nrf_cloud_coap_client/prj_qemu_x86.conf create mode 100644 samples/cellular/nrf_cloud_coap_client/sample.yaml create mode 100644 samples/cellular/nrf_cloud_coap_client/src/handle_fota.c create mode 100644 samples/cellular/nrf_cloud_coap_client/src/handle_fota.h create mode 100644 samples/cellular/nrf_cloud_coap_client/src/main.c create mode 100644 samples/cellular/nrf_cloud_coap_client/src/scan_wifi.c create mode 100644 samples/cellular/nrf_cloud_coap_client/src/scan_wifi.h diff --git a/samples/cellular/nrf_cloud_coap_client/CMakeLists.txt b/samples/cellular/nrf_cloud_coap_client/CMakeLists.txt new file mode 100644 index 000000000000..24c6d8f42a14 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright (c) 2022 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(nrf-cloud-coap-client) +zephyr_compile_definitions(PROJECT_NAME=${PROJECT_NAME}) +find_program(ZCBOR zcbor REQUIRED) + +# NORDIC SDK APP START +target_sources(app PRIVATE src/main.c src/handle_fota.c) +# NORDIC SDK APP END + +zephyr_include_directories(src) diff --git a/samples/cellular/nrf_cloud_coap_client/Kconfig b/samples/cellular/nrf_cloud_coap_client/Kconfig new file mode 100644 index 000000000000..e9891bf55326 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/Kconfig @@ -0,0 +1,72 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "nRF Cloud CoAP client sample" + +config NRF_CLOUD_COAP_CLIENT_SAMPLE_VERSION + string "The sample's version string" + default "1.0.0" + +config COAP_FOTA_JOB_CHECK_RATE_MIN + int "Rate (minutes) at which this sample will check for FOTA updates" + default 1 + +config COAP_FOTA_DL_TIMEOUT_MIN + int "The time (minutes) allotted for a FOTA download to complete" + default 15 + help + If the FOTA download does not complete in the allotted time, + the download will be cancelled and the job status will be + set as failed. + +config COAP_FOTA_USE_NRF_CLOUD_SETTINGS_AREA + bool "Use the same settings area as the nRF Cloud FOTA library" + default y + help + Using the same settings area as the nRF Cloud FOTA library will + allow this sample to perform application-FOTA updates to applications + built with CONFIG_NRF_CLOUD_FOTA enabled. + +config COAP_FOTA_SETTINGS_NAME + depends on !COAP_FOTA_USE_NRF_CLOUD_SETTINGS_AREA + string "Settings identifier for the COAP FOTA sample" + default "coap_fota_sample" + help + The root settings identifier for this application. + For application-FOTA, this value should also be found in your target application + so that the FOTA status can be properly reported to nRF Cloud. + +config COAP_FOTA_SETTINGS_KEY_PENDING_JOB + depends on !COAP_FOTA_USE_NRF_CLOUD_SETTINGS_AREA + string "Settings item key for pending FOTA job info" + default "pending_job" + help + The settings item key where pending FOTA job info will be stored. + For application-FOTA, this value should also be found in your target application + so that the FOTA status can be properly reported to nRF Cloud. + +config COAP_FOTA_USE_THREAD + bool "Use a thread to process FOTA in the background" + help + If not enabled, call the handle_fota_process() periodically. Otherwise, a + thread will process based on COAP_FOTA_JOB_CHECK_RATE_MIN. + +config COAP_FOTA_THREAD_STACK_SIZE + int "CoAP FOTA Thread Stack Size (bytes)" + default 2048 + help + Sets the stack size (in bytes) for the CoAP FOTA thread of the + sample. + +module = NRF_CLOUD_COAP_CLIENT +module-dep = LOG +module-str = nRF Cloud CoAP Client Sample +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" +endmenu # Application sample + +menu "Zephyr Kernel" +source "Kconfig.zephyr" +endmenu diff --git a/samples/cellular/nrf_cloud_coap_client/nrf9160dk_with_nrf7002ek.overlay b/samples/cellular/nrf_cloud_coap_client/nrf9160dk_with_nrf7002ek.overlay new file mode 100644 index 000000000000..f2951b0f3182 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/nrf9160dk_with_nrf7002ek.overlay @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* Use high drive mode on SPI pins that handle communication with nRF7002 + * so that SCK frequency > 4 MHz can be used. */ + &spi3_default { + group1 { + nordic,drive-mode = ; + }; +}; + +/* Disabled because of conflicts on P0.00 and P0.01 - Arduino pins D0 and D1 + * (iovdd-ctrl-gpios and bucken-gpios in nrf7002ek, respectively). */ +&uart1 { + status = "disabled"; +}; diff --git a/samples/cellular/nrf_cloud_coap_client/overlay-debug.conf b/samples/cellular/nrf_cloud_coap_client/overlay-debug.conf new file mode 100644 index 000000000000..e4e42a90e87f --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/overlay-debug.conf @@ -0,0 +1,41 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +# General config +CONFIG_DEBUG=y + +# Logging +CONFIG_LOG=y +CONFIG_COAP_LOG_LEVEL_DBG=y +CONFIG_NRF_CLOUD_COAP_CLIENT_LOG_LEVEL_DBG=y +CONFIG_NRF_CLOUD_COAP_LOG_LEVEL_DBG=y +CONFIG_NRF_CLOUD_LOG_LEVEL_INF=y +CONFIG_NRF_CLOUD_GPS_LOG_LEVEL_INF=y +CONFIG_LOCATION_LOG_LEVEL_DBG=y +CONFIG_LTE_LINK_CONTROL_LOG_LEVEL_WRN=y +CONFIG_NET_LOG=y +CONFIG_NET_OFFLOAD_LOG_LEVEL_WRN=y +CONFIG_NET_UDP_LOG_LEVEL_WRN=y +CONFIG_NET_SOCKETS_LOG_LEVEL_WRN=y +CONFIG_NET_IF_LOG_LEVEL_WRN=y +CONFIG_NET_CONFIG_LOG_LEVEL_WRN=y +CONFIG_DNS_RESOLVER_LOG_LEVEL_WRN=n +CONFIG_NET_CONTEXT_LOG_LEVEL_WRN=y +CONFIG_AT_HOST_LOG_LEVEL_WRN=y +CONFIG_DATE_TIME_LOG_LEVEL_INF=y +CONFIG_SETTINGS_LOG_LEVEL_INF=y +CONFIG_DOWNLOAD_CLIENT_LOG_LEVEL_INF=y + +CONFIG_LOG_PRINTK=y +CONFIG_LOG_MODE_IMMEDIATE=y +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_FUNC_NAME_PREFIX_DBG=y +CONFIG_LOG_FUNC_NAME_PREFIX_INF=y +CONFIG_LOG_FUNC_NAME_PREFIX_WRN=y +CONFIG_LOG_FUNC_NAME_PREFIX_ERR=y + +# Modem library +CONFIG_NRF_MODEM_LIB_LOG_LEVEL_INF=y +CONFIG_NRF_MODEM_LOG=n diff --git a/samples/cellular/nrf_cloud_coap_client/overlay-nrf7002ek-wifi-scan-only.conf b/samples/cellular/nrf_cloud_coap_client/overlay-nrf7002ek-wifi-scan-only.conf new file mode 100644 index 000000000000..6c7e436af708 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/overlay-nrf7002ek-wifi-scan-only.conf @@ -0,0 +1,51 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Overlay to use nRF7002 EK on top of nrf9160 DK for Wi-Fi scanning + +# Enable the Wi-Fi location method: +CONFIG_LOCATION_METHOD_WIFI=y +# Align this with CONFIG_NRF700X_SCAN_LIMIT. +# Also see comments for CONFIG_HEAP_MEM_POOL_SIZE. +CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT=10 +CONFIG_NRF700X_SCAN_LIMIT=10 + +# Enable Wi-Fi drivers, (and the native NET stack so that the location library can access them) +CONFIG_WIFI=y +CONFIG_WIFI_NRF700X=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_NATIVE=y + +# System settings +CONFIG_NEWLIB_LIBC=y + +# Disable the supplicant and local networking features, since we don't need connectivity +CONFIG_WPA_SUPP=n +CONFIG_MBEDTLS=n +CONFIG_NET_IPV4=n +CONFIG_NET_IPV6=n +CONFIG_NET_UDP=n +CONFIG_NET_TCP=n +CONFIG_NORDIC_SECURITY_BACKEND=n + +# Stack/heap tweaks needed to support Wi-Fi. +# Heap allocation should be changed when CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT +# and CONFIG_NRF700X_SCAN_LIMIT (which should be the same value) are changed. +CONFIG_HEAP_MEM_POOL_SIZE=20000 +CONFIG_NET_MGMT_EVENT_STACK_SIZE=2048 + +# NET tweaks needed to support Wi-Fi +CONFIG_NET_BUF_RX_COUNT=8 +CONFIG_NET_BUF_TX_COUNT=8 +CONFIG_NET_PKT_RX_COUNT=1 +CONFIG_NET_PKT_TX_COUNT=1 +CONFIG_NET_TX_STACK_SIZE=4096 +CONFIG_NET_RX_STACK_SIZE=4096 +CONFIG_NET_TC_TX_COUNT=1 +CONFIG_NET_MAX_CONTEXTS=5 + +CONFIG_NRF_CLOUD_COAP_LOG_LEVEL_DBG=y +CONFIG_LOCATION_LOG_LEVEL_DBG=y diff --git a/samples/cellular/nrf_cloud_coap_client/overlay-offload-tls.conf b/samples/cellular/nrf_cloud_coap_client/overlay-offload-tls.conf new file mode 100644 index 000000000000..2300eafcf144 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/overlay-offload-tls.conf @@ -0,0 +1,19 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Networking +CONFIG_NET_NATIVE=n +CONFIG_NET_OFFLOAD=y +CONFIG_NET_SOCKETS_OFFLOAD=y + +CONFIG_NET_SOCKETS_TLS_SET_MAX_FRAGMENT_LENGTH=y + +# Configure for using offloaded UDP and TLS +CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY=40 +CONFIG_MODEM_KEY_MGMT=y + +# Generate JWTs in the modem +CONFIG_MODEM_JWT=y diff --git a/samples/cellular/nrf_cloud_coap_client/prj.conf b/samples/cellular/nrf_cloud_coap_client/prj.conf new file mode 100644 index 000000000000..d2c2f05829d1 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/prj.conf @@ -0,0 +1,114 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +# General config +CONFIG_HEAP_MEM_POOL_SIZE=16384 +CONFIG_NEWLIB_LIBC=y +CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y + +CONFIG_DK_LIBRARY=y + +# Mbed TLS logs include %zu formatters, which are not handled by the nano +# version of newlib +CONFIG_NEWLIB_LIBC_NANO=n + +# Main thread +CONFIG_MAIN_STACK_SIZE=6144 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=6144 + +# Networking +CONFIG_NETWORKING=y +CONFIG_NET_CONFIG_NEED_IPV6=n +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_POSIX_NAMES=y + +# LTE link control +CONFIG_LTE_LINK_CONTROL=y +CONFIG_LTE_NETWORK_MODE_LTE_M_GPS=y + +# Modem library +CONFIG_NRF_MODEM_LIB=y +CONFIG_NRF_MODEM_LIB_LOG_FW_VERSION_UUID=n + +# Modem info +CONFIG_MODEM_INFO=y +CONFIG_MODEM_INFO_ADD_DEVICE=y +CONFIG_MODEM_INFO_ADD_NETWORK=y +CONFIG_MODEM_INFO_ADD_DATE_TIME=n +CONFIG_MODEM_INFO_ADD_SIM=n +CONFIG_MODEM_INFO_ADD_SIM_ICCID=n +CONFIG_MODEM_INFO_ADD_SIM_IMSI=n + +# AT Host +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_UART_0_INTERRUPT_DRIVEN=y +CONFIG_AT_HOST_LIBRARY=y +CONFIG_AT_MONITOR_HEAP_SIZE=4096 + +# LTE link control +CONFIG_NET_L2_VIRTUAL=y + +# Date/Time +CONFIG_DATE_TIME=y +CONFIG_DATE_TIME_AUTO_UPDATE=y +CONFIG_DATE_TIME_MODEM=y +CONFIG_DATE_TIME_NTP=n + +CONFIG_FPU=y +CONFIG_CBPRINTF_FP_SUPPORT=y + +CONFIG_NRF_CLOUD_COAP=y +CONFIG_COAP_CLIENT_BLOCK_SIZE=1024 +CONFIG_COAP_CLIENT_STACK_SIZE=6144 +CONFIG_COAP_CLIENT_THREAD_PRIORITY=0 +CONFIG_COAP_EXTENDED_OPTIONS_LEN_VALUE=40 + +CONFIG_DOWNLOAD_CLIENT_STACK_SIZE=4096 + +# Flash - Used by FOTA and PGPS +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y +CONFIG_STREAM_FLASH=y +CONFIG_MPU_ALLOW_FLASH_WRITE=y +CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=n +CONFIG_FCB=y +CONFIG_SETTINGS=y +CONFIG_SETTINGS_FCB=y + +# MCUBOOT - Needed by FOTA +CONFIG_BOOTLOADER_MCUBOOT=y +CONFIG_IMG_MANAGER=y +CONFIG_MCUBOOT_IMG_MANAGER=y +CONFIG_STREAM_FLASH_ERASE=y +CONFIG_IMG_ERASE_PROGRESSIVELY=y + +CONFIG_REBOOT=y + +# FOTA download +CONFIG_FOTA_DOWNLOAD=y +CONFIG_FOTA_DOWNLOAD_PROGRESS_EVT=y +CONFIG_DFU_TARGET=y +CONFIG_DOWNLOAD_CLIENT=y + +CONFIG_LOG=y +CONFIG_NET_LOG=y +CONFIG_LOG_PRINTK=y +CONFIG_COAP_LOG_LEVEL_INF=y +CONFIG_NRF_CLOUD_COAP_LOG_LEVEL_INF=y +CONFIG_NRF_CLOUD_COAP_CLIENT_LOG_LEVEL_INF=y + +CONFIG_LOCATION=y +CONFIG_LOCATION_METHOD_GNSS=y +CONFIG_LOCATION_METHOD_CELLULAR=y +CONFIG_LOCATION_DATA_DETAILS=y +CONFIG_LOCATION_LOG_LEVEL_INF=y + +# GNSS assistance +CONFIG_NRF_CLOUD_AGPS=y +CONFIG_NRF_CLOUD_PGPS=y +CONFIG_NRF_CLOUD_PGPS_REQUEST_UPON_INIT=y +CONFIG_NRF_CLOUD_PGPS_NUM_PREDICTIONS=4 diff --git a/samples/cellular/nrf_cloud_coap_client/prj_qemu_x86.conf b/samples/cellular/nrf_cloud_coap_client/prj_qemu_x86.conf new file mode 100644 index 000000000000..348fa7efa5d7 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/prj_qemu_x86.conf @@ -0,0 +1,31 @@ +# +# Copyright (c) 2019 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +# General config +CONFIG_TEST_RANDOM_GENERATOR=y + +# CoAP +CONFIG_COAP=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_UDP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_POSIX_NAMES=y + +CONFIG_DNS_RESOLVER=y +CONFIG_DNS_SERVER_IP_ADDRESSES=y +CONFIG_DNS_SERVER1="8.8.8.8" + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" + +# Main thread +CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE=2048 diff --git a/samples/cellular/nrf_cloud_coap_client/sample.yaml b/samples/cellular/nrf_cloud_coap_client/sample.yaml new file mode 100644 index 000000000000..63bc24594e4d --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/sample.yaml @@ -0,0 +1,19 @@ +sample: + name: nRF Cloud CoAP client sample +tests: + samples.nrf9160.nrf_cloud_coap_client: + build_only: true + integration_platforms: + - nrf9160dk_nrf9160_ns + platform_allow: nrf9160dk_nrf9160_ns nrf9161dk_nrf9161_ns + extra_args: OVERLAY_CONFIG=overlay-offload-tls.conf + tags: ci_build + sample.nrf9160.nrf_cloud_coap_client.nrf7002ek_wifi: + build_only: true + integration_platforms: + - nrf9160dk_nrf9160_ns + platform_allow: nrf9160dk_nrf9160_ns nrf9161dk_nrf9161_ns + extra_args: SHIELD=nrf7002ek + OVERLAY_CONFIG="overlay-nrf7002ek-wifi-scan-only.conf;overlay-offload-tls.conf" + DTC_OVERLAY_FILE="nrf9160dk_with_nrf7002ek.overlay" + tags: ci_build diff --git a/samples/cellular/nrf_cloud_coap_client/src/handle_fota.c b/samples/cellular/nrf_cloud_coap_client/src/handle_fota.c new file mode 100644 index 000000000000..77014626dca3 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/src/handle_fota.c @@ -0,0 +1,533 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "handle_fota.h" + +LOG_MODULE_REGISTER(nrf_cloud_coap_fota, CONFIG_NRF_CLOUD_COAP_CLIENT_LOG_LEVEL); + +#define PENDING_REBOOT_S 10 +#define JOB_WAIT_S 30 +#define ERROR_REBOOT_S 30 +#define FOTA_REBOOT_S 10 + +/* Use the settings library to store FOTA job information to flash so + * that the job status can be updated after a reboot + */ +#if defined(CONFIG_COAP_FOTA_USE_NRF_CLOUD_SETTINGS_AREA) +#define FOTA_SETTINGS_NAME NRF_CLOUD_SETTINGS_FULL_FOTA +#define FOTA_SETTINGS_KEY_PENDING_JOB NRF_CLOUD_SETTINGS_FOTA_JOB +#define FOTA_SETTINGS_FULL NRF_CLOUD_SETTINGS_FULL_FOTA_JOB +#else +#define FOTA_SETTINGS_NAME CONFIG_COAP_FOTA_SETTINGS_NAME +#define FOTA_SETTINGS_KEY_PENDING_JOB CONFIG_COAP_FOTA_SETTINGS_KEY_PENDING_JOB +#define FOTA_SETTINGS_FULL FOTA_SETTINGS_NAME "/" FOTA_SETTINGS_KEY_PENDING_JOB +#endif + +#define FOTA_DL_FRAGMENT_SZ 1400 + +/* FOTA job status strings that provide additional details for nrf_cloud_fota_status values */ +const char * const FOTA_STATUS_DETAILS_TIMEOUT = "Download did not complete in the allotted time"; +const char * const FOTA_STATUS_DETAILS_DL_ERR = "Error occurred while downloading the file"; +const char * const FOTA_STATUS_DETAILS_MDM_REJ = "Modem rejected the update; invalid delta?"; +const char * const FOTA_STATUS_DETAILS_MDM_ERR = "Modem was unable to apply the update"; +const char * const FOTA_STATUS_DETAILS_MCU_REJ = "Device rejected the update"; +const char * const FOTA_STATUS_DETAILS_MCU_ERR = "Update could not be validated"; +const char * const FOTA_STATUS_DETAILS_SUCCESS = "FOTA update completed successfully"; +const char * const FOTA_STATUS_DETAILS_NO_VALIDATE = "FOTA update completed without validation"; +const char * const FOTA_STATUS_DETAILS_MISMATCH = "FW file does not match specified FOTA type"; + +/* Semaphore to indicate the FOTA download has arrived at a terminal state */ +static K_SEM_DEFINE(fota_download_sem, 0, 1); + +/* FOTA job info received from nRF Cloud */ +static struct nrf_cloud_fota_job_info job; + +/* FOTA job status */ +static enum nrf_cloud_fota_status fota_status = NRF_CLOUD_FOTA_QUEUED; +static char const *fota_status_details = FOTA_STATUS_DETAILS_SUCCESS; + +/* Pending job info used with the settings library */ +static struct nrf_cloud_settings_fota_job pending_job = { + .type = NRF_CLOUD_FOTA_TYPE__INVALID, + .validate = NRF_CLOUD_FOTA_VALIDATE_NONE +}; + +#if defined(CONFIG_NRF_CLOUD_FOTA_FULL_MODEM_UPDATE) +/* Flag to indicate if full modem FOTA is enabled */ +static bool full_modem_fota_initd; +#endif + +static int coap_fota_settings_set(const char *key, size_t len_rd, + settings_read_cb read_cb, void *cb_arg); + +SETTINGS_STATIC_HANDLER_DEFINE(coap_fota, FOTA_SETTINGS_NAME, NULL, + coap_fota_settings_set, NULL, NULL); + +static int coap_fota_settings_set(const char *key, size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + ssize_t sz; + + if (!key) { + LOG_DBG("Key is NULL"); + return -EINVAL; + } + + LOG_DBG("Settings key: %s, size: %d", key, len_rd); + + if (strncmp(key, FOTA_SETTINGS_KEY_PENDING_JOB, strlen(FOTA_SETTINGS_KEY_PENDING_JOB))) { + return -ENOMSG; + } + + if (len_rd > sizeof(pending_job)) { + LOG_INF("FOTA settings size larger than expected"); + len_rd = sizeof(pending_job); + } + + sz = read_cb(cb_arg, (void *)&pending_job, len_rd); + if (sz == 0) { + LOG_DBG("FOTA settings key-value pair has been deleted"); + return -EIDRM; + } else if (sz < 0) { + LOG_ERR("FOTA settings read error: %d", sz); + return -EIO; + } + + if (sz == sizeof(pending_job)) { + LOG_INF("Saved job: %s, type: %d, validate: %d, bl: 0x%X", + pending_job.id, pending_job.type, + pending_job.validate, pending_job.bl_flags); + } else { + LOG_INF("FOTA settings size smaller than current, likely outdated"); + } + + return 0; +} + +static int save_pending_job(void) +{ + int ret = settings_save_one(FOTA_SETTINGS_FULL, &pending_job, sizeof(pending_job)); + + if (ret) { + LOG_ERR("Failed to save FOTA job to settings, error: %d", ret); + } + + return ret; +} + +static void http_fota_dl_handler(const struct fota_download_evt *evt) +{ + LOG_DBG("evt: %d", evt->id); + + switch (evt->id) { + case FOTA_DOWNLOAD_EVT_FINISHED: + LOG_INF("FOTA download finished"); + fota_status = NRF_CLOUD_FOTA_SUCCEEDED; + k_sem_give(&fota_download_sem); + break; + case FOTA_DOWNLOAD_EVT_ERASE_PENDING: + LOG_INF("FOTA download erase pending"); + fota_status = NRF_CLOUD_FOTA_SUCCEEDED; + k_sem_give(&fota_download_sem); + break; + case FOTA_DOWNLOAD_EVT_ERASE_DONE: + LOG_DBG("FOTA download erase done"); + break; + case FOTA_DOWNLOAD_EVT_ERROR: + LOG_INF("FOTA download error: %d", evt->cause); + + fota_status = NRF_CLOUD_FOTA_FAILED; + fota_status_details = FOTA_STATUS_DETAILS_DL_ERR; + + if (evt->cause == FOTA_DOWNLOAD_ERROR_CAUSE_INVALID_UPDATE) { + fota_status = NRF_CLOUD_FOTA_REJECTED; + if (nrf_cloud_fota_is_type_modem(job.type)) { + fota_status_details = FOTA_STATUS_DETAILS_MDM_REJ; + } else { + fota_status_details = FOTA_STATUS_DETAILS_MCU_REJ; + } + } else if (evt->cause == FOTA_DOWNLOAD_ERROR_CAUSE_TYPE_MISMATCH) { + fota_status_details = FOTA_STATUS_DETAILS_MISMATCH; + } + k_sem_give(&fota_download_sem); + break; + case FOTA_DOWNLOAD_EVT_PROGRESS: + LOG_INF("FOTA download percent: %d", evt->progress); + break; + default: + break; + } +} + +static bool pending_fota_job_exists(void) +{ + return (pending_job.validate != NRF_CLOUD_FOTA_VALIDATE_NONE); +} + +static void process_pending_job(void) +{ + bool reboot_required = false; + int ret; + + LOG_INF("Checking for pending FOTA job"); + ret = nrf_cloud_pending_fota_job_process(&pending_job, &reboot_required); + + if ((ret == 0) && reboot_required) { + /* Save validate status and reboot */ + (void)save_pending_job(); + + /* Sleep to display log message */ + LOG_INF("Rebooting in %us...", PENDING_REBOOT_S); + LOG_PANIC(); + k_sleep(K_SECONDS(PENDING_REBOOT_S)); + sys_reboot(SYS_REBOOT_COLD); + } +} + +int handle_fota_init(void) +{ + int err; + + LOG_INF("Loading FOTA settings..."); + err = settings_subsys_init(); + if (err) { + LOG_ERR("Failed to initialize settings subsystem, error: %d", err); + return err; + } + err = settings_load_subtree(settings_handler_coap_fota.name); + if (err) { + LOG_WRN("Failed to load settings, error: %d", err); + } + +#if defined(CONFIG_NRF_CLOUD_FOTA_FULL_MODEM_UPDATE) + struct dfu_target_fmfu_fdev fmfu_dev_inf = { + .size = 0, + .offset = 0, + /* CONFIG_DFU_TARGET_FULL_MODEM_USE_EXT_PARTITION is enabled, so no need + * to specify the flash device here + */ + .dev = NULL + }; + + err = nrf_cloud_fota_fmfu_dev_set(&fmfu_dev_inf); + if (err < 0) { + LOG_WRN("Full modem FOTA not initialized"); + return err; + } + + full_modem_fota_initd = true; +#endif + return err; +} + +int handle_fota_begin(void) +{ + int err; + + /* This function may perform a reboot if a FOTA update is in progress */ + process_pending_job(); + + err = fota_download_init(http_fota_dl_handler); + if (err) { + LOG_ERR("Failed to initialize FOTA download, error: %d", err); + return err; + } + + return 0; +} + +static bool validate_in_progress_job(void) +{ + /* Update in progress job status if validation has been done */ + if (pending_fota_job_exists()) { + + if (pending_job.validate == NRF_CLOUD_FOTA_VALIDATE_PASS) { + fota_status = NRF_CLOUD_FOTA_SUCCEEDED; + fota_status_details = FOTA_STATUS_DETAILS_SUCCESS; + } else if (pending_job.validate == NRF_CLOUD_FOTA_VALIDATE_FAIL) { + fota_status = NRF_CLOUD_FOTA_FAILED; + if (nrf_cloud_fota_is_type_modem(pending_job.type)) { + fota_status_details = FOTA_STATUS_DETAILS_MDM_ERR; + } else { + fota_status_details = FOTA_STATUS_DETAILS_MCU_ERR; + } + } else { + fota_status = NRF_CLOUD_FOTA_SUCCEEDED; + fota_status_details = FOTA_STATUS_DETAILS_NO_VALIDATE; + } + LOG_DBG("FOTA job completed"); + return true; + } + + LOG_DBG("No FOTA job to update"); + return false; +} + +static int check_for_job(void) +{ + int err; + + LOG_INF("Checking for FOTA job..."); + err = nrf_cloud_coap_fota_job_get(&job); + if (err == -ENOMSG) { + LOG_INF("No pending FOTA job"); + return 1; + } else if (err < 0) { + LOG_ERR("Failed to fetch FOTA job, error: %d", err); + return -ENOENT; + } else if (err > 0) { + return err; + } + + if (job.type == NRF_CLOUD_FOTA_TYPE__INVALID) { + LOG_INF("No pending FOTA job"); + return 1; + } + + LOG_INF("FOTA Job: %s, type: %d\n", job.id ? job.id : "", job.type); + return 0; +} + +static int update_job_status(void) +{ + int err; + bool is_job_pending = pending_fota_job_exists(); + + /* Send FOTA job status to nRF Cloud and, if successful, clear pending job in settings */ + LOG_INF("Updating FOTA job status..."); + err = nrf_cloud_coap_fota_job_update(is_job_pending ? pending_job.id : job.id, + fota_status, fota_status_details); + + pending_job.validate = NRF_CLOUD_FOTA_VALIDATE_NONE; + pending_job.type = NRF_CLOUD_FOTA_TYPE__INVALID; + pending_job.bl_flags = NRF_CLOUD_FOTA_BL_STATUS_CLEAR; + memset(pending_job.id, 0, NRF_CLOUD_FOTA_JOB_ID_SIZE); + + if (err) { + LOG_ERR("Failed to update FOTA job, error: %d", err); + } else { + LOG_INF("FOTA job updated, status: %d", fota_status); + + /* Clear the pending job in settings */ + if (is_job_pending) { + (void)save_pending_job(); + } + } + + return err; +} + +static int start_download(void) +{ + enum dfu_target_image_type img_type; + + /* Start the FOTA download, specifying the job/image type */ + switch (job.type) { + case NRF_CLOUD_FOTA_BOOTLOADER: + case NRF_CLOUD_FOTA_APPLICATION: + img_type = DFU_TARGET_IMAGE_TYPE_MCUBOOT; + break; + case NRF_CLOUD_FOTA_MODEM_DELTA: + img_type = DFU_TARGET_IMAGE_TYPE_MODEM_DELTA; + break; + case NRF_CLOUD_FOTA_MODEM_FULL: + img_type = DFU_TARGET_IMAGE_TYPE_FULL_MODEM; + break; + default: + LOG_ERR("Unhandled FOTA type: %d", job.type); + return -EFTYPE; + } + + LOG_INF("Starting FOTA download of %s/%s", job.host, job.path); + int err = fota_download_start_with_image_type(job.host, job.path, + CONFIG_NRF_CLOUD_SEC_TAG, 0, FOTA_DL_FRAGMENT_SZ, + img_type); + + if (err != 0) { + LOG_ERR("Failed to start FOTA download, error: %d", err); + return -ENODEV; + } + + return 0; +} + +static int wait_for_download(void) +{ + LOG_INF("Waiting for FOTA download to complete"); + int err = k_sem_take(&fota_download_sem, + K_MINUTES(CONFIG_COAP_FOTA_DL_TIMEOUT_MIN)); + if (err == -EAGAIN) { + fota_download_cancel(); + return -ETIMEDOUT; + } else if (err != 0) { + LOG_ERR("k_sem_take error: %d", err); + return -ENOLCK; + } + + return 0; +} + +static void handle_download_succeeded_and_reboot(void) +{ + int err; + + /* Save the pending FOTA job info to flash */ + memcpy(pending_job.id, job.id, NRF_CLOUD_FOTA_JOB_ID_SIZE); + pending_job.type = job.type; + pending_job.validate = NRF_CLOUD_FOTA_VALIDATE_PENDING; + pending_job.bl_flags = NRF_CLOUD_FOTA_BL_STATUS_CLEAR; + + err = nrf_cloud_bootloader_fota_slot_set(&pending_job); + if (err) { + LOG_WRN("Failed to set B1 slot flag, BOOT FOTA validation may be incorrect"); + } + + (void)lte_lc_deinit(); + +#if defined(CONFIG_NRF_CLOUD_FOTA_FULL_MODEM_UPDATE) + if (job.type == NRF_CLOUD_FOTA_MODEM_FULL) { + LOG_INF("Applying full modem FOTA update..."); + err = nrf_cloud_fota_fmfu_apply(); + if (err) { + LOG_ERR("Failed to apply full modem FOTA update %d", err); + pending_job.validate = NRF_CLOUD_FOTA_VALIDATE_FAIL; + } else { + pending_job.validate = NRF_CLOUD_FOTA_VALIDATE_PASS; + } + } +#endif + + err = save_pending_job(); + if (err) { + LOG_WRN("FOTA job will be marked as successful without validation"); + fota_status_details = FOTA_STATUS_DETAILS_NO_VALIDATE; + (void)update_job_status(); + } + + LOG_INF("Rebooting in %us to complete FOTA update...", FOTA_REBOOT_S); + LOG_PANIC(); + k_sleep(K_SECONDS(FOTA_REBOOT_S)); + sys_reboot(SYS_REBOOT_COLD); +} + +static void cleanup(void) +{ + nrf_cloud_coap_fota_job_free(&job); +} + +static void wait_after_job_update(void) +{ + /* Job operations can take up to 30s to be processed */ + LOG_INF("Waiting %u seconds for job update to be processed by nRF Cloud...", JOB_WAIT_S); + cleanup(); + k_sleep(K_SECONDS(JOB_WAIT_S)); +} + +static void error_reboot(void) +{ + LOG_INF("Rebooting due to error in %us...", ERROR_REBOOT_S); + (void)lte_lc_deinit(); + LOG_PANIC(); + k_sleep(K_SECONDS(ERROR_REBOOT_S)); + sys_reboot(SYS_REBOOT_COLD); +} + +int handle_fota_process(void) +{ + int err; + + /* If a FOTA job is in progress, handle it first */ + if (validate_in_progress_job()) { + err = update_job_status(); + if (err) { + LOG_ERR("Error; must reboot: %d", err); + error_reboot(); + } + wait_after_job_update(); + return -ENOENT; + } + + /* Check for a new FOTA job */ + err = check_for_job(); + if (err < 0) { + return err; + } + + if (err > 0) { + /* No job. Wait for the configured duration or a button press */ + cleanup(); + +#if defined(COAP_FOTA_USE_THREAD) + LOG_DBG("Retrying in %d minute(s)", CONFIG_COAP_FOTA_JOB_CHECK_RATE_MIN); +#endif + return -ENOENT; + } + + /* Start the FOTA download process and wait for completion (or timeout) */ + err = start_download(); + if (err) { + return err; + } + err = wait_for_download(); + if (err == -ETIMEDOUT) { + LOG_ERR("Timeout; FOTA download took longer than %d minutes", + CONFIG_COAP_FOTA_DL_TIMEOUT_MIN); + fota_status = NRF_CLOUD_FOTA_TIMED_OUT; + fota_status_details = FOTA_STATUS_DETAILS_TIMEOUT; + } + + /* On download success, save job info and reboot to complete installation. + * Job status will be sent to nRF Cloud after reboot and validation. + */ + if (fota_status == NRF_CLOUD_FOTA_SUCCEEDED) { + LOG_INF("FOTA download succeeded"); + handle_download_succeeded_and_reboot(); + } + + /* Job was not successful, send status to nRF Cloud */ + err = update_job_status(); + if (err) { + LOG_ERR("Error updating job status: %d", err); + } + + wait_after_job_update(); + + return err; +} + +#if defined(COAP_FOTA_USE_THREAD) + +#define FOTA_THREAD_DELAY_S 5 + +static int fota_thread(void) +{ + int err; + + while (1) { + err = coap_fota_handle(); + if (err == -EAGAIN) { + k_sleep(K_MINUTES(CONFIG_COAP_FOTA_JOB_CHECK_RATE_MIN)); + } else { + k_sleep(K_SECONDS(FOTA_THREAD_DELAY_S)); + } + } + return 0; +} + +K_THREAD_DEFINE(coap_fota, CONFIG_COAP_FOTA_THREAD_STACK_SIZE, fota_thread, + NULL, NULL, NULL, 0, 0, 0); + +#endif /* COAP_FOTA_USE_THREAD */ diff --git a/samples/cellular/nrf_cloud_coap_client/src/handle_fota.h b/samples/cellular/nrf_cloud_coap_client/src/handle_fota.h new file mode 100644 index 000000000000..dde869e91b2e --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/src/handle_fota.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef HANDLE_FOTA_H +#define HANDLE_FOTA_H + +int handle_fota_init(void); +int handle_fota_begin(void); +int handle_fota_process(void); + +#endif /* HANDLE_FOTA_H */ diff --git a/samples/cellular/nrf_cloud_coap_client/src/main.c b/samples/cellular/nrf_cloud_coap_client/src/main.c new file mode 100644 index 000000000000..e44e8965e094 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/src/main.c @@ -0,0 +1,573 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * 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 "handle_fota.h" + +#include +LOG_MODULE_REGISTER(nrf_cloud_coap_client, CONFIG_NRF_CLOUD_COAP_CLIENT_LOG_LEVEL); + +#define SAMPLE_SIGNON_FMT "nRF Cloud CoAP Client Sample, version: %s" + +#define CREDS_REQ_WAIT_SEC 10 +#define BTN_NUM 1 +#define APP_COAP_SEND_INTERVAL_MS 20000 +#define APP_COAP_INTERVAL_LIMIT 60 + +/* Uncomment to incrementally increase time between coap packets */ +/* #define DELAY_INTERPACKET_PERIOD */ + +static bool connected; + +/* Modem info struct used for modem FW version and cell info used for single-cell requests */ +#if defined(CONFIG_MODEM_INFO) +static struct modem_param_info mdm_param; +#endif + +/* Current RRC mode */ +static enum lte_lc_rrc_mode cur_rrc_mode = LTE_LC_RRC_MODE_IDLE; + +static volatile bool button_pressed; + +static struct nrf_cloud_gnss_data gnss; + +static K_SEM_DEFINE(location_event, 0, 1); + +static int update_shadow(void); +static void button_handler(uint32_t button_states, uint32_t has_changed); + +static void check_modem_fw_version(void) +{ + char mfwv_str[128]; + +#if defined(CONFIG_MODEM_INFO) + if (modem_info_string_get(MODEM_INFO_FW_VERSION, mfwv_str, sizeof(mfwv_str)) <= 0) { + LOG_WRN("Failed to get modem FW version"); + return; + } +#else + strncpy(mfwv_str, "1.3.0", sizeof(mfwv_str)); +#endif + LOG_INF("Modem FW version: %s", mfwv_str); +} + +#if defined(CONFIG_NRF_MODEM_LIB) +/**@brief Recoverable modem library error. */ +void nrf_modem_recoverable_error_handler(uint32_t err) +{ + LOG_ERR("Modem library recoverable error: %u", (unsigned int)err); +} +#endif /* defined(CONFIG_NRF_MODEM_LIB) */ + +K_SEM_DEFINE(lte_ready, 0, 1); + +static void lte_handler(const struct lte_lc_evt *const evt) +{ + switch (evt->type) { + case LTE_LC_EVT_NW_REG_STATUS: + if ((evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME) || + (evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_ROAMING)) { + LOG_DBG("Connected to LTE network"); + k_sem_give(<e_ready); + } else { + LOG_DBG("reg status %d", evt->nw_reg_status); + } + break; + + case LTE_LC_EVT_RRC_UPDATE: + cur_rrc_mode = evt->rrc_mode; + if (cur_rrc_mode == LTE_LC_RRC_MODE_IDLE) { + LOG_DBG("RRC mode: idle"); + } else { + LOG_DBG("RRC mode: connected"); + } + break; + + default: + LOG_DBG("LTE event %d (0x%x)", evt->type, evt->type); + break; + } +} + +static void location_event_handler(const struct location_event_data *event_data) +{ + switch (event_data->id) { + case LOCATION_EVT_LOCATION: + LOG_INF("Got location:"); + LOG_INF(" method: %s", location_method_str(event_data->method)); + LOG_INF(" latitude: %.06f", event_data->location.latitude); + LOG_INF(" longitude: %.06f", event_data->location.longitude); + LOG_INF(" accuracy: %.01f m", event_data->location.accuracy); + if (event_data->location.datetime.valid) { + LOG_INF(" date: %04d-%02d-%02d", + event_data->location.datetime.year, + event_data->location.datetime.month, + event_data->location.datetime.day); + LOG_INF(" time: %02d:%02d:%02d.%03d UTC", + event_data->location.datetime.hour, + event_data->location.datetime.minute, + event_data->location.datetime.second, + event_data->location.datetime.ms); + } + LOG_INF(" Google maps URL: https://maps.google.com/?q=%.06f,%.06f\n", + event_data->location.latitude, event_data->location.longitude); + break; + + case LOCATION_EVT_TIMEOUT: + LOG_INF("Getting location timed out"); + break; + + case LOCATION_EVT_ERROR: + LOG_INF("Getting location failed: num satellites: %d", + event_data->error.details.gnss.satellites_tracked); + break; + + case LOCATION_EVT_GNSS_ASSISTANCE_REQUEST: + LOG_INF("Getting location assistance requested (A-GPS). Not doing anything."); + break; + + case LOCATION_EVT_GNSS_PREDICTION_REQUEST: + LOG_INF("Getting location assistance requested (P-GPS). Not doing anything."); + break; + + default: + LOG_INF("Getting location: Unknown event: %d", event_data->id); + break; + } + + k_sem_give(&location_event); +} + +static void location_event_wait(void) +{ + k_sem_take(&location_event, K_FOREVER); +} + +/** + * @brief Retrieve location so that fallback is applied. + * + * @details This is achieved by setting GNSS as first priority method and giving it too short + * timeout. Then a fallback to next method, which is cellular in this example, occurs. + */ +static int location_cellular_get(void) +{ + int err; + struct location_config config; + enum location_method methods[] = {LOCATION_METHOD_CELLULAR}; + + location_config_defaults_set(&config, ARRAY_SIZE(methods), methods); + /* Default cellular configuration may be overridden here. */ + config.methods[1].cellular.timeout = 40 * MSEC_PER_SEC; + + err = location_request(&config); + if (err) { + printk("Requesting location failed, error: %d\n", err); + return err; + } + + location_event_wait(); + return 0; +} + +/** + * @brief Retrieve location with GNSS high accuracy. + */ +static int location_gnss_high_accuracy_get(void) +{ + int err; + struct location_config config; + enum location_method methods[] = {LOCATION_METHOD_GNSS}; + + location_config_defaults_set(&config, ARRAY_SIZE(methods), methods); + config.methods[0].gnss.accuracy = LOCATION_ACCURACY_HIGH; + + printk("Requesting high accuracy GNSS location...\n"); + + err = location_request(&config); + if (err) { + printk("Requesting location failed, error: %d\n", err); + return err; + } + + location_event_wait(); + return 0; +} + +#if defined(CONFIG_LOCATION_METHOD_WIFI) +/** + * @brief Retrieve location with Wi-Fi positioning as first priority, GNSS as second + * and cellular as third. + */ +static int location_wifi_get(void) +{ + int err; + struct location_config config; + enum location_method methods[] = { + LOCATION_METHOD_WIFI}; + + location_config_defaults_set(&config, ARRAY_SIZE(methods), methods); + + printk("Requesting Wi-Fi location...\n"); + + err = location_request(&config); + if (err) { + printk("Requesting location failed, error: %d\n", err); + return err; + } + + location_event_wait(); + return 0; +} +#endif + +/**@brief Configures modem to provide LTE link. Blocks until link is + * successfully established. + */ +static int modem_configure(void) +{ + int err; + + err = nrf_modem_lib_init(); + if (err < 0) { + LOG_ERR("Modem library initialization failed, error: %d", err); + return err; + } else if (err == NRF_MODEM_DFU_RESULT_OK) { + LOG_DBG("Modem library initialized after " + "successful modem firmware update."); + } else if (err > 0) { + LOG_ERR("Modem library initialized after " + "failed modem firmware update, error: %d", err); + return err; + } else { + LOG_DBG("Modem library initialized."); + } + + lte_lc_register_handler(lte_handler); + LOG_INF("LTE Link Connecting ..."); + err = lte_lc_init_and_connect(); + __ASSERT(err == 0, "LTE link could not be established."); + k_sem_take(<e_ready, K_FOREVER); + LOG_INF("LTE Link Connected"); + err = lte_lc_psm_req(true); + if (err) { + LOG_ERR("Unable to enter PSM mode: %d", err); + } + + err = nrf_modem_at_printf("AT+CEREG=5"); + if (err) { + LOG_ERR("Can't subscribe to +CEREG events."); + } + + /* Modem info library is used to obtain the modem FW version + * and network info for single-cell requests + */ +#if defined(CONFIG_MODEM_INFO) + err = modem_info_init(); + if (err) { + LOG_ERR("Modem info initialization failed, error: %d", err); + return err; + } + + err = modem_info_params_init(&mdm_param); + if (err) { + LOG_ERR("Modem info params initialization failed, error: %d", err); + return err; + } + + err = modem_info_params_get(&mdm_param); + if (err) { + LOG_ERR("Modem info params reading failed, error: %d", err); + } +#endif + + /* Check modem FW version */ + check_modem_fw_version(); + + return err; +} + +int init(void) +{ + int err; + + err = handle_fota_init(); + if (err) { + LOG_ERR("Error initializing FOTA: %d", err); + return err; + } + + err = modem_configure(); + if (err) { + return err; + } + + err = handle_fota_begin(); + if (err) { + return err; + } + + err = nrf_cloud_coap_init(); + if (err) { + LOG_ERR("Failed to initialize CoAP client: %d", err); + return err; + } + + err = nrf_cloud_coap_connect(); + if (err) { + LOG_ERR("Failed to connect and get authorized: %d", err); + return err; + } + connected = true; + + err = update_shadow(); + if (err) { + LOG_ERR("Error updating shadow: %d", err); + return err; + } + LOG_INF("Shadow updated"); + + err = location_init(location_event_handler); + if (err) { + LOG_ERR("Error initializing location library: %d", err); + } + LOG_INF("Location library initialized"); + + /* Init the button */ + err = dk_buttons_init(button_handler); + if (err) { + LOG_ERR("Failed to initialize button: error: %d", err); + } + + return err; +} + +static int update_shadow(void) +{ + /* Enable FOTA for modem and application */ + struct nrf_cloud_svc_info_fota fota = { + .modem = 1, + .application = 1 + }; + struct nrf_cloud_svc_info_ui ui_info = { + .gnss = true, + .temperature = true + }; + struct nrf_cloud_svc_info service_info = { + .fota = &fota, + .ui = &ui_info + }; + struct nrf_cloud_modem_info modem_info = { + .device = NRF_CLOUD_INFO_SET, + .network = NRF_CLOUD_INFO_SET, + .sim = IS_ENABLED(CONFIG_MODEM_INFO_ADD_SIM) ? NRF_CLOUD_INFO_SET : 0, + /* Use the modem info already obtained */ +#if defined(CONFIG_MODEM_INFO) + .mpi = &mdm_param, +#endif + /* Include the application version */ + .application_version = CONFIG_NRF_CLOUD_COAP_CLIENT_SAMPLE_VERSION + }; + struct nrf_cloud_device_status device_status = { + .modem = &modem_info, + .svc = &service_info + }; + + return nrf_cloud_coap_shadow_device_status_update(&device_status); +} + +static int do_next_test(void) +{ + static double temp = 21.5; + static int cur_test = 1; + int err = 0; + char buf[512]; + + if (!gnss.type) { + gnss.type = NRF_CLOUD_GNSS_TYPE_PVT; + gnss.pvt.lat = 45.525616; + gnss.pvt.lon = -122.685978; + gnss.pvt.accuracy = 30; + } + + LOG_INF("\n***********************************************"); + switch (cur_test) { + case 1: + LOG_INF("**** %d. Getting pending FOTA job execution ****\n", cur_test); + err = handle_fota_process(); + if (err != -EAGAIN) { + LOG_INF("FOTA check completed."); + } + break; + case 2: + LOG_INF("*** %d. Getting shadow delta *******************\n", cur_test); + buf[0] = '\0'; + err = nrf_cloud_coap_shadow_get(buf, sizeof(buf), true); + if (err) { + LOG_ERR("Failed to request shadow delta: %d", err); + } else { + size_t len = strlen(buf); + + LOG_INF("Delta: %s", len ? buf : "None"); + if (len) { + err = nrf_cloud_coap_shadow_state_update(buf); + if (err) { + LOG_ERR("Failed to acknowledge delta: %d", err); + } else { + LOG_INF("Delta acknowledged"); + } + } + } + break; + case 3: + LOG_INF("*** %d. Sending temperature ********************\n", cur_test); + err = nrf_cloud_coap_sensor_send(NRF_CLOUD_JSON_APPID_VAL_TEMP, temp, + NRF_CLOUD_NO_TIMESTAMP); + if (err) { + LOG_ERR("Error sending sensor data: %d", err); + break; + } + LOG_INF("Sent %.1f C", temp); + temp += 0.1; + break; + case 4: + LOG_INF("*** %d. Getting Cellular Location ****************\n", cur_test); + err = location_cellular_get(); + break; + case 5: +#if defined(CONFIG_LOCATION_METHOD_WIFI) + LOG_INF("*** %d. Getting Wi-Fi Location *******************\n", cur_test); + k_sleep(K_SECONDS(3)); + err = location_wifi_get(); + break; +#else + cur_test++; +#endif + case 6: + LOG_INF("*** %d. Getting GNSS Location ******************\n", cur_test); + err = location_gnss_high_accuracy_get(); + break; + case 7: + LOG_INF("*** %d. Sending GNSS PVT ***********************\n", cur_test); + err = nrf_cloud_coap_location_send(&gnss); + if (err) { + LOG_ERR("Error sending GNSS PVT data: %d", err); + } else { + LOG_INF("PVT sent"); + } + break; + } + + if (++cur_test > 7) { + cur_test = 1; + } + return err; +} + +static void button_handler(uint32_t button_states, uint32_t has_changed) +{ + if (has_changed & button_states & BIT(BTN_NUM - 1)) { + LOG_INF("Button %d pressed", BTN_NUM); + button_pressed = true; + } +} + +int main(void) +{ + int64_t next_msg_time; + int delta_ms = APP_COAP_SEND_INTERVAL_MS; + int err; + int i = 1; + + LOG_INF(SAMPLE_SIGNON_FMT, + CONFIG_NRF_CLOUD_COAP_CLIENT_SAMPLE_VERSION); + + err = init(); + if (err) { + LOG_ERR("Initialization failed. Stopping."); + return 0; + } + + next_msg_time = k_uptime_get() + delta_ms; + + while (1) { + if (k_uptime_get() >= next_msg_time) { + + if (!connected) { + LOG_INF("Going online"); + err = lte_lc_normal(); + if (err) { + LOG_ERR("Error going online: %d", err); + } else { + k_sem_take(<e_ready, K_FOREVER); + err = nrf_cloud_coap_connect(); + if (err) { + LOG_ERR("Failed to connect and get authorized: %d", + err); + next_msg_time += delta_ms; + continue; + } + connected = true; + } + } + + if (connected) { + err = do_next_test(); + + if ((err == -EAGAIN) || (err == -EFAULT) || button_pressed) { + connected = false; + if (button_pressed) { + LOG_INF("Disconnecting due to button press..."); + button_pressed = false; + } + LOG_INF("Disconnecting CoAP"); + err = nrf_cloud_coap_disconnect(); + if (err) { + LOG_ERR("Error closing socket: %d", err); + } else { + LOG_INF("Socket closed."); + } + LOG_INF("LTE going offline"); + err = lte_lc_offline(); + if (err) { + LOG_ERR("Error going offline: %d", err); + } else { + LOG_INF("Offline."); + } + k_sleep(K_SECONDS(5)); + } + } + + delta_ms = APP_COAP_SEND_INTERVAL_MS * i; + next_msg_time += delta_ms; +#if defined(DELAY_INTERPACKET_PERIOD) + LOG_INF("Next transfer in %d minutes, %d seconds", + delta_ms / 60000, (delta_ms / 1000) % 60); + if (++i > APP_COAP_INTERVAL_LIMIT) { + i = APP_COAP_INTERVAL_LIMIT; + } +#endif + } + k_sleep(K_MSEC(100)); + } + return err; +} diff --git a/samples/cellular/nrf_cloud_coap_client/src/scan_wifi.c b/samples/cellular/nrf_cloud_coap_client/src/scan_wifi.c new file mode 100644 index 000000000000..fd109afbfdc3 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/src/scan_wifi.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +/* #include */ + +#include +/* #include "location_core.h" */ +/* #include "location_utils.h" */ +/* #include "cloud_service/cloud_service.h" */ + +LOG_MODULE_REGISTER(scan_wifi, CONFIG_NRF_CLOUD_COAP_CLIENT_LOG_LEVEL); + +#define WIFI_SCANNING_RESULTS_MAX_CNT CONFIG_NRF700X_SCAN_LIMIT + +static struct net_if *wifi_iface; +static struct net_mgmt_event_callback scan_wifi_net_mgmt_cb; + +static struct wifi_scan_result scan_results[WIFI_SCANNING_RESULTS_MAX_CNT]; +static struct wifi_scan_info scan_wifi_info = { + .ap_info = scan_results, +}; +static struct k_sem *scan_wifi_ready; + +struct wifi_scan_info *scan_wifi_results_get(void) +{ + if (scan_wifi_info.cnt <= 1) { + if (scan_wifi_info.cnt == 1) { + /* Following statement seems to be true at least with HERE + * (400: bad request). + */ + LOG_WRN("Retrieving a location based on a single Wi-Fi " + "access point is not possible, using only cellular data"); + } else { + LOG_WRN("No Wi-Fi scanning results, using only cellular data"); + } + return NULL; + } + + return &scan_wifi_info; +} + +int scan_wifi_start(struct k_sem *wifi_scan_ready) +{ + int ret; + + scan_wifi_ready = wifi_scan_ready; + + LOG_INF("Triggering start of Wi-Fi scanning"); + + scan_wifi_info.cnt = 0; + + __ASSERT_NO_MSG(wifi_iface != NULL); + ret = net_mgmt(NET_REQUEST_WIFI_SCAN, wifi_iface, NULL, 0); + if (ret) { + LOG_ERR("Failed to initiate Wi-Fi scanning: %d", ret); + ret = -EFAULT; + k_sem_give(wifi_scan_ready); + wifi_scan_ready = NULL; + } + return ret; +} + +static void scan_wifi_result_handle(struct net_mgmt_event_callback *cb) +{ + const struct wifi_scan_result *entry = (const struct wifi_scan_result *)cb->info; + struct wifi_scan_result *current; + + if (scan_wifi_info.cnt < WIFI_SCANNING_RESULTS_MAX_CNT) { + current = &scan_wifi_info.ap_info[scan_wifi_info.cnt]; + *current = *entry; + scan_wifi_info.cnt++; + + LOG_INF("scan result #%d stored: ssid %s, channel %d," + " mac %02x:%02x:%02x:%02x:%02x:%02x", + scan_wifi_info.cnt, + current->ssid, + current->channel, + current->mac[0], current->mac[1], current->mac[2], + current->mac[3], current->mac[4], current->mac[5]); + } else { + LOG_WRN("Scanning result (mac %02x:%02x:%02x:%02x:%02x:%02x) " + "did not fit to result buffer - dropping it", + entry->mac[0], entry->mac[1], entry->mac[2], + entry->mac[3], entry->mac[4], entry->mac[5]); + } +} + +static void scan_wifi_done_handle(struct net_mgmt_event_callback *cb) +{ + const struct wifi_status *status = (const struct wifi_status *)cb->info; + + if (status->status) { + LOG_WRN("Wi-Fi scan request failed (%d)", status->status); + } else { + LOG_INF("Scan request done with %d Wi-Fi APs", scan_wifi_info.cnt); + } + + k_sem_give(scan_wifi_ready); + scan_wifi_ready = NULL; +} + +void scan_wifi_net_mgmt_event_handler( + struct net_mgmt_event_callback *cb, + uint32_t mgmt_event, + struct net_if *iface) +{ + ARG_UNUSED(iface); + + if (scan_wifi_ready != NULL) { + switch (mgmt_event) { + case NET_EVENT_WIFI_SCAN_RESULT: + scan_wifi_result_handle(cb); + break; + case NET_EVENT_WIFI_SCAN_DONE: + scan_wifi_done_handle(cb); + break; + default: + break; + } + } +} + +int scan_wifi_cancel(void) +{ + if (scan_wifi_ready != NULL) { + k_sem_reset(scan_wifi_ready); + scan_wifi_ready = NULL; + } + return 0; +} + +int scan_wifi_init(void) +{ + const struct device *wifi_dev; + + wifi_iface = NULL; +#if defined(CONFIG_WIFI_NRF700X) + wifi_dev = device_get_binding("wlan0"); +#else + wifi_dev = DEVICE_DT_GET(DT_CHOSEN(ncs_location_wifi)); +#endif + if (!device_is_ready(wifi_dev)) { + LOG_ERR("Wi-Fi device %s not ready", wifi_dev->name); + return -ENODEV; + } + + wifi_iface = net_if_lookup_by_dev(wifi_dev); + if (wifi_iface == NULL) { + LOG_ERR("Could not get the Wi-Fi net interface"); + return -EFAULT; + } + + net_mgmt_init_event_callback(&scan_wifi_net_mgmt_cb, scan_wifi_net_mgmt_event_handler, + (NET_EVENT_WIFI_SCAN_RESULT | NET_EVENT_WIFI_SCAN_DONE)); + net_mgmt_add_event_callback(&scan_wifi_net_mgmt_cb); + + return 0; +} diff --git a/samples/cellular/nrf_cloud_coap_client/src/scan_wifi.h b/samples/cellular/nrf_cloud_coap_client/src/scan_wifi.h new file mode 100644 index 000000000000..1b133a9de124 --- /dev/null +++ b/samples/cellular/nrf_cloud_coap_client/src/scan_wifi.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef SCAN_WIFI_H +#define SCAN_WIFI_H + +#include + +int scan_wifi_init(void); +int scan_wifi_start(struct k_sem *wifi_scan_ready); +struct wifi_scan_info *scan_wifi_results_get(void); +int scan_wifi_cancel(void); + +#endif /* SCAN_WIFI_H */