diff --git a/samples/cellular/nrf_cloud_ble_gateway/.gitignore b/samples/cellular/nrf_cloud_ble_gateway/.gitignore new file mode 100644 index 000000000000..b2290143a47b --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/.gitignore @@ -0,0 +1 @@ +certs diff --git a/samples/cellular/nrf_cloud_ble_gateway/CMakeLists.txt b/samples/cellular/nrf_cloud_ble_gateway/CMakeLists.txt new file mode 100644 index 000000000000..f85d5b079c15 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/CMakeLists.txt @@ -0,0 +1,67 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +set(BOARD_ROOT ${CMAKE_CURRENT_LIST_DIR}) + +set(spm_CONF_FILE + prj.conf + ${CMAKE_CURRENT_SOURCE_DIR}/${BOARD}_spm.conf) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(nrf_cloud_ble_gateway) +zephyr_compile_definitions(PROJECT_NAME=${PROJECT_NAME}) + +# Include certs files if enabled. +zephyr_include_directories_ifdef(CONFIG_NRF_CLOUD_PROVISION_CERTIFICATES certs) + +# NORDIC SDK APP START +target_sources(app PRIVATE src/main.c) +target_sources(app PRIVATE src/application.c) +target_sources(app PRIVATE src/cloud_connection.c) +target_sources(app PRIVATE src/message_queue.c) +target_sources(app PRIVATE src/temperature.c) +target_sources(app PRIVATE src/fota_support.c) +target_sources(app PRIVATE src/led_control.c) +target_sources(app PRIVATE src/sample_reboot.c) +target_sources(app PRIVATE src/shadow_config.c) + +if(CONFIG_LOCATION_TRACKING) +target_sources(app PRIVATE src/location_tracking.c) +endif() + +if(CONFIG_COAP_SHADOW) +target_sources(app PRIVATE src/shadow_support_coap.c) +endif() + +if(CONFIG_COAP_FOTA) +target_sources(app PRIVATE src/fota_support_coap.c) +endif() + +if(CONFIG_NRF_PROVISIONING) +target_sources(app PRIVATE src/provisioning_support.c) +endif() + +# Include modem-specific features if supported +if(CONFIG_NRF_MODEM_LIB) + target_sources(app PRIVATE src/at_commands.c) +endif() + +if (CONFIG_NRF_CLOUD_GATEWAY) + target_sources(app PRIVATE src/ble/ble.c) + target_sources(app PRIVATE src/ble/ble_codec.c) + target_sources(app PRIVATE src/ble/ble_conn_mgr.c) + target_sources(app PRIVATE src/ble/gateway.c) + target_sources_ifdef(CONFIG_SHELL app PRIVATE src/ble/cli.c) + target_sources_ifdef(CONFIG_GATEWAY_BLE_FOTA app PRIVATE src/ble/dfu/peripheral_dfu.c) + target_sources_ifdef(CONFIG_FLASH_TEST, app PRIVATE src/ble/flash/flash_test.c) + zephyr_include_directories(src src/ble src/ble/flash src/ble/dfu) + #add_subdirectory(src/ble/dfu) + add_subdirectory(src/ble/flash) +endif() + +# NORDIC SDK APP END diff --git a/samples/cellular/nrf_cloud_ble_gateway/Kconfig b/samples/cellular/nrf_cloud_ble_gateway/Kconfig new file mode 100644 index 000000000000..0216b29db5c6 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/Kconfig @@ -0,0 +1,453 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "nRF Cloud BLE Gateway Sample Settings" + +config APP_VERSION + string "nRF Cloud BLE Gateway Sample version" + default "1.0.0" + +config USE_BT_HCI_SETUP + bool "Use BT HCI setup" + default y + select BT_HCI_SETUP + help + Get the setup to run. + +config CLOUD_CONNECTION_RETRY_TIMEOUT_SECONDS + int "Cloud connection retry timeout (seconds)" + default 30 + help + If connecting to nRF Cloud takes longer than this timeout, it will be + reattempted. + +config CLOUD_READY_TIMEOUT_SECONDS + int "Cloud readiness timeout (seconds)" + default 600 + help + 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 + int "Modem date and time establishment timeout (seconds)" + default 300 + help + The sample will wait this many seconds for the modem to determine the + current date and time before giving up and moving on. + +config APPLICATION_THREAD_STACK_SIZE + int "Application Thread Stack Size (bytes)" + default 2048 + help + Sets the stack size (in bytes) for the application thread of the + sample. + +config CONNECTION_THREAD_STACK_SIZE + int "Connection Thread Stack Size (bytes)" + default 2048 + help + Sets the stack size (in bytes) for the connection thread of the + sample. + +config MESSAGE_THREAD_STACK_SIZE + int "Message Queue Thread Stack Size (bytes)" + default 2048 + help + Sets the stack size (in bytes) for the message queue processing + thread of the sample. + +config LED_THREAD_STACK_SIZE + int "LED Thread Stack Size (bytes)" + default 1024 + help + Sets the stack size (in bytes) for the LED thread. + +config MAX_OUTGOING_MESSAGES + int "Outgoing message maximum" + default 25 + help + Sets the maximum number of device messages which may be enqueued + before further messages are dropped. + Increasing this value may require an increase to CONFIG_HEAP_MEM_POOL_SIZE. + +config MAX_CONSECUTIVE_SEND_FAILURES + int "Max outgoing consecutive send failures" + default 5 + help + 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 + int "Cooldown after max consecutive send failures exceeded" + default 10 + help + If a connection reset is triggered by too many failed device + messages, the sample will wait this long (in seconds) before trying + again. + +config SENSOR_SAMPLE_INTERVAL_SECONDS + int "Sensor sampling interval (seconds)" + default 60 + help + Sets the time to wait between each sensor sample. + +menuconfig LOCATION_TRACKING + bool "Location tracking service" + default y + help + Uses GNSS, LTE, and/or Wi-Fi information to determine and report device + location. + +if LOCATION_TRACKING + +config LOCATION_TRACKING_SAMPLE_INTERVAL_SECONDS + int "Location sampling interval (seconds)" + default 60 + help + Sets the location sampling interval in seconds. + +config LOCATION_TRACKING_GNSS + bool "Use GNSS method for location tracking" + default y + depends on LOCATION_METHOD_GNSS + help + Disable all location tracking methods to completely disable location tracking. + +config LOCATION_TRACKING_CELLULAR + bool "Use cellular method for location tracking" + default y + depends on LOCATION_METHOD_CELLULAR + help + Disable all location tracking methods to completely disable location tracking. + +config LOCATION_TRACKING_WIFI + bool "Use Wi-Fi method for location tracking" + default n + depends on LOCATION_METHOD_WIFI + help + Requires the use of an nRF7002 companion chip. + Disable all location tracking methods to completely disable location tracking. + +endif + +config TEMP_DATA_USE_SENSOR + bool "Use genuine temperature data" + depends on BOARD_THINGY91_NRF9160_NS || BOARD_THINGY91X_NRF9151_NS + default y if BOARD_THINGY91_NRF9160_NS || BOARD_THINGY91X_NRF9151_NS + select SENSOR + select BME680 + help + Sets whether to take genuine temperature measurements from a + connected BME680 sensor, or just simulate sensor data. + +config TEMP_TRACKING + bool "Track temperature" + default y + select NRF_CLOUD_ENABLE_SVC_INF_UI_TEMP + help + Sets whether to take temperature measurements. + +config TEMP_ALERT_LIMIT + int "Temperature limit to send alert" + default 30 + help + Sets the temperature in degrees C over which a temperature alert will + be sent to the cloud. + +choice + prompt "LED indication" + default LED_INDICATION_PWM if BOARD_THINGY91_NRF9160_NS || BOARD_THINGY91X_NRF9151_NS + default LED_INDICATION_GPIO if BOARD_NRF9160DK_NRF9160_NS || BOARD_NRF9161DK_NRF9161_NS || $(dt_compat_enabled,gpio-leds) + default LED_INDICATION_DISABLED + + config LED_INDICATION_PWM + select LED + select LED_PWM + select PWM + bool "Enable LED indication using the pwm-leds driver" + + config LED_INDICATION_GPIO + select LED + select LED_GPIO + bool "Enable LED indication using the gpio-leds driver" + + config LED_INDICATION_DISABLED + bool "Disable LED indication" +endchoice + +choice + prompt "LED indication LED type" + depends on !LED_INDICATION_DISABLED + default LED_INDICATOR_RGB if (BOARD_THINGY91_NRF9160_NS || BOARD_THINGY91X_NRF9151_NS) && LED_INDICATION_PWM + default LED_INDICATOR_4LED if !LED_INDICATION_DISABLED + default LED_INDICATOR_NONE + + config LED_INDICATOR_RGB + depends on LED_INDICATION_PWM + bool "A single RGB LED" + + config LED_INDICATOR_4LED + depends on !LED_INDICATION_DISABLED + bool "Four binary LEDs" + + config LED_INDICATOR_NONE + depends on LED_INDICATION_DISABLED + bool "No indicator LEDs" + +endchoice + +config LED_VERBOSE_INDICATION + depends on !LED_INDICATION_DISABLED + bool "Enables extra LED status indications" + default y + +config LED_CONTINUOUS_INDICATION + depends on !LED_INDICATION_DISABLED + bool "Show an idle pattern instead of turning LEDs off" + default y + +config GNSS_FIX_TIMEOUT_SECONDS + int "GNSS Fix timeout" + default 40 + help + On each location sample, try for this long to achieve a GNSS fix + before falling back to cellular positioning. + Set this to 20 or so seconds below the sensor sample interval so + that there is time left over to perform cellular positioning. + Otherwise, location samples may occur at a longer interval than + requested. + +config TEST_COUNTER + bool "Sets whether the test counter is enabled" + help + When enabled, the test counter configuration setting in the shadow is ignored. + +config TEST_COUNTER_MULTIPLIER + int "The number of test counter messages sent on each update" + default 1 + range 1 1000 + help + This is a way to increase the number of device messages sent by the sample. + It is useful for load testing. + +config AT_CMD_REQUESTS + depends on !NRF_CLOUD_COAP + bool "Enables remote execution of AT commands using device messages" + default y + +config AT_CMD_REQUEST_RESPONSE_BUFFER_LENGTH + int "Max length (in bytes) for Modem responses during AT command requests" + help + Responses to modem AT commands are stored in a buffer of this length before being sent to + nRF Cloud. If a response from the modem exceeds this length, it will be dropped, and an + error code sent in its place (-NRF_E2BIG). Cannot be less than 40 bytes. + default 200 + +config SEND_ONLINE_ALERT + bool "Sends a routine ALERT_TYPE_DEVICE_NOW_ONLINE on startup" + help + Enable this to demonstrate the alert feature of nRF Cloud. Reception of this alert + indicates the device has rebooted. + +config POST_PROVISIONING_INTERVAL_M + int "Delay in minutes between provisioning checks once connected" + default 30 + help + Use a slower rate to check for provisioning after we have successfully connected. + Until then use CONFIG_NRF_PROVISIONING_INTERVAL_S. + +if NRF_CLOUD_COAP + +menuconfig COAP_FOTA + bool "FOTA service when using CoAP to connect to nRF Cloud" + select FOTA_DOWNLOAD + select FOTA_DOWNLOAD_PROGRESS_EVT + select IMG_ERASE_PROGRESSIVELY + select DFU_TARGET + select DOWNLOADER + select REBOOT + select CJSON_LIB + select SETTINGS + default y + +if COAP_FOTA + +config COAP_FOTA_JOB_CHECK_RATE_MINUTES + int "FOTA job check interval (minutes)" + default 60 + help + Sets the time to wait between each request to get any pending FOTA job. + +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. + +endif + +menuconfig COAP_SHADOW + bool "Updating shadow service" + default y + help + Updates the shadow with device information. Checks for a shadow + delta periodically and processes it if received. + +if COAP_SHADOW + +config COAP_SHADOW_CHECK_RATE_SECONDS + int "Shadow job check interval (seconds)" + default 300 + help + Sets the time to wait between each request to get any change to the + device shadow. + +config COAP_SHADOW_THREAD_STACK_SIZE + int "CoAP Shadow Thread Stack Size (bytes)" + default 3072 + help + Sets the stack size (in bytes) for the CoAP shadow thread of the + sample. + +endif + +config COAP_SEND_CONFIRMABLE + bool "Send device messages as confirmable" + help + Ensures all CoAP transfers, even for often less important messages such + as sensor data, are sent as confirmable messages. + +endif + +endmenu + +config APPLICATION_WORKQUEUE_STACK_SIZE + int "Application workqueue stack size" + default 4096 + +config APPLICATION_WORKQUEUE_PRIORITY + int "Application workqueue priority" + default SYSTEM_WORKQUEUE_PRIORITY + +config FLASH_TEST + bool "Enable Flash Test module" + default n + select NRFX_SPIM if FLASH_TEST + select NRFX_SPIM3 if FLASH_TEST + select SPI if FLASH_TEST + select SPI_NOR if FLASH_TEST + help + Enable to test Erase, Write, and Read of external flash. + +config CLOUD_BUTTON + bool "Enable button sensor" + default y + +config CLOUD_BUTTON_INPUT + int "Set button sensor button number" + range 1 4 if BOARD_NRF9160_PCA10090NS + range 1 1 if BOARD_NRF9160_PCA20035NS + default 1 + +config ENTER_52840_MCUBOOT_VIA_BUTTON + bool "Button enables 52840 MCUboot" + depends on BOARD_APRICITY_GATEWAY_NRF9160 || BOARD_APRICITY_GATEWAY_NRF9160_NS + help + Holding a button during and after startup will place the + nrf52840 into MCUboot mode so the firmware can be updated. + +config SHELL_PROMPT_SECURE + string "Displayed prompt name" + default "uart:~# " + help + Displayed prompt name for UART backend once user has logged in. + +config SHELL_DEFAULT_PASSWORD + string "Factory-set initial password" + default "nordic" + help + User needs to type this to enter the shell and execute + commands. + +config GATEWAY_BLE_FOTA + bool "Enable BLE FOTA support" + default y + help + Enable ability to receive FOTA jobs for specific BLE devices, + then inject data from one or more file downloads to a BLE + device that supports a compatible DFU protocol. + +config GATEWAY_DBG_CMDS + bool "Enable debugging commands" + default y + select CPU_LOAD + select CPU_LOAD_CMDS + select KERNEL_SHELL + +config GATEWAY_SHELL + bool "Enable shell for administering nRF Cloud gateway" + default y + select SHELL + select SHELL_CMDS + select SHELL_HISTORY + select SHELL_METAKEYS + select SHELL_VT100_COLORS + select SHELL_BACKEND_SERIAL + select SHELL_CMDS_SELECT + +config STARTING_LOG_OVERRIDE + bool "Different logging level at boot" + default n + help + All configured logging will occur if disabled. Otherwise, + logging changed to STARTING_LOG_LEVEL until user logs in and + manually enables with 'log enable inf' (or whatever level is + desired). + +choice + prompt "Max compiled-in log level to boot with" + default STARTING_LOG_LEVEL_INF + depends on LOG + +config STARTING_LOG_LEVEL_OFF + bool "Off" + +config STARTING_LOG_LEVEL_ERR + bool "Error" + +config STARTING_LOG_LEVEL_WRN + bool "Warning" + +config STARTING_LOG_LEVEL_INF + bool "Info" + +config STARTING_LOG_LEVEL_DBG + bool "Debug" + +endchoice + +config STARTING_LOG_LEVEL + int + depends on LOG + default 0 if STARTING_LOG_LEVEL_OFF + default 1 if STARTING_LOG_LEVEL_ERR + default 2 if STARTING_LOG_LEVEL_WRN + default 3 if STARTING_LOG_LEVEL_INF + default 4 if STARTING_LOG_LEVEL_DBG + +if NRF_CLOUD_MQTT +module-str = MQTT BLE Gateway +endif +if NRF_CLOUD_COAP +module-str = CoAP BLE Gateway +endif +module = NRFCLOUD_BLE_GATEWAY +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +menu "Zephyr Kernel" +source "Kconfig.zephyr" +endmenu diff --git a/samples/cellular/nrf_cloud_ble_gateway/Kconfig.sysbuild b/samples/cellular/nrf_cloud_ble_gateway/Kconfig.sysbuild new file mode 100644 index 000000000000..106113037c37 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/Kconfig.sysbuild @@ -0,0 +1,14 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config WIFI_NRF70 + default y if BOARD_THINGY91X_NRF9151_NS + +choice WIFI_NRF70_OPER_MODES + default WIFI_NRF70_SCAN_ONLY if BOARD_THINGY91X_NRF9151_NS +endchoice + +source "${ZEPHYR_BASE}/share/sysbuild/Kconfig" diff --git a/samples/cellular/nrf_cloud_ble_gateway/README.rst b/samples/cellular/nrf_cloud_ble_gateway/README.rst new file mode 100644 index 000000000000..12a9526759e4 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/README.rst @@ -0,0 +1,1474 @@ +.. _nrf_cloud_ble_gateway: + +Cellular: nRF Cloud BLE gateway +############################### + +.. contents:: + :local: + :depth: 2 + +This sample is a minimal, error tolerant, integrated demonstration of the :ref:`lib_nrf_cloud`, :ref:`lib_location`, and :ref:`lib_at_host` libraries. +It demonstrates how you can integrate Firmware-Over-The-Air (FOTA), Location Services, Alert and Log Services, periodic sensor sampling, and more in your `nRF Cloud`_-enabled application. +It also demonstrates how to build connected, error-tolerant applications without worrying about physical-level specifics using Zephyr's ``conn_mgr``. + +.. _nrf_cloud_ble_gateway_requirements: + +Requirements +************ + +The sample supports the following development kits: + +.. table-from-sample-yaml:: + +.. include:: /includes/tfm.txt + +.. include:: /includes/external_flash_nrf91.txt + +For the nRF9160 DK, when using `CoAP`_, use modem firmware version 1.3.5 or above to benefit from the power savings provided by `DTLS Connection ID `_. + +.. _nrf_cloud_ble_gateway_features: + +Features +******** + +This sample implements or demonstrates the following features: + +* Generic, disconnect-tolerant integration of LTE using Zephyr's ``conn_mgr`` and :kconfig:option:`CONFIG_NRF_MODEM_LIB_NET_IF`. +* Error-tolerant use of the nRF Cloud CoAP API using the :ref:`lib_nrf_cloud_coap` library. +* Error-tolerant use of the `nRF Cloud MQTT API`_ using the :ref:`lib_nrf_cloud` library. +* Support for `Firmware-Over-The-Air (FOTA) update service `_ using the `nRF Cloud`_ portal. +* Support for modem AT commands over UART using the :ref:`lib_at_host` library (default) or the :ref:`lib_at_shell` library (for builds that need the :ref:`Zephyr shell `). + See `nRF91x1 AT Commands Reference Guide`_ or `nRF9160 AT Commands Reference Guide`_ documentation on each AT command. +* Support for the `nRF Cloud Provisioning Service`_ using the :ref:`lib_nrf_provisioning` library. + For compatibility with auto-onboarding, the device ID uses the 128 bit UUID format rather than the older nrf- format. +* Support for remote execution of modem AT commands using application-specific device messages. +* Periodic cellular, Wi-Fi, and GNSS location tracking using the :ref:`lib_location` library. +* Periodic temperature sensor sampling on your `Nordic Thingy:91`_, or fake temperature measurements on your `nRF9151 DK `_ , `nRF9161 DK `_, or `nRF9160 DK `_. +* Transmission of sensor and GNSS location samples to the nRF Cloud portal as `nRF Cloud device messages `_. +* Construction of valid `nRF Cloud device messages `_. +* Minimal LED status indication using the `Zephyr LED API`_. +* Transmission of an alert on sample startup using the :ref:`lib_nrf_cloud_alert` library. +* Transmission of additional alerts, whenever a specified temperature limit is exceeded. +* Optional transmission of log messages to the cloud using the :ref:`lib_nrf_cloud_log` library. +* Experimental support for Wi-Fi connectivity. + +.. _nrf_cloud_ble_gateway_structure_and_theory_of_operation: + +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 :file:`src/main.c` file. +This file starts three primary threads, each with a distinct function: + +* The cloud connection thread (``con_thread``, :file:`src/cloud_connection.c`) runs the :ref:`nrf_cloud_ble_gateway_cloud_connection_loop`, which maintains a connection to `nRF Cloud`_. +* The application thread (``app_thread``, :file:`src/application.c`) runs the :ref:`nrf_cloud_ble_gateway_application_thread_and_main_application_loop`, which controls demo features and submits `device messages `_ to the :ref:`nrf_cloud_ble_gateway_device_message_queue`. +* The message queue thread (``msg_thread``, :file:`src/message_queue.c`) then transmits these messages whenever there is an active connection. + See :ref:`nrf_cloud_ble_gateway_device_message_queue`. + +:file:`src/main.c` also optionally starts a fourth thread, the ``led_thread``, which animates any onboard LEDs if :ref:`nrf_cloud_ble_gateway_led_status_indication` is enabled. + +When using CoAP, two additional threads start: + +* The CoAP FOTA job checking thread (``coap_fota``, :file:`src/fota_support_coap.c`) runs the :ref:`nrf_cloud_ble_gateway_coap_fota_loop` which periodically asks nRF Cloud for any pending FOTA job. +* The CoAP shadow delta checking thread (``coap_shadow``, :file:`src/shadow_support_coap.c`) runs the :ref:`nrf_cloud_ble_gateway_coap_shadow_loop` which periodically asks nRF Cloud for any shadow changes. + +.. _nrf_cloud_ble_gateway_cloud_connection_loop: + +Cloud connection loop +===================== + +The cloud connection loop (implemented in :file:`src/cloud_connection.c`) monitors network availability. +It starts a connection with `nRF Cloud`_ whenever the Internet becomes reachable, and closes that connection whenever Internet access is lost. +It has error handling and timeout features to ensure that failed or lost connections are re-established after a waiting period (:ref:`CONFIG_CLOUD_CONNECTION_RETRY_TIMEOUT_SECONDS `). + +Since the :kconfig:option:`CONFIG_NRF_MODEM_LIB_NET_IF` Kconfig option is enabled, Zephyr's ``conn_mgr`` automatically enables and connects to LTE. + +Whenever a connection to nRF Cloud is started, the cloud connection loop follows the :ref:`nRF Cloud connection process `. +The :ref:`lib_nrf_cloud` library handles most of the connection process, with exception to the following behavior: + +When an MQTT 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 :c:func:`cloud_event_handler` function. + +When a CoAP device attempts to connect to nRF Cloud, the connection will fail to be authenticated until the device is onboarded to an nRF Cloud account. +See :ref:`nrf_cloud_ble_gateway_standard_onboarding` for details. + +Upon startup, the cloud connection loop also updates the `device shadow `_. +This is performed in the :c:func:`update_shadow` function. + +.. _nrf_cloud_ble_gateway_device_message_queue: + +Device message queue +==================== + +Any thread may submit `device messages `_ to the device message queue (implemented in :file:`src/message_queue.c`), 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. +Transmission is paused whenever connection to nRF Cloud is lost. +If more than :ref:`CONFIG_MAX_CONSECUTIVE_SEND_FAILURES ` messages in a row fail to send, the connection to nRF Cloud is reset, and then reconnected after a delay (:ref:`CONFIG_CLOUD_CONNECTION_RETRY_TIMEOUT_SECONDS `). + +Most messages sent to nRF Cloud by this sample are sent using the message queue, but the following are sent directly: + +* Alerts using the :ref:`lib_nrf_cloud_alert` library. +* Logs using the :ref:`lib_nrf_cloud_log` library. +* `Device shadow `_ updates. +* Ground fix requests from the :ref:`lib_location` library. + +.. _nrf_cloud_ble_gateway_application_thread_and_main_application_loop: + +Application thread and main application loop +============================================ + +The application thread is implemented in the :file:`src/application.c` file, and is responsible for the high-level behavior of this sample. + +When it starts, it logs the `reset reason code `_. +If the :kconfig:option:`CONFIG_SEND_ONLINE_ALERT` Kconfig option is enabled, it sends an alert to nRF Cloud containing the reset reason as the value field. + +It performs the following major tasks: + +* Establishes periodic position tracking (which the :ref:`lib_location` library performs). +* Periodically samples temperature data (using the :file:`src/temperature.c` file). +* Constructs timestamped sensor sample and location `device messages `_. +* Sends sensor sample and location device messages to the :ref:`nrf_cloud_ble_gateway_device_message_queue`. +* Checks for and executes :ref:`remote modem AT command requests `. +* Sends temperature alerts and sample startup alerts using the :ref:`lib_nrf_cloud_alert` library. + +.. note:: + Periodic location tracking is handled by the :ref:`lib_location` library once it has been requested, whereas temperature samples are individually requested by the Main Application Loop. + +.. _nrf_cloud_ble_gateway_coap_fota_loop: + +CoAP FOTA job check loop +======================== + +The nRF Cloud CoAP service provides device-initiated communication with the server. +Server to device communication is only in response to a device request, which means the device polls for asynchronous changes to server resources. +The CoAP FOTA job checking thread performs the following tasks: + +* Checks the settings storage area to see if the device has recently rebooted after performing a FOTA update. +* If it had recently rebooted, it sends the results of the FOTA update to the server with the :c:func:`nrf_cloud_coap_fota_job_update` function. +* Checks for a new FOTA job with the :c:func:`nrf_cloud_coap_fota_job_get` function. +* If one is available, it downloads the update using HTTPS, saves the job information in settings, then reboots the device. +* If the download was unsuccessful, it notifies the server with the :c:func:`nrf_cloud_coap_fota_job_update` function. + +.. _nrf_cloud_ble_gateway_coap_shadow_loop: + +CoAP shadow delta checking loop +=============================== + +The CoAP shadow delta checking thread performs the following tasks: + +* Checks for a change in the device shadow with the :c:func:`nrf_cloud_coap_shadow_get` function. +* Parse and process the JSON shadow delta document. +* If a change is received, the thread sends the change back with the :c:func:`nrf_cloud_coap_shadow_state_update` function. + This is necessary to prevent the device from unnecessarily receiving the same shadow delta the next time through the loop. + +.. _nrf_cloud_ble_gateway_fota: + +FOTA +==== + +When using MQTT, the `FOTA update `_ support is almost entirely implemented by enabling the :kconfig:option:`CONFIG_NRF_CLOUD_FOTA` option (which is implicitly enabled by :kconfig:option:`CONFIG_NRF_CLOUD_MQTT`). + +However, even with :kconfig:option:`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 :file:`src/fota_support.c` file, triggered by a call from the :file:`src/cloud_connection.c` file. + +`Device Shadow `_ updates are performed in the :file:`src/cloud_connection.c` file. + +In a real-world setting, these two behaviors could be directly implemented in the :file:`src/cloud_connection.c` file. +In this sample, they are separated for clarity. + +This sample supports full modem FOTA for the nRF91 Series development kit. +Version 0.14.0 or higher is required for nRF9160 DK. +To enable full modem FOTA, add the following parameter to your build command: + +``-DEXTRA_CONF_FILE=overlay_full_modem_fota.conf`` + +Also, specify your development kit version by appending it to the board name. +For example, if you are using an nRF9160 DK version 1.0.1, use the following board name in your build command: + +``nrf9160dk@1.0.1/nrf9160/ns`` + +This sample also supports placement of the MCUboot secondary partition in external flash for the nRF91x1 DKs, and for nRF9160 DK version 0.14.0 and higher. +To enable this, add the following parameters to your build command: + +* ``-DEXTRA_CONF_FILE=overlay_mcuboot_ext_flash.conf`` +* ``-DSB_CONF_FILE=sysbuild_ext_flash.conf`` + +Then specify your development kit version as described earlier. + +.. _nrf_cloud_ble_gateway_temperature_sensing: + +Temperature sensing +=================== + +Temperature sensing is mostly implemented in the :file:`src/temperature.c` file. +This includes generation of false temperature readings on your nRF91 Series 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 :file:`boards/thingy91_nrf9160_ns.overlay` file, as well as enabling the Kconfig options :kconfig:option:`CONFIG_SENSOR` and :kconfig:option:`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 :ref:`CONFIG_TEMP_DATA_USE_SENSOR `. + +.. note:: + For temperature readings to be visible in the nRF Cloud portal, they must be marked as enabled in the `Device Shadow `_. + This is performed by the :file:`src/cloud_connection.c` file. + +.. _nrf_cloud_ble_gateway_location_tracking: + +Location tracking +================= + +All matters concerning location tracking are handled in the :file:`src/location_tracking.c` file. +This involves setting up a periodic location request, and then passing the results to a callback configured by the :file:`src/application.c` file. + +For location readings to be visible in the nRF Cloud portal, they must be marked as enabled in the `Device Shadow `_. +This is performed by the :file:`src/cloud_connection.c` file. + +Each enabled location method is tried in the following order until one succeeds: GNSS, Wi-Fi, then cellular. + +The GNSS and cellular location tracking methods are enabled by default and will work on both Thingy:91 and nRF91 Series DK targets. + +.. _nrf_cloud_ble_gateway_wifi_location_tracking: + +The Wi-Fi location tracking method is not enabled by default and requires the nRF7002 Wi-Fi companion chip. + +When enabled, this location method scans the MAC addresses of nearby access points and submits them to nRF Cloud to obtain a location estimate. + +See :ref:`nrf_cloud_ble_gateway_building_wifi_scan` for details on how to enable Wi-Fi location tracking. + +This sample supports placing P-GPS data in external flash for the nRF91 Series development kit. +Version 0.14.0 or later is required for the nRF9160 DK. +To enable this, add the following parameter to your build command: + +``-DEXTRA_CONF_FILE=overlay_pgps_ext_flash.conf`` + +Also, specify your development kit version by appending it to the board name. +For example, if you are using an nRF9160 development kit version 1.0.1, use the following board name in your build command: + +``nrf9160dk@1.0.1/nrf9160/ns`` + +.. _nrf_cloud_ble_gateway_remote_at: + +Remote execution of modem AT commands (MQTT only) +================================================= + +If the :ref:`CONFIG_AT_CMD_REQUESTS ` Kconfig option is enabled, you can remotely execute modem AT commands on your device by sending a device message with appId ``MODEM``, messageType ``CMD``, and the data key set to the command you would like to execute. + +The application executes the command stored in the data key, and responds with a device message containing either an error code or the response from the modem to the AT command. + +For example, if you send the following device message to a device running this sample with :ref:`CONFIG_AT_CMD_REQUESTS ` enabled: + +.. code-block:: json + + {"appId":"MODEM", "messageType":"CMD", "data":"AT+CGMR"} + +It executes the modem AT command ``AT+CGMR`` and sends a device message similar to the following back to nRF Cloud (an example of nRF9160, modem firmware v1.3.2): + +.. code-block:: json + + { + "appId": "MODEM", + "messageType": "DATA", + "ts": 1669244834095, + "data": "mfw_nrf9160_1.3.2\r\nOK" + } + + +To do this in the nRF Cloud portal, write `{"appId":"MODEM", "messageType":"CMD", "data":"AT+CGMR"}` into the **Send a message** box of the **Terminal** card and click :guilabel:`Send`. + +.. _nrf_cloud_ble_gateway_led_status_indication: + +LED status indication +===================== + +On boards that support LED status indication, this sample can indicate its current status with any on-board LEDs. + +This is performed by a background thread implemented in the :file:`src/led_control.c` file. + +Other threads may request either a temporary or indefinite LED pattern. +This wakes up the ``led_thread``, which begins animating the requested pattern, sleeping for 100 milliseconds at a time between animation frames, until the requested pattern has completed (if it is temporary), or until a new pattern is requested in its place. + +This feature is enabled by default for the *board_target* mentioned in the `Requirements`_ sections. + +The patterns displayed, the states they describe, and the options required for them to appear are as follows: + ++------------------------------------------------------+--------------------------------------+--------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------+ +| Status | Thingy:91 | nRF91 Series DK | Conditions | ++======================================================+======================================+========================================================================================================+===========================================================================================================+ +| Trying to connect to nRF Cloud (for the first time) | Pulsating orange LED | Single LED lit, spinning around the square of LEDs | LED status indication is enabled | ++------------------------------------------------------+--------------------------------------+--------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------+ +| Connection to nRF Cloud lost, reconnecting | Pulsating orange LED | Single LED lit, spinning around the square of LEDs | The :ref:`CONFIG_LED_VERBOSE_INDICATION ` option is enabled | ++------------------------------------------------------+--------------------------------------+--------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------+ +| Fatal error | Blinking red LED, 75% duty cycle | All four LEDs blinking, 75% duty cycle | LED status indication is enabled | ++------------------------------------------------------+--------------------------------------+--------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------+ +| Device message sent successfully | Blinking green LED, 25% duty cycle | Alternating checkerboard pattern (two LEDs are lit at a time, either LED1 and LED4, or LED2 and LED3) | The :ref:`CONFIG_LED_VERBOSE_INDICATION ` option is enabled | ++------------------------------------------------------+--------------------------------------+--------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------+ +| Idle | LED pulsating between blue and cyan | Single LED lit, bouncing between opposite corners (LED1 and LED4) | The :ref:`CONFIG_LED_CONTINUOUS_INDICATION ` option is enabled | ++------------------------------------------------------+--------------------------------------+--------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------+ + +Under all other circumstances, on-board LEDs are turned off. + +.. note:: + The :ref:`CONFIG_LED_VERBOSE_INDICATION ` and :ref:`CONFIG_LED_CONTINUOUS_INDICATION ` options are enabled by default. + +See :ref:`nrf_cloud_ble_gateway_customizing_LED_status_indication` for details on customizing the LED behavior. + +See :ref:`nrf_cloud_ble_gateway_led_third_party` for details on configuring LED status indication on third-party boards. + +.. _nrf_cloud_ble_gateway_test_counter: + +Test counter +============ + +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. + +You can enable or disable the test counter using the device shadow. +In the desired config section, set ``"counterEnable"`` to ``true`` to enable or ``false`` to disable the test counter. + +.. code-block:: json + + "desired": { + "config": { + "counterEnable": true + } + } + +You can perform the shadow update by clicking the :guilabel:`View Config` button on the **Device** page in the nRF Cloud portal or through the ``UpdateDeviceState`` endpoint in the `nRF Cloud Rest API`_. +To ignore the shadow setting so that the test counter is always active, enable the :ref:`CONFIG_TEST_COUNTER ` Kconfig option. + +.. _nrf_cloud_ble_gateway_device_message_formatting: + +Device message formatting +========================= + +This sample, when using MQTT to communicate with nRF Cloud, constructs JSON-based `device messages `_. + +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 :file:`src/application.c` file all adhere to the general message schema. + +GNSS and temperature data device messages conform to the ``gnss`` and ``temp`` ``deviceToCloud`` schemas respectively. + +This sample, when using CoAP to communicate with nRF Cloud, uses the :ref:`lib_nrf_cloud_coap` library to construct and transmit CBOR-based device messages. +Some CoAP traffic, specifically AWS shadow traffic, remains in JSON format. + +.. _nrf_cloud_ble_gateway_configuration: + +Configuration +************* +|config| + +Disabling key features +====================== + +You can independently disable the following key features of this sample by setting their respective Kconfig option disabled: + +.. list-table:: + :widths: 50 50 + :header-rows: 1 + + * - Feature + - Kconfig option + * - GNSS-based location tracking + - :ref:`CONFIG_LOCATION_TRACKING_GNSS ` + * - Cellular-based location tracking + - :ref:`CONFIG_LOCATION_TRACKING_CELLULAR ` + * - Wi-Fi-based location tracking + - :ref:`CONFIG_LOCATION_TRACKING_WIFI ` + * - Temperature tracking + - :ref:`CONFIG_TEMP_TRACKING ` + * - GNSS assistance (A-GNSS) + - :kconfig:option:`CONFIG_NRF_CLOUD_AGNSS` + * - Predictive GNSS assistance (P-GPS) + - :kconfig:option:`CONFIG_NRF_CLOUD_PGPS` + * - FOTA when using MQTT + - :kconfig:option:`CONFIG_NRF_CLOUD_FOTA` + * - FOTA when using CoAP + - :ref:`CONFIG_COAP_FOTA ` + * - Shadow handling when using CoAP + - :ref:`CONFIG_COAP_SHADOW ` + +If you disable GNSS, Wi-Fi-based, and cellular-based location tracking, location tracking is completely disabled. +In that case, also set the :ref:`CONFIG_LOCATION_TRACKING ` option to disabled. + +For examples, see the related minimal overlays in the :ref:`nrf_cloud_ble_gateway_minimal` section. + +.. note:: + MQTT should only be used with applications that need to stay connected constantly or transfer data frequently. + While this sample does allow its core features to be slowed or completely disabled, in real-world applications, you should carefully consider your data throughput and whether MQTT is an appropriate solution. + If you want to disable or excessively slow all of these features for a real-world application, other solutions, such as the `nRF Cloud Rest API`_, may be more appropriate. + +Customizing GNSS antenna configuration +====================================== + +This sample uses the :ref:`lib_modem_antenna` library, which is enabled by default for the *board_target* mentioned in the `Requirements`_ sections. + +If you are using a different board or board target, or would like to use a custom or external GNSS antenna, see the :ref:`lib_modem_antenna` library documentation for configuration instructions. + +Enable :kconfig:option:`CONFIG_MODEM_ANTENNA_GNSS_EXTERNAL` to use an external antenna. + +.. _nrf_cloud_ble_gateway_customizing_LED_status_indication: + +Customizing LED status indication +================================= + +To disable LED status indication (other than the selected idle behavior) after a connection to nRF Cloud has been established at least once, disable :ref:`CONFIG_LED_VERBOSE_INDICATION `. + +To turn the LED off while the sample is idle (rather than show an idle pattern), disable :ref:`CONFIG_LED_CONTINUOUS_INDICATION `. + +If you disable both of these options together, the status indicator LED remains off after a connection to nRF Cloud has been established at least once. + +.. _nrf_cloud_ble_gateway_led_third_party: + +Configuring LED status indication for third-party boards +======================================================== + +This sample assumes that the target board either has a single RGB LED with PWM support, or four discrete LEDs available. + +For third-party boards, you can select the RGB LED option by enabling both the :ref:`CONFIG_LED_INDICATION_PWM ` and :ref:`CONFIG_LED_INDICATION_RGB ` options. +In this case, the board must have a devicetree entry marked as compatible with the `Zephyr pwm-leds`_ driver. + +Otherwise, the four-LED option (:ref:`CONFIG_LED_INDICATION_GPIO ` and :ref:`CONFIG_LED_INDICATOR_4LED `) is selected by default as long as there is a devicetree entry compatible with the `Zephyr gpio-leds`_ driver. + +The four-LED option should work even if there are not four LEDs available, as long as an appropriate devicetree entry exists. +However, if fewer than four LEDs are available, the patterns may be difficult to identify. + +To add your own LED indication implementations, you can add values to the ``LED_INDICATOR`` Kconfig choice and modify the :file:`src/led_control.c` file accordingly. + +To disable LED indication, enable the :ref:`CONFIG_LED_INDICATION_DISABLED ` option. + +For examples of how to set up devicetree entries compatible with the Zephyr ``gpio-leds`` and ``pwm-leds`` drivers, see the following files, depending on the DK you are using: + +* :file:`zephyr/boards/nordic/nrf9161dk/nrf9151dk_nrf9151_common.dts` +* :file:`zephyr/boards/nordic/nrf9161dk/nrf9161dk_nrf9161_common.dts` +* :file:`zephyr/boards/nordic/nrf9160dk/nrf9160dk_nrf9160_common.dts` +* :file:`zephyr/boards/arm/thingy91_nrf9160/thingy91_nrf9160_common.dts` + +Search for nodes with ``compatible = "gpio-leds";`` and ``compatible = "pwm-leds";`` respectively. + +Useful debugging options +======================== + +To see all debug output for this sample, enable the :ref:`CONFIG_ble_gateway_LOG_LEVEL_DBG ` option. + +To monitor the GNSS module (for instance, to see whether A-GNSS or P-GPS assistance data is being consumed), enable the :kconfig:option:`CONFIG_NRF_CLOUD_GPS_LOG_LEVEL_DBG` option. + +See also the :ref:`nrf_cloud_ble_gateway_test_counter`. + +Configuration options +===================== + +Set the following configuration options for the sample: + +.. _CONFIG_ble_gateway_LOG_LEVEL_DBG: + +CONFIG_ble_gateway_LOG_LEVEL_DBG - Sample debug logging + Sets the log level for this sample to debug. + +.. _CONFIG_CLOUD_CONNECTION_RETRY_TIMEOUT_SECONDS: + +CONFIG_CLOUD_CONNECTION_RETRY_TIMEOUT_SECONDS - Cloud connection retry timeout (seconds) + Sets the cloud connection retry timeout in seconds. + +.. _CONFIG_CLOUD_READY_TIMEOUT_SECONDS: + +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: + +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: + +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: + +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: + +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: + +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: + +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: + +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: + +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-GNSS data, one minute is a reasonable interval for reliably getting a location estimate. + This allows using long enough value for :ref:`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 :kconfig:option:`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_FIX_TIMEOUT_SECONDS: + +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_TRACKING: + +CONFIG_LOCATION_TRACKING - Enable or disable location tracking + Enables location tracking. + Enable at least one location tracking method to avoid a build error. + +.. _CONFIG_LOCATION_TRACKING_SAMPLE_INTERVAL_SECONDS: + +CONFIG_LOCATION_TRACKING_SAMPLE_INTERVAL_SECONDS - Location sampling interval (seconds) + Sets the location sampling interval in seconds. + +.. _CONFIG_LOCATION_TRACKING_GNSS: + +CONFIG_LOCATION_TRACKING_GNSS - GNSS location tracking + Enables GNSS location tracking. + Disable :ref:`CONFIG_LOCATION_TRACKING ` and all location tracking methods to completely disable location tracking. + Defaults to enabled. + +.. _CONFIG_LOCATION_TRACKING_CELLULAR: + +CONFIG_LOCATION_TRACKING_CELLULAR - Cellular location tracking + Enables cellular location tracking. + Disable :ref:`CONFIG_LOCATION_TRACKING ` and all location tracking methods to completely disable location tracking. + Defaults to enabled. + +.. _CONFIG_LOCATION_TRACKING_WIFI: + +CONFIG_LOCATION_TRACKING_WIFI - Wi-Fi location tracking + Enables Wi-Fi location tracking. + Disable :ref:`CONFIG_LOCATION_TRACKING ` and all location tracking methods to completely disable location tracking. + Requires the use of an nRF7002 companion chip. + Defaults to disabled. + +.. _CONFIG_TEMP_DATA_USE_SENSOR: + +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_TEMP_TRACKING: + +CONFIG_TEMP_TRACKING - Track temperature data + Enables tracking and reporting of temperature data to `nRF Cloud`_. + Defaults to enabled. + +.. _CONFIG_LED_INDICATION_PWM: + +CONFIG_LED_INDICATION_PWM - PWM LED indication + Use the `Zephyr pwm-leds`_ driver for LED indication. + Defaults to enabled on the Thingy:91. + +.. _CONFIG_LED_INDICATION_GPIO: + +CONFIG_LED_INDICATION_GPIO - GPIO LED indication + Use the `Zephyr gpio-leds`_ driver for LED indication. + Defaults to enabled if there is a compatible devicetree entry, and the Thingy:91 is not the target. + Defaults to enabled on the nRF91 Series DKs. + +.. _CONFIG_LED_INDICATION_DISABLED: + +CONFIG_LED_INDICATION_DISABLED - Completely disable LED indication + Defaults to enabled if both :ref:`CONFIG_LED_INDICATION_PWM ` and :ref:`CONFIG_LED_INDICATION_GPIO ` are disabled. + +.. _CONFIG_LED_INDICATOR_RGB: + +CONFIG_LED_INDICATOR_RGB - RGB LED status indication + Use an on-board RGB LED for status indication. + Defaults to enabled on the Thingy:91. + +.. _CONFIG_LED_INDICATOR_4LED: + +CONFIG_LED_INDICATOR_4LED - Four-LED status indication + Use four discrete LEDs for status indication. + Defaults to enabled if :ref:`CONFIG_LED_INDICATOR_RGB ` is disabled and LED indication is not disabled. + +.. _CONFIG_LED_VERBOSE_INDICATION: + +CONFIG_LED_VERBOSE_INDICATION - Verbose LED status indication + Show more detailed LED status updates. + Show a pattern when device messages are successfully sent, and when the initial connection to nRF Cloud is lost. + Defaults to enabled if LED indication is enabled. + +.. _CONFIG_LED_CONTINUOUS_INDICATION: + +CONFIG_LED_CONTINUOUS_INDICATION - Continuous LED status indication + Show an idle pattern, rather than turn the LEDs off, when the sample is idle. + Defaults to enabled if LED indication is enabled. + +.. _CONFIG_TEST_COUNTER: + +CONFIG_TEST_COUNTER - Enable test counter + Enable the test counter. + When enabled, the test counter configuration setting in the shadow is ignored. + +.. _CONFIG_AT_CMD_REQUESTS: + +CONFIG_AT_CMD_REQUESTS - Enable AT command requests + Allow remote execution of modem AT commands, requested using application-specific device messages. + See :ref:`nrf_cloud_ble_gateway_remote_at` for details. + +.. _CONFIG_AT_CMD_REQUEST_RESPONSE_BUFFER_LENGTH: + +CONFIG_AT_CMD_REQUEST_RESPONSE_BUFFER_LENGTH - Length of AT command request response buffer (bytes) + Sets the size of the buffer for storing responses to modem AT commands before they are forwarded to the cloud. + Modem responses longer than this length will be replaced with an error code message (-NRF_E2BIG). + Cannot be less than 40 bytes. + +When using CoAP, the following additional configuration options are available: + +.. _CONFIG_COAP_SHADOW: + +CONFIG_COAP_SHADOW - Enable shadow handling + Periodically check for and process shadow delta messages. + +If :ref:`CONFIG_COAP_SHADOW ` is enabled, these options additional are available: + +.. _CONFIG_COAP_SHADOW_CHECK_RATE_SECONDS: + +CONFIG_COAP_SHADOW_CHECK_RATE_SECONDS - Rate to check for shadow changes + How many seconds between requests for any change (delta) in the device shadow. + +.. _CONFIG_COAP_SHADOW_THREAD_STACK_SIZE: + +CONFIG_COAP_SHADOW_THREAD_STACK_SIZE - CoAP Shadow Thread Stack Size (bytes) + Sets the stack size (in bytes) for the shadow delta checking thread of the sample. + +.. _CONFIG_COAP_FOTA: + +CONFIG_COAP_FOTA - Enable FOTA with CoAP + The sample periodically checks for pending FOTA jobs. + The sample performs the FOTA update when received. + +If :ref:`CONFIG_COAP_FOTA ` is enabled, these options additional are available: + +.. _CONFIG_COAP_FOTA_DL_TIMEOUT_MIN: + +CONFIG_COAP_FOTA_DL_TIMEOUT_MIN - CoAP FOTA download timeout + The time in minutes allotted for a FOTA download to complete. + +.. _CONFIG_COAP_FOTA_USE_NRF_CLOUD_SETTINGS_AREA: + +CONFIG_COAP_FOTA_USE_NRF_CLOUD_SETTINGS_AREA - Make FOTA compatible with other samples + Use the same settings area as the nRF Cloud FOTA library. + +.. _CONFIG_COAP_FOTA_SETTINGS_NAME: + +CONFIG_COAP_FOTA_SETTINGS_NAME - Settings identifier + Set the identifier for the CoAP FOTA storage if :kconfig:option:`CONFIG_COAP_FOTA_USE_NRF_CLOUD_SETTINGS_AREA` is not enabled. + +.. _CONFIG_COAP_FOTA_SETTINGS_KEY_PENDING_JOB: + +CONFIG_COAP_FOTA_SETTINGS_KEY_PENDING_JOB - Settings item key + Set the settings item key for pending FOTA job info if :kconfig:option:`CONFIG_COAP_FOTA_USE_NRF_CLOUD_SETTINGS_AREA` is not enabled. + +.. _CONFIG_COAP_FOTA_JOB_CHECK_RATE_MINUTES: + +CONFIG_COAP_FOTA_JOB_CHECK_RATE_MINUTES - FOTA job check interval (minutes) + How many minutes between requests for a FOTA job. + +.. _CONFIG_COAP_FOTA_THREAD_STACK_SIZE: + +CONFIG_COAP_FOTA_THREAD_STACK_SIZE - CoAP FOTA Thread Stack Size (bytes) + Sets the stack size (in bytes) for the FOTA job checking thread of the sample. + +.. include:: /libraries/modem/nrf_modem_lib/nrf_modem_lib_trace.rst + :start-after: modem_lib_sending_traces_UART_start + :end-before: modem_lib_sending_traces_UART_end + +.. _nrf_cloud_ble_gateway_building_and_running: + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/cellular/nrf_cloud_ble_gateway` + +.. include:: /includes/build_and_run_ns.txt + +.. _nrf_cloud_ble_gateway_building_lte: + +Building with LTE connectivity +============================== + +You can build the sample to connect over LTE as follows: + +.. tabs:: + + .. group-tab:: MQTT + + .. parsed-literal:: + :class: highlight + + west build -p -b *board_target* + + |board_target| + + .. group-tab:: CoAP + + To build the sample to use CoAP instead of MQTT, use the ``-DEXTRA_CONF_FILE=overlay_coap.conf`` option. + + .. parsed-literal:: + :class: highlight + + west build -p -b *board_target* -- -DEXTRA_CONF_FILE="overlay_coap.conf" + + |board_target| + +Once the sample is built and flashed, proceed to :ref:`nrf_cloud_ble_gateway_standard_onboarding` for instructions on how to onboard your device. + +.. _nrf_cloud_ble_gateway_building_provisioning_service: + +Building with nRF Cloud Provisioning Service Support +==================================================== + +The `nRF Cloud Provisioning Service`_ allows you to securely provision and onboard your Nordic Semiconductor devices entirely over-the-air (after you have obtained an `attestation token `_ for the device). + +.. note:: + + This service is not compatible with devices that use the nRF9160, but only for the nRF9161 device. + +You can enable support for this service by building the sample as follows: + +.. tabs:: + + .. group-tab:: MQTT + + .. code-block:: console + + west build -p -b nrf9161dk/nrf9161/ns -- -DEXTRA_CONF_FILE="overlay-http_nrf_provisioning.conf" + + The :file:`overlay-http_nrf_provisioning.conf` overlay enables the :ref:`lib_nrf_provisioning` library, and its shell interface to use HTTP for communication. + A side-effect of this is that the sample will use the :ref:`lib_at_shell` library instead of the :ref:`lib_at_host` library, so AT commands must be issued using the ``at`` shell command. + + .. group-tab:: CoAP + + To build the sample to use CoAP instead of MQTT, use the ``-DEXTRA_CONF_FILE=overlay_coap.conf`` option. + + .. code-block:: console + + west build -p -b nrf9161dk/nrf9161/ns -- -DEXTRA_CONF_FILE="overlay-coap_nrf_provisioning.conf;overlay_coap.conf" + + The :file:`overlay-coap_nrf_provisioning.conf` overlay enables the :ref:`lib_nrf_provisioning` library to use CoAP for communication. + It does not enable the shell. + + The :file:`overlay_coap.conf` overlay causes the sample to use CoAP instead of MQTT for normal communication. + +Once the sample is built and flashed, proceed to :ref:`nrf_cloud_ble_gateway_provisioning_service` for instructions on how to provision your device with the Provisioning Service. +The device is identified using its UUID rather than its IMEI, since both overlays set the :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID_SRC_INTERNAL_UUID` Kconfig option. + +.. _nrf_cloud_ble_gateway_building_wifi_scan: + +Building with nRF7002 Wi-Fi scanning support +============================================ + +To build the sample with Wi-Fi scanning support for the nRF7002 EK shield attached to an nRF91xx DK, use the ``-DSHIELD=nrf7002ek_nrf7000``, ``-DSB_CONF_FILE=sysbuild_nrf700x-wifi-scan.conf``, and ``-DEXTRA_CONF_FILE=overlay-nrf7002ek-wifi-scan-only`` options. + +This enables the Wi-Fi location tracking method automatically. + +.. parsed-literal:: + :class: highlight + + west build -p -b *board_target* -- -DSHIELD=nrf7002ek_nrf7000 -DSB_CONF_FILE="sysbuild_nrf700x-wifi-scan.conf" -DEXTRA_CONF_FILE="overlay-nrf7002ek-wifi-scan-only.conf" + +.. note:: + + The ``nrf7002ek_nrf7000`` shield is used here, rather than the ``nrf7002ek`` shield, to put the nRF7002 EK into nRF7000 emulation mode. + This is required in order to use scan-only mode. + + To build the sample with Wi-Fi connectivity instead, see :ref:`nrf_cloud_ble_gateway_building_lte`. + +|board_target| + +For the Thingy:91 X, which contains both an nRF9151 System-in-Package and the nRF7002 Companion IC, Wi-Fi scanning support is enabled by default. + +See also :ref:`the paragraphs on the Wi-Fi location tracking method `. + +Once the sample is built and flashed, proceed to :ref:`nrf_cloud_ble_gateway_standard_onboarding` for instructions on how to onboard your device. + +.. note:: + + This option enables Wi-Fi scanning, but still uses LTE connectivity. + +.. _nrf_cloud_ble_gateway_building_wifi_conn: + +Building with experimental support for Wi-Fi connectivity +========================================================= + +This sample :ref:`experimentally ` supports connecting to nRF Cloud using Wi-Fi instead of using LTE. + +Overlays for this are provided for the nRF7002 DK, and the nRF5340 DK with the nRF7002 EK shield attached. + +It is possible to use Wi-Fi with other hardware combinations, but you must adjust heap and stack usage accordingly. +See the :file:`src/prj.conf` configuration file and the :file:`overlay_nrf700x_wifi_mqtt_no_lte.conf` overlay for additional details. + +.. important:: + Connecting to nRF Cloud using Wi-Fi currently requires that device credentials are used insecurely. + + The provided overlays for Wi-Fi connectivity use the :ref:`TLS Credentials Subsystem ` (with the PSA Protected Storage backend, see :kconfig:option:`CONFIG_TLS_CREDENTIALS_BACKEND_PROTECTED_STORAGE`) to store credentials when not in use. + Even though this is more secure than :ref:`hard-coded credentials `, the device private key still has to be loaded into unprotected memory during TLS connections. + +This overlay also enables the :ref:`TLS Credentials Shell ` for run-time credential installation. + +If you are certain you understand the risks, you can configure your build to use Wi-Fi connectivity using the ``-DSB_CONF_FILE=sysbuild_nrf700x-wifi-conn.conf`` and ``-DEXTRA_CONF_FILE=overlay_nrf700x_wifi_mqtt_no_lte.conf`` options. +On the nRF5340 DK with the nRF7002 EK shield, you need to also use the ``-DSHIELD=nrf7002ek`` option. + +You must also configure a (globally unique) device ID at build time by enabling the :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME` Kconfig option and setting :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID` to the device ID. + +For example, for a device ID ``698d4c11-0ccc-4f04-89cd-6882724e3f6f``: + +.. tabs:: + + .. group-tab:: nRF5340 DK with the nRF7002 EK shield + + .. tabs:: + + .. group-tab:: MQTT + + .. tabs:: + + .. group-tab:: Bash + + .. parsed-literal:: + :class: highlight + + west build --board nrf5340dk/nrf5340/cpuapp/ns -p always -- -DSHIELD=nrf7002ek -DEXTRA_CONF_FILE=overlay_nrf700x_wifi_mqtt_no_lte.conf -DSB_CONF_FILE=sysbuild_nrf700x-wifi-conn.conf -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y -DCONFIG_NRF_CLOUD_CLIENT_ID=\"698d4c11-0ccc-4f04-89cd-6882724e3f6f\" + + .. group-tab:: PowerShell + + .. parsed-literal:: + :class: highlight + + west build --board nrf5340dk/nrf5340/cpuapp/ns -p always -- -DSHIELD=nrf7002ek -DEXTRA_CONF_FILE=overlay_nrf700x_wifi_mqtt_no_lte.conf -DSB_CONF_FILE=sysbuild_nrf700x-wifi-conn.conf -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y "-DCONFIG_NRF_CLOUD_CLIENT_ID=\`"698d4c11-0ccc-4f04-89cd-6882724e3f6f\`"" + + .. group-tab:: CoAP + + .. tabs:: + + .. group-tab:: Bash + + .. parsed-literal:: + :class: highlight + + west build --board nrf5340dk/nrf5340/cpuapp/ns -p always -- -DSHIELD=nrf7002ek -DEXTRA_CONF_FILE=overlay_nrf700x_wifi_coap_no_lte.conf -DSB_CONF_FILE=sysbuild_nrf700x-wifi-conn.conf -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y -DCONFIG_NRF_CLOUD_CLIENT_ID=\"698d4c11-0ccc-4f04-89cd-6882724e3f6f\" + + .. group-tab:: PowerShell + + .. parsed-literal:: + :class: highlight + + west build --board nrf5340dk/nrf5340/cpuapp/ns -p always -- -DSHIELD=nrf7002ek -DEXTRA_CONF_FILE=overlay_nrf700x_wifi_coap_no_lte.conf -DSB_CONF_FILE=sysbuild_nrf700x-wifi-conn.conf -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y "-DCONFIG_NRF_CLOUD_CLIENT_ID=\`"698d4c11-0ccc-4f04-89cd-6882724e3f6f\`"" + + .. group-tab:: nRF7002 DK + + .. tabs:: + + .. group-tab:: MQTT + + .. tabs:: + + .. group-tab:: Bash + + .. parsed-literal:: + :class: highlight + + west build --board nrf7002dk/nrf5340/cpuapp/ns -p always -- -DEXTRA_CONF_FILE=overlay_nrf700x_wifi_mqtt_no_lte.conf -DSB_CONF_FILE=sysbuild_nrf700x-wifi-conn.conf -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y -DCONFIG_NRF_CLOUD_CLIENT_ID=\"698d4c11-0ccc-4f04-89cd-6882724e3f6f\" + + .. group-tab:: PowerShell + + .. parsed-literal:: + :class: highlight + + west build --board nrf7002dk/nrf5340/cpuapp/ns -p always -- -DEXTRA_CONF_FILE=overlay_nrf700x_wifi_mqtt_no_lte.conf -DSB_CONF_FILE=sysbuild_nrf700x-wifi-conn.conf -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y "-DCONFIG_NRF_CLOUD_CLIENT_ID=\`"698d4c11-0ccc-4f04-89cd-6882724e3f6f\`"" + + .. group-tab:: CoAP + + .. tabs:: + + .. group-tab:: Bash + + .. parsed-literal:: + :class: highlight + + west build --board nrf7002dk/nrf5340/cpuapp/ns -p always -- -DEXTRA_CONF_FILE=overlay_nrf700x_wifi_coap_no_lte.conf -DSB_CONF_FILE=sysbuild_nrf700x-wifi-conn.conf -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y -DCONFIG_NRF_CLOUD_CLIENT_ID=\"698d4c11-0ccc-4f04-89cd-6882724e3f6f\" + + .. group-tab:: PowerShell + + .. parsed-literal:: + :class: highlight + + west build --board nrf7002dk/nrf5340/cpuapp/ns -p always -- -DEXTRA_CONF_FILE=overlay_nrf700x_wifi_coap_no_lte.conf -DSB_CONF_FILE=sysbuild_nrf700x-wifi-conn.conf -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y "-DCONFIG_NRF_CLOUD_CLIENT_ID=\`"698d4c11-0ccc-4f04-89cd-6882724e3f6f\`"" + +Once the sample is built and flashed, proceed to :ref:`nrf_cloud_ble_gateway_standard_onboarding` for instructions on how to onboard your device. + +Once your device is onboarded, proceed to :ref:`nrf_cloud_ble_gateway_setup_wifi_cred` for instructions on configuring your device to connect to a specific access point. + +.. _nrf_cloud_ble_gateway_build_hardcoded: + +Building with hard-coded CA and device credentials +================================================== + +The :kconfig:option:`CONFIG_NRF_CLOUD_PROVISION_CERTIFICATES` Kconfig option allows you to use hard-coded CA and device credentials stored in unprotected program memory for connecting to nRF Cloud. + +.. important:: + The :kconfig:option:`CONFIG_NRF_CLOUD_PROVISION_CERTIFICATES` Kconfig option is not secure, and should be used only for demonstration purposes! + Because this setting stores your device's private key in unprotected program memory, using it makes your device vulnerable to impersonation. + +.. note:: + This is only one of several mutually exclusive ways to install credentials to your device. + See :ref:`nrf_cloud_ble_gateway_provisioning_onboarding` for a list of other methods. + +If you are certain you understand the inherent security risks, you can use this option as follows: + +1. Follow the instructions under :ref:`nrf_cloud_ble_gateway_onboard_hardcoded`. + +#. Create a :file:`certs` folder directly in the :file:`nrf_cloud_ble_gateway` folder, and copy :file:`client-cert.pem`, :file:`private-key.pem`, and :file:`ca-cert.pem` files into it. + + Make sure not to place the new folder in the :file:`nrf_cloud_ble_gateway/src` folder by accident. + + Now, these certificates are automatically used if the :kconfig:option:`CONFIG_NRF_CLOUD_PROVISION_CERTIFICATES` Kconfig option is enabled. + +#. Build the sample with the :kconfig:option:`CONFIG_NRF_CLOUD_PROVISION_CERTIFICATES` Kconfig option enabled and the device ID you created configured. + + This is required for onboarding to succeed. + + Enable the :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME` Kconfig option and set :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID` to the device ID. + + For example, if the device ID is ``698d4c11-0ccc-4f04-89cd-6882724e3f6f``: + + .. tabs:: + + .. group-tab:: Bash + + .. parsed-literal:: + :class: highlight + + west build --board *board_target* -p always -- -DCONFIG_NRF_CLOUD_PROVISION_CERTIFICATES=y -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y -DCONFIG_NRF_CLOUD_CLIENT_ID=\"698d4c11-0ccc-4f04-89cd-6882724e3f6f\" + + .. group-tab:: PowerShell + + .. parsed-literal:: + :class: highlight + + west build --board *board_target* -p always -- -DCONFIG_NRF_CLOUD_PROVISION_CERTIFICATES=y -DCONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y "-DCONFIG_NRF_CLOUD_CLIENT_ID=\`"698d4c11-0ccc-4f04-89cd-6882724e3f6f\`"" + + +.. _nrf_cloud_ble_gateway_setup_wifi_cred: + +Setting up Wi-Fi access point credentials +========================================= + +This sample uses the :ref:`lib_wifi_credentials` library to manage Wi-Fi credentials. +Before the sample can connect to a Wi-Fi network, you must add at least one credential set. + +Once your device has been flashed with this sample, you can add a credential by connecting to your device's UART interface and then entering the following command: + +.. parsed-literal:: + :class: highlight + + wifi_cred add -s *NetworkSSID* -k 1 -p *NetworkPassword* + +Where *NetworkSSID* is replaced with the SSID of the Wi-Fi access point you want your device to connect to, and *NetworkPassword* is its password. +Then either reboot the device or use the ``wifi_cred auto_connect`` command to manually trigger a connection attempt. + +From now on, these credentials will automatically be used when the configured network is reachable. + +See the :ref:`Wi-Fi shell sample documentation ` for more details on the ``wifi_cred`` command. + +Building with nRF Cloud logging support +======================================= + +To enable transmission of `logs `_ to nRF Cloud using the :ref:`lib_nrf_cloud_log` library, add the following parameter to your build command: + +``-DEXTRA_CONF_FILE=overlay_nrfcloud_logging.conf`` + +This overlay enables transmission of `logs `_ to nRF Cloud. +Set the :kconfig:option:`CONFIG_NRF_CLOUD_LOG_OUTPUT_LEVEL` Kconfig option to the log level of messages to send to nRF Cloud, such as ``4`` for debug log messages. + +The overlay selects the :kconfig:option:`CONFIG_LOG_BACKEND_NRF_CLOUD_OUTPUT_TEXT` Kconfig option that enables log messages in JSON format. +You can read JSON log messages in real-time in the nRF Cloud user interface. +However, because JSON logs are large, you may want to edit the overlay file to change to using dictionary logging. +Deselect the :kconfig:option:`CONFIG_LOG_BACKEND_NRF_CLOUD_OUTPUT_TEXT` Kconfig option and select :kconfig:option:`CONFIG_LOG_BACKEND_NRF_CLOUD_OUTPUT_DICTIONARY` instead. +See `Dictionary-based Logging`_ to learn how dictionary-based logging works, how the dictionary is built, and how to decode the binary log output. + +.. _nrf_cloud_ble_gateway_minimal: + +Building with minimal services +============================== + +To build the sample with only temperature tracking enabled for either MQTT or CoAP, add the following parameter to your build command: + +``-DEXTRA_CONF_FILE=overlay_min_mqtt.conf`` + +or + +``-DEXTRA_CONF_FILE=overlay_min_coap.conf`` + +These overlays show all the Kconfig settings changes needed to properly disable all but a single sensor. + +.. _nrf_cloud_ble_gateway_provisioning_onboarding: + +Provisioning and onboarding +*************************** + +For your device to successfully connect to nRF Cloud, you must `onboard it `_. +The following sections outline the various onboarding methods that this sample supports. + + * If you are building with :ref:`provisioning service support `, proceed to :ref:`nrf_cloud_ble_gateway_provisioning_service`. + Use this method for nRF91x1-based devices. + * If you are not building with Provisioning Service support, proceed to :ref:`nrf_cloud_ble_gateway_standard_onboarding`. + Use this method for nRF9160-based devices. + * If you are building with :ref:`hard-coded credentials `, proceed to :ref:`nrf_cloud_ble_gateway_onboard_hardcoded`. + +.. note:: + You only need to perform one of the above methods. + Select the one that best matches your needs, then build and run the sample with the required build configuration. + +.. _nrf_cloud_ble_gateway_install_nrf_utils: + +Download and install nRF Cloud Utilities +======================================== + +To perform many of the actions in the other sections, you will need to install the `nRF Cloud Utilities `_ repository. +This is not necessary if you are using the `auto-onboarding `_ feature of the nRF Cloud Provisioning Service. + +To install the repository, clone or download it and `install the required packages `_. + +.. _nrf_cloud_ble_gateway_provisioning_service: + +Provisioning with the nRF Cloud Provisioning Service +==================================================== + +The nRF Cloud Provisioning Service is only compatible with nRF91x1 devices. +For devices using the nRF9160, proceed to :ref:`nrf_cloud_ble_gateway_standard_onboarding`. + +If you have :ref:`enabled support ` for the `provisioning service `_, you can provision and onboard your device in one of two ways: + +* Using auto-onboarding, the easiest method. + + The nRF Cloud Provisioning Service auto-onboarding is compatible with CoAP, REST and MQTT connectivity with nRF Cloud. + + With this method, use the `Serial Terminal app`_ and the nRF Cloud portal. + The device ID used in nRF Cloud portal requires the UUID format and not the 'nrf-\ *IMEI*\ ' format. + See `device claiming `_ for more information. + +* Using scripted onboarding, as follows: + + First, :ref:`create a self-signed CA certificate `. + + Then, complete the following steps for each device you wish to onboard: + + 1. Make sure that your device is plugged in and this sample has been flashed to it. + #. Use the :file:`claim_and_provision_device.py` Python script :ref:`you installed ` to provision and onboard your device + + .. tabs:: + + .. group-tab:: MQTT + + .. parsed-literal:: + :class: highlight + + python3 claim_and_provision_device.py --api_key "\ *your_api_key*\ " --ca="self\_\ *self_cert_serial*\ \_ca.pem" --ca_key="self\_\ *self_cert_serial*\ \_prv.pem" --install_ca --unclaim --id_imei --id_str "nrf-" + + Where the :file:`.pem` files are the self-signed CA certificate and private key files :ref:`you created ` and *your_api_key* is your `nRF Cloud REST API key`_. + + .. group-tab:: CoAP + + .. parsed-literal:: + :class: highlight + + python3 claim_and_provision_device.py --api_key "\ *your_api_key*\ " --ca="self\_\ *self_cert_serial*\ \_ca.pem" --ca_key="self\_\ *self_cert_serial*\ \_prv.pem" --install_ca --unclaim --id_imei --id_str "nrf-" --coap + + Where the :file:`.pem` files are the self-signed CA certificate and private key files :ref:`you created ` and *your_api_key* is your `nRF Cloud REST API key`_. + The ``--coap`` option indicates that the device needs the CoAP root CA installed. + See below for more details. + + .. note:: + This command assumes you have left the :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID_SRC_IMEI` option enabled and the :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID_PREFIX` option set to ``nrf-``. + See :ref:`configuration_device_id` to use other device ID formats. + + This script automatically performs the following steps: + + 1. Obtains an `attestation token `_ from your device over UART using the :ref:`lib_nrf_provisioning` library shell, and then *claims* your device on nRF Cloud. + 2. Generates, signs, and installs a device credential for your device. + + * This happens entirely over-the-air. + * The private key for this credential is generated by the device itself. + It is stored securely and never leaves the device. + + 3. Installs any necessary root CA certificates. + + * CoAP connections use one root CA certificate, whereas HTTPS and MQTT use another. + * Devices using CoAP need both installed, since HTTPS is used for FOTA and P-GPS on CoAP devices. + + This script may take a few minutes. + When it succeeds, you should see several provisioning commands be issued and succeed, and some additional final output: + + .. parsed-literal:: + :class: highlight + + Adding device 'nrf-\ *IMEI*\ ' to cloud account... + ProvisionDevices API call response: 202 - Accepted + Done. + + Where *IMEI* is the IMEI of your device. + + The device should also appear in the devices list in the nRF Cloud portal. + + Once the script is complete, you can connect to your device with a UART terminal to monitor its activity. + + Within a few minutes of script completion, it should successfully connect to nRF Cloud and begin demonstrating the :ref:`supported nRF Cloud features `. + +.. _nrf_cloud_ble_gateway_standard_onboarding: + +Onboarding a device without the nRF Cloud Provisioning Service +============================================================== + +If you are not using :ref:`provisioning service support `, you can onboard your devices as follows: + +First, :ref:`create a self-signed CA certificate `. + +Then, complete the following steps for each device you wish to onboard: + +1. Make sure your device is plugged in and that this sample has been flashed to it. +#. Install the device and server credentials using the :file:`device_credentials_installer.py` Python script :ref:`you installed `: + + (Select the protocol (MQTT or CoAP) and connectivity technology (LTE or Wi-Fi) you built the sample for) + + .. tabs:: + .. group-tab:: MQTT + .. tabs:: + .. group-tab:: LTE + .. parsed-literal:: + :class: highlight + + python3 device_credentials_installer.py --ca self\_\ *self_cert_serial*\ \_ca.pem --ca_key self\_\ *self_cert_serial*\ \_prv.pem --id_str nrf- --id_imei -s -d --verify + + Where the :file:`.pem` files are the self-signed CA certificate and private key files :ref:`you created `. + + .. note:: + This command assumes you have left the :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID_SRC_IMEI` option enabled and the :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID_PREFIX` option set to ``nrf-``. + See :ref:`configuration_device_id` to use other device ID formats. + + .. group-tab:: Wi-Fi + .. parsed-literal:: + :class: highlight + + python3 device_credentials_installer.py --ca self\_\ *self_cert_serial*\ \_ca.pem --ca_key self\_\ *self_cert_serial*\ \_prv.pem --id_str "\ *device_id*\ " -s -d --verify --local_cert --cmd_type tls_cred_shell --port *device_port* + + Where: + + * *device id* is the (globally unique) ID for your device. + You must use the same device ID that you configured for the sample at build time. + See :ref:`nrf_cloud_ble_gateway_building_wifi_conn` for details. + * The :file:`.pem` files are the self-signed CA certificate and private key files :ref:`you created `. + * *device port* is the serial port your device is attached to. + + The ``--cmd_type tls_cred_shell`` option indicates that the device is using the :ref:`TLS Credentials Shell ` for run-time credentials management instead of AT commands. + + The ``--local_cert`` option indicates that the device private key and certificate should be generated on the host machine, not on-device. + This is necessary because the TLS Credentials Shell does not support CSR generation currently. + Delete the device private key from your machine after it is installed to the device by this script. + + .. group-tab:: CoAP + .. tabs:: + .. group-tab:: LTE + .. parsed-literal:: + :class: highlight + + python3 device_credentials_installer.py --ca self\_\ *self_cert_serial*\ \_ca.pem --ca_key self\_\ *self_cert_serial*\ \_prv.pem --id_str nrf- --id_imei -s -d --verify --coap + + Where the :file:`.pem` files are the self-signed CA certificate and private key files :ref:`you created `. + The ``--coap`` option indicates that the device needs the CoAP root CA installed. + See below for more details. + + .. note:: + This command assumes you have left the :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID_SRC_IMEI` option enabled and the :kconfig:option:`CONFIG_NRF_CLOUD_CLIENT_ID_PREFIX` option set to ``nrf-``. + See :ref:`configuration_device_id` to use other device ID formats. + + .. group-tab:: Wi-Fi + .. parsed-literal:: + :class: highlight + + python3 device_credentials_installer.py --ca self\_\ *self_cert_serial*\ \_ca.pem --ca_key self\_\ *self_cert_serial*\ \_prv.pem --id_str "\ *device_id*\ " -s -d --verify --coap --local_cert --cmd_type tls_cred_shell --port *device_port* + + Where: + + * *device id* is the (globally unique) ID for your device. + You must use the same device ID that you configured for the sample at build time. + See :ref:`nrf_cloud_ble_gateway_building_wifi_conn` for details. + * The :file:`.pem` files are the self-signed CA certificate and private key files :ref:`you created `. + * *device port* is the serial port your device is attached to. + + The ``--cmd_type tls_cred_shell`` option indicates that the device is using the :ref:`TLS Credentials Shell ` for run-time credentials management instead of AT commands. + + The ``--local_cert`` option indicates that the device private key and certificate should be generated on the host machine, not on-device. + This is necessary because the TLS Credentials Shell does not support CSR generation currently. + Delete the device private key from your machine after it is installed to the device by this script. + + This script generates, signs, and installs a device credential for your device. + The private key for this credential is generated by the device itself, and stored on the modem. + + This script also installs any nRF Cloud root CA certificates required in a single chain to the :kconfig:option:`CONFIG_NRF_CLOUD_SEC_TAG` security tag (``sec_tag``). + CoAP connections use one root CA certificate, whereas HTTPS and MQTT use another. + Devices using CoAP need both installed, since HTTPS is used for FOTA and P-GPS on CoAP devices. + + If the script succeeds, you should see the following output: + + .. code-block:: console + + Saving provisioning endpoint CSV file provision.csv... + Provisioning CSV file saved + + And a new file, :file:`provision.csv` should be created. + This file will be used in the next step. + +#. Navigate to the `Bulk Onboard Devices`_ page of the nRF Cloud portal and upload the :file:`provision.csv` file to onboard the device. + + To get there from the :guilabel:`Dashboard`, click :guilabel:`Devices` under :guilabel:`Device Management` in the navigation pane on the left, then click :guilabel:`Add Devices` and select **Bulk Onboard**. + + Once the `Bulk Onboard Devices`_ page is open, drag in the :file:`provision.csv` file and click **Onboard**. + + You should see a message stating that the file was uploaded successfully, and your device should appear in the `Devices `_ page. + +.. _nrf_cloud_ble_gateway_onboard_hardcoded: + +Onboarding with hard-coded device credentials +============================================= + +It is possible to onboard your devices using hard-coded device credentials. +If you are certain you understand the inherent security risks, you can do so as follows: + +First, create a :ref:`self-signed CA certificate `. + +Next, complete the following steps for each device you wish to onboard: + +1. :ref:`Locally generate a device credential ` + + In addition to the arguments prescribed in that section, also include the ``-embed_save`` argument when running the :file:`create_device_credentials.py` Python script: + + .. parsed-literal:: + :class: highlight + + python3 create_device_credentials.py -ca "self\_\ *self_cert_serial*\ \_ca.pem" -ca_key "self\_\ *self_cert_serial*\ \_prv.pem" -c US -cn "\ *device_id*\ " -f cred\_ -embed_save + + This automatically generates the following three files that are needed for the next step: + + * :file:`client-cert.pem` + * :file:`private-key.pem` + * :file:`ca-cert.pem` + + The :file:`client-cert.pem` and :file:`private-key.pem` files are specially formatted versions of the :file:`cred__crt.pem` and :file:`cred__prv.pem` files respectively. + The :file:`ca-cert.pem` is a copy of the nRF Cloud root CA in the same format. + Do not confuse this CA certificate with your :ref:`self-signed CA certificate `. + See `CA certificates for server authentication in AWS IoT Core`_ for more details. + + Your device needs these three credentials to connect successfully with nRF Cloud. + +#. :ref:`Manually onboard the device to nRF Cloud ` + +#. Follow the instructions under :ref:`nrf_cloud_ble_gateway_build_hardcoded`. + +.. _nrf_cloud_ble_gateway_create_self_signed_ca: + +Creating a self-signed CA certificate for device certificate signing +==================================================================== + +Before a device can connect to nRF Cloud, it must have device credentials. +Unless you are using `Just-in-Time provisioning `_, you need to sign these credentials yourself using a CA certificate you create. +This is referred to as your self-signed CA certificate. + +To create your self-signed CA certificate: + +1. :ref:`Download and install ` the `nRF Cloud Utilities `_ repository. +#. Use the nRF Cloud Utilities :file:`create_ca_cert.py` Python script to generate the certificate: + + .. code-block:: console + + python3 create_ca_cert.py -c US -f self_ + + Remember to set ``-c`` to your two-letter country code. + See the `Create CA Cert `_ section in the nRF Cloud Utilities documentation for more details. + + You should now have the following three files: + + * :file:`self__ca.pem` + * :file:`self__prv.pem` + * :file:`self__pub.pem` + + Where ```` is your self-signed CA certificate's serial number in hex. + These three files are your self-signed CA certificate and private/public keypair. + You will use them to sign device credentials. + + If you were directed here as part of other instructions, proceed to the next step of those instructions. + + .. note:: + You only need to generate these three files once. + You can be use them to sign as many device credentials as you need. + +.. _nrf_cloud_ble_gateway_create_device_cred_locally: + +Generating device credentials locally +===================================== + +To generate and sign a device credential locally using your :ref:`self-signed CA certificate `, perform the following steps: + +1. Create a globally unique `device ID `_ for the device. + + The ID can be anything you want, as long as it is not prefixed with ``nrf-`` and is globally unique. + Alternatively, if your device is an LTE development kit from Nordic Semiconductor, you can use ``nrf-`` where ```` is the IMEI of the device. + See `nRF Cloud Device ID`_ for details. + + Each device should have its own device ID, and you must use it exactly for all other actions involving that device, otherwise onboarding will fail. + This ID is referred to in other steps using the term *device_id*. + +#. Navigate to the folder where you :ref:`created your self-signed CA certificate `, and use the :file:`create_device_credentials.py` Python script :ref:`you installed ` to generate a self-signed certificate for the device: + + .. parsed-literal:: + :class: highlight + + python3 create_device_credentials.py -ca "self\_\ *self_cert_serial*\ \_ca.pem" -ca_key "self\_\ *self_cert_serial*\ \_prv.pem" -c US -cn "\ *device_id*\ " -f cred\_ + + Where *device_id* is the device ID you created, and the :file:`.pem` files are the self-signed CA certificate and private key files :ref:`you created `. + + Remember to set ``-c`` to your two-letter country code. + See the `Create Device Credentials `_ section in the nRF Cloud Utilities documentation for more details. + + You should now have the following three files: + + * :file:`cred__crt.pem` + * :file:`cred__pub.pem` + * :file:`cred__prv.pem` + + These three files are your device credentials, and can only be used by a single device. + + If you were directed here as part of other instructions, proceed to the next step of those instructions. + + .. important:: + + If an attacker obtains the private key contained in :file:`cred__prv.pem`, they will be able to impersonate your device. + +.. _nrf_cloud_ble_gateway_onboard_device_manually: + +Onboarding a device manually +============================ + +Once you have obtained device credentials for your device, it can be `onboarded `_. + +To onboard devices manually, you can use the `Bulk Onboard Devices `_ page of the nRF Cloud portal as follows: + +1. Create an onboarding CSV file named :file:`_onboard.csv` and give it the following contents: + + .. parsed-literal:: + :class: highlight + + *device_id*\ ,,,,"\ *device_cert*\ " + + Where *device_id* is replaced by the device ID :ref:`you created `, and *device_cert* is replaced by the exact contents of :file:`_crt.pem`. + + For example, if the device ID you created is ``698d4c11-0ccc-4f04-89cd-6882724e3f6f``: + + .. code-block:: none + + 698d4c11-0ccc-4f04-89cd-6882724e3f6f,,,,"-----BEGIN CERTIFICATE----- + sCC8AtbNQhzbp4y01FEzXaf5Fko3Qdq0o5LbuNpVA7S6AKAkjt17QzKJAiGWHakh + RnwzoA2dF4wR0rMP5vR6dqBblaGAA5hN7GE2vPBHTDNZGJ6tZ9dnO6446dg9gGds + eeadE1HdVnUj8nb+7CGm39vJ4fuNk9vogH0nMdxjCnXAinoOMRx8EklQsR747+Gz + sxcdVYuNEb/E2vWBHTDNZGJ6tZC1JC9d6/RC3Vb1JC4tWnK9mk/Jw984ZuYugpMc + 1t9umoGFYCz0nMdxjCnXAbnoOMC5A0RxcWPzxfC5A0RH+j+mwoMxwhgfFY4EhVxp + oCC8labNQhzRC3Vc1JC4tWnK9mpVA7k/o5LbuNpVA7S6AKAkjt17QzKJAiGWHakh + RXwcoAndF4wPzxfC5A0RHponmwBHTDoM7GE2vPBHTDNZGJ6tZ9dnO6446dg9gGds + eefdE1HcVnULbuNpVA7S6AKAkjxjCnt1gH0nMdxjCnXAinoOMRx8EklQsR747+Fz + srm/VYaNEb/E2vPBHTDNZGJ6tZc1JC9d6/RC3Vc1JC4tWnK9mk/Jr984ZuYugpMc + nt9uZTGFYCzZD0FFAA5NAC4i1PARStFycWPzxfC5A0RqodhswoMxwhgfFY4EhVx= + -----END CERTIFICATE----- + " + + Do not attempt to use this example directly, it has been filled with dummy data. + See `nRF Cloud REST API ProvisionDevices`_ for more details. + + If you are onboarding more than a single device at once, you can repeat this format (separated by a newline) for each device you are onboarding. + Alternatively, you can create a separate CSV file for each device you wish to onboard, and upload them individually. + +#. Navigate to the `Bulk Onboard Devices`_ page of the nRF Cloud portal and upload the :file:`_onboard.csv` file you created. + + To get there from the :guilabel:`Dashboard`, click :guilabel:`Devices` under :guilabel:`Device Management` in the navigation pane on the left, then click :guilabel:`Add Devices` and select **Bulk Onboard**. + + Once the `Bulk Onboard Devices`_ page is open, drag in the :file:`_onboard.csv` file and click **Onboard**. + + You should see a message stating that the file was uploaded successfully, and a device with the device ID(s) you created should appear in the `Devices `_ page. + +If you were directed here as part of other instructions, proceed to the next step of those instructions. + +.. _nrf_cloud_ble_gateway_dependencies: + +References +********** + +* `RFC 7252 - The Constrained Application Protocol`_ +* `RFC 7959 - Block-Wise Transfer in CoAP`_ +* `RFC 7049 - Concise Binary Object Representation`_ +* `RFC 8610 - Concise Data Definition Language (CDDL)`_ +* `RFC 8132 - PATCH and FETCH Methods for CoAP`_ +* `RFC 9146 - Connection Identifier for DTLS 1.2`_ + +Dependencies +************ + +This sample uses the following |NCS| libraries and drivers: + +* :ref:`lib_nrf_cloud` +* :ref:`lib_location` +* :ref:`lib_at_host` +* :ref:`lte_lc_readme` +* :ref:`lib_nrf_cloud_alert` +* :ref:`lib_nrf_cloud_log` + +It uses the following `sdk-nrfxlib`_ library: + +* :ref:`nrfxlib:nrf_modem` + +It uses the following Zephyr libraries: + +* :ref:`CoAP ` +* :ref:`CoAP Client ` + +In addition, it uses the following secure firmware component: + +* :ref:`Trusted Firmware-M ` diff --git a/samples/cellular/nrf_cloud_ble_gateway/apricity_gateway_nrf9160_ns.overlay b/samples/cellular/nrf_cloud_ble_gateway/apricity_gateway_nrf9160_ns.overlay new file mode 100644 index 000000000000..194557241a0a --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/apricity_gateway_nrf9160_ns.overlay @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + zephyr,bt-uart=&uart1; + }; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/apricity_gateway_nrf9160_ns.conf b/samples/cellular/nrf_cloud_ble_gateway/boards/apricity_gateway_nrf9160_ns.conf new file mode 100644 index 000000000000..057fb3d976db --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/apricity_gateway_nrf9160_ns.conf @@ -0,0 +1,8 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Apricity +CONFIG_ENTER_52840_MCUBOOT_VIA_BUTTON=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/CMakeLists.txt b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/CMakeLists.txt new file mode 100644 index 000000000000..fd96983f872e --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/CMakeLists.txt @@ -0,0 +1,8 @@ +# Kconfig - APRICITY GATEWAY NRF9160 board configuration +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +zephyr_library() +zephyr_library_sources(nrf52840_reset.c) diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig new file mode 100644 index 000000000000..eedfc09b2a47 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig @@ -0,0 +1,18 @@ +# Apricity Gateway nRF9160 board configuration +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +module=BOARD +module-dep=LOG +module-str=Log level for board +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +config BOARD_NRF52840_GPIO_RESET + bool "Use nRF52840 PCA20035 GPIO reset pin" + default y + help + Use a GPIO pin to reset the nRF52840 controller and let it wait + until all bytes traveling to the H4 device have been received + and drained, thus ensuring communication can begin correctly. diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig.board b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig.board new file mode 100644 index 000000000000..0b3c8207e1a6 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig.board @@ -0,0 +1,15 @@ +# Apricity Gateway nRF9160 board configuration +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +if SOC_NRF9160_SICA + +config BOARD_APRICITY_GATEWAY_NRF9160 + bool "nRF9160 APRICITY GATEWAY" + +config BOARD_APRICITY_GATEWAY_NRF9160_NS + bool "nRF9160 APRICITY GATEWAY non-secure" + +endif # SOC_NRF9160_SICA diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig.defconfig b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig.defconfig new file mode 100644 index 000000000000..af8da70dae3c --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig.defconfig @@ -0,0 +1,60 @@ +# Apricity Gateway nRF9160 board configuration +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +if BOARD_APRICITY_GATEWAY_NRF9160 || BOARD_APRICITY_GATEWAY_NRF9160_NS + +config BOARD + default "apricity_gateway_nrf9160" + +# By default, if we build for a Non-Secure version of the board, +# enable building with TF-M as the Secure Execution Environment. +config BUILD_WITH_TFM + default y if BOARD_NRF9160DK_NRF9160_NS + +if BUILD_WITH_TFM + +# By default, if we build with TF-M, instruct build system to +# flash the combined TF-M (Secure) & Zephyr (Non Secure) image +config TFM_FLASH_MERGED_BINARY + bool + default y + +endif # BUILD_WITH_TFM + +# For the secure version of the board the firmware is linked at the beginning +# of the flash, or into the code-partition defined in DT if it is intended to +# be loaded by MCUboot. If the secure firmware is to be combined with a non- +# secure image (TRUSTED_EXECUTION_SECURE=y), the secure FW image shall always +# be restricted to the size of its code partition. +# For the non-secure version of the board, the firmware +# must be linked into the code-partition (non-secure) defined in DT, regardless. +# Apply this configuration below by setting the Kconfig symbols used by +# the linker according to the information extracted from DT partitions. + +config FLASH_LOAD_SIZE + default $(dt_chosen_reg_size_hex,$(DT_CHOSEN_Z_CODE_PARTITION)) + depends on BOARD_APRICITY_GATEWAY_NRF9160 && TRUSTED_EXECUTION_SECURE + +if BOARD_APRICITY_GATEWAY_NRF9160_NS + +config FLASH_LOAD_OFFSET + default $(dt_chosen_reg_addr_hex,$(DT_CHOSEN_Z_CODE_PARTITION)) + +config FLASH_LOAD_SIZE + default $(dt_chosen_reg_size_hex,$(DT_CHOSEN_Z_CODE_PARTITION)) + +endif # BOARD_APRICITY_GATEWAY_NRF9160_NS + +config BT_HCI_VS + default y if BT + +config BT_WAIT_NOP + default BT && $(dt_nodelabel_enabled,nrf52840_reset) + +config I2C + default $(dt_compat_on_bus,$(DT_COMPAT_NXP_PCAL6408A),i2c) + +endif # BOARD_APRICITY_GATEWAY_NRF9160 || BOARD_APRICITY_GATEWAY_NRF9160_NS diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig.sysbuild b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig.sysbuild new file mode 100644 index 000000000000..d969caac9f7a --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/Kconfig.sysbuild @@ -0,0 +1,17 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +choice BOOTLOADER + default BOOTLOADER_MCUBOOT +endchoice + +menu "Thingy91 configuration" + +if BOARD_APRICITY_GATEWAY_NRF9160_NS + +endif # if BOARD_APRICITY_GATEWAY_NRF9160_NS + +endmenu diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160.dts b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160.dts new file mode 100644 index 000000000000..b9724ab24b0c --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160.dts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/dts-v1/; +#include +#include "apricity_gateway_nrf9160_common.dts" + +/ { + chosen { + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,code-partition = &slot0_partition; + }; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160.yaml b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160.yaml new file mode 100644 index 000000000000..595d702ab954 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160.yaml @@ -0,0 +1,16 @@ +identifier: apricity_gateway_nrf9160 +name: Apricity-Gateway-NRF9160 +type: mcu +arch: arm +toolchain: + - gnuarmemb + - xtools + - zephyr +ram: 88 +flash: 1024 +supported: + - gpio + - i2c + - pwm + - spi + - counter diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_common.dts b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_common.dts new file mode 100644 index 000000000000..d36ad93eff12 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_common.dts @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + model = "Apricity Gateway nRF9160"; + compatible = "apricity,apricity-gateway-nrf9160"; + + chosen { + zephyr,console = &uart0; + zephyr,shell-uart = &uart0; + zephyr,uart-mcumgr = &uart0; + zephyr,bt-hci=&bt_hci_uart; + }; + + buttons { + compatible = "gpio-keys"; + + button0: button_0 { + gpios = <&gpio0 17 0>; + label = "Button 1"; + }; + }; + + leds { + compatible = "gpio-leds"; + red_led: led_1 { + gpios = <&gpio0 29 0>; + label = "RGB red channel"; + }; + green_led: led_2 { + gpios = <&gpio0 30 0>; + label = "RGB green channel"; + }; + blue_led: led_3 { + gpios = <&gpio0 31 0>; + label = "RGB blue channel"; + }; + red_led_2: led_4 { + gpios = <&gpio0 26 0>; + label = "RGB red 2 channel"; + }; + green_led_2: led_5 { + gpios = <&gpio0 27 0>; + label = "RGB green 2 channel"; + }; + blue_led_2: led_6 { + gpios = <&gpio0 28 0>; + label = "RGB blue 2 channel"; + }; + }; + + interface_to_nrf52840: gpio-interface { + compatible = "nordic,nrf9160dk-nrf52840-interface"; + #gpio-cells = <2>; + gpio-map-mask = <0xf 0>; + gpio-map-pass-thru = <0 0xffffffff>; + gpio-map = <0 0 &gpio0 18 0>, + <1 0 &gpio0 19 0>, + <2 0 &gpio0 20 0>, + <3 0 &gpio0 21 0>, + <4 0 &gpio0 22 0>, + <5 0 &gpio0 23 0>, + /* 6: COEX0 */ + /* 7: COEX1 */ + /* 8: COEX2 */ + <9 0 &gpio0 13 0>, + <10 0 &gpio0 5 0>, + <11 0 &gpio0 9 0>; + }; + + nrf52840_reset: gpio-reset { + compatible = "nordic,nrf9160dk-nrf52840-reset"; + status = "okay"; + /* + * This line is specified as active high for compatibility + * with the previously used Kconfig-based configuration. + */ + gpios = <&interface_to_nrf52840 9 GPIO_ACTIVE_HIGH>; + }; + + nrf52840_boot: gpio-boot { + compatible = "nordic,nrf9160dk-nrf52840-boot"; + status = "okay"; + gpios = <&interface_to_nrf52840 10 GPIO_PULL_UP>; + }; + + ext_mem_ctrl: gpio-ext-mem-ctrl { + compatible = "nordic,ext-mem-ctrl"; + status = "okay"; + gpios = <&interface_to_nrf52840 11 0>; + }; + + aliases { + sw0 = &button0; + led0 = &red_led; + led1 = &green_led; + led2 = &blue_led; + led0-1 = &red_led_2; + led1-1 = &green_led_2; + led2-1 = &blue_led_2; + rgb-pwm = &pwm0; + rgb2-pwm = &pwm1; + }; +}; + +&adc { + status = "okay"; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +/* PWM0 is intended for RGB LED control */ +&pwm0 { + compatible = "nordic,nrf-pwm"; + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-1 = <&pwm0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +/* PWM1 is intended for RGB LED control */ +&pwm1 { + compatible = "nordic,nrf-pwm"; + status = "okay"; + pinctrl-0 = <&pwm1_default>; + pinctrl-1 = <&pwm1_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi3 { + compatible = "nordic,nrf-spim"; + status = "okay"; + pinctrl-0 = <&spi3_default_alt>; + pinctrl-1 = <&spi3_sleep_alt>; + pinctrl-names = "default", "sleep"; + cs-gpios = <&gpio0 8 1>; + w25q64jv: w25q64jv@0 { + status = "okay"; + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <80000000>; + jedec-id = [ef 40 17]; + size = <67108864>; + }; +}; + +&pinctrl { + spi3_default_alt: spi3_default_alt { + group1 { + psels = , + , + ; + nordic,drive-mode = ; + }; + }; + + spi3_sleep_alt: spi3_sleep_alt { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + pwm0_default: pwm0_default { + group1 { + psels = , + , + ; + nordic,invert; + }; + }; + + pwm0_sleep: pwm0_sleep { + group1 { + psels = , + , + ; + low-power-enable; + nordic,invert; + }; + }; + + pwm1_default: pwm1_default { + group1 { + psels = , + , + ; + nordic,invert; + }; + }; + + pwm1_sleep: pwm1_sleep { + group1 { + psels = , + , + ; + low-power-enable; + nordic,invert; + }; + }; + + uart0_default: uart0_default { + group1 { + psels = , + , + , + ; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + psels = , + , + , + ; + low-power-enable; + }; + }; + + uart1_default: uart1_default { + group1 { + psels = , + , + , + ; + }; + }; + + uart1_sleep: uart1_sleep { + group1 { + psels = , + , + , + ; + low-power-enable; + }; + }; +}; + +&uart0 { + compatible = "nordic,nrf-uarte"; + current-speed = <115200>; + status = "okay"; + pinctrl-0 = <&uart0_default>; + pinctrl-1 = <&uart0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart1 { + compatible = "nordic,nrf-uarte"; + current-speed = <1000000>; + status = "okay"; + hw-flow-control; + pinctrl-0 = <&uart1_default>; + pinctrl-1 = <&uart1_sleep>; + pinctrl-names = "default", "sleep"; + bt_hci_uart: bt_hci_uart { + compatible = "zephyr,bt-hci-uart"; + status = "okay"; + }; +}; + +&flash0 { + /* + * For more information, see: + * http://docs.zephyrproject.org/devices/dts/flash_partitions.html + */ + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + boot_partition: partition@0 { + label = "mcuboot"; + reg = <0x00000000 0x10000>; + }; + slot0_partition: partition@10000 { + label = "image-0"; + }; + slot0_ns_partition: partition@50000 { + label = "image-0-nonsecure"; + }; + slot1_partition: partition@80000 { + label = "image-1"; + }; + slot1_ns_partition: partition@c0000 { + label = "image-1-nonsecure"; + }; + scratch_partition: partition@f0000 { + label = "image-scratch"; + reg = <0x000f0000 0xa000>; + }; + /* 0xf0000 to 0xf7fff reserved for TF-M partitions */ + storage_partition: partition@fa000 { + label = "storage"; + reg = <0x000f8000 0x00008000>; + }; + }; +}; + +/ { + + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + sram0_s: image_s@20000000 { + /* Secure image memory */ + }; + + sram0_bsd: image_modem@20010000 { + /* Modem (shared) memory */ + }; + + sram0_ns: image_ns@20020000 { + /* Non-Secure image memory */ + }; + }; +}; + +/* Include partition configuration file */ +#include "apricity_gateway_nrf9160_partition_conf.dts" diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_defconfig b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_defconfig new file mode 100644 index 000000000000..0c1655816494 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_defconfig @@ -0,0 +1,28 @@ +CONFIG_SOC_SERIES_NRF91X=y +CONFIG_SOC_NRF9160_SICA=y +CONFIG_BOARD_APRICITY_GATEWAY_NRF9160=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# Enable hardware stack protection +CONFIG_HW_STACK_PROTECTION=y + +# Enable TrustZone-M +CONFIG_ARM_TRUSTZONE_M=y + +# Enable PINCTRL +CONFIG_PINCTRL=y + +# Enable GPIO +CONFIG_GPIO=y + +# Enable UARTE +CONFIG_SERIAL=y + +# Enable console +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y + +# Disable entropy driver, as it's not yet implemented for nRF9160 +CONFIG_ENTROPY_NRF5_RNG=n diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_ns.dts b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_ns.dts new file mode 100644 index 000000000000..a74979467523 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_ns.dts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/dts-v1/; +#include +#include "apricity_gateway_nrf9160_common.dts" + +/ { + chosen { + zephyr,flash = &flash0; + zephyr,sram = &sram0_ns; + zephyr,code-partition = &slot0_ns_partition; + }; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_ns.yaml b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_ns.yaml new file mode 100644 index 000000000000..0b6839722f22 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_ns.yaml @@ -0,0 +1,16 @@ +identifier: apricity_gateway_nrf9160_ns +name: APRICITY_GATEWAY-nRF9160-Non-Secure +type: mcu +arch: arm +toolchain: + - gnuarmemb + - zephyr +ram: 128 +flash: 256 +supported: + - serial + - spi + - i2c + - pwm + - netif:modem + - gpio diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_ns_defconfig b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_ns_defconfig new file mode 100644 index 000000000000..bd35a4c5c36c --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_ns_defconfig @@ -0,0 +1,39 @@ +CONFIG_SOC_SERIES_NRF91X=y +CONFIG_SOC_NRF9160_SICA=y +CONFIG_BOARD_APRICITY_GATEWAY_NRF9160_NS=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# Enable hardware stack protection +CONFIG_HW_STACK_PROTECTION=y + +# Enable TrustZone-M +CONFIG_ARM_TRUSTZONE_M=y + +# This Board implies building Non-Secure firmware +CONFIG_TRUSTED_EXECUTION_NONSECURE=y + +# Enable PINCTRL +CONFIG_PINCTRL=y + +# Enable GPIO +CONFIG_GPIO=y + +# Enable UARTE +CONFIG_SERIAL=y + +# Enable console +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y + +# Disable entropy driver, as it's not yet implemented for nRF9160 +CONFIG_ENTROPY_NRF5_RNG=n + +# Enable SPI +CONFIG_SPI=y + +# Disable entropy driver, as it's not yet implemented for nRF9160 +CONFIG_ENTROPY_NRF5_RNG=n + +CONFIG_BOOTLOADER_MCUBOOT=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_partition_conf.dts b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_partition_conf.dts new file mode 100644 index 000000000000..505b07fc6028 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/apricity_gateway_nrf9160_partition_conf.dts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* + * Default Flash planning for apricity_gateway_nrf9160. + * + * Zephyr build for nRF9160 with ARM TrustZone-M support, + * implies building Secure and Non-Secure Zephyr images. + * + * Secure image will be placed, by default, in flash0 + * (or in slot0, if MCUboot is present). + * Secure image will use sram0 for system memory. + * + * Non-Secure image will be placed in slot0_ns, and use + * sram0_ns for system memory. + * + * Note that the Secure image only requires knowledge of + * the beginning of the Non-Secure image (not its size). + */ + +&slot0_partition { + reg = <0x00010000 0x40000>; +}; + +&slot0_ns_partition { + reg = <0x00050000 0x30000>; +}; + +&slot1_partition { + reg = <0x00080000 0x40000>; +}; + +&slot1_ns_partition { + reg = <0x000c0000 0x30000>; +}; + +/* Default SRAM planning when building for nRF9160 with + * ARM TrustZone-M support + * - Lowest 88 kB SRAM allocated to Secure image (sram0_s). + * - 40 kB SRAM reserved for and used by the modem library + * (sram0_modem). This memory is Non-Secure. + * - Upper 128 kB allocated to Non-Secure image (sram0_ns). + * When building with TF-M, both sram0_modem and sram0_ns + * are allocated to the Non-Secure image. + */ + +&sram0_s { + reg = <0x20000000 DT_SIZE_K(88)>; +}; + +&sram0_bsd { + reg = <0x20016000 DT_SIZE_K(40)>; +}; + +&sram0_ns { + reg = <0x20020000 DT_SIZE_K(128)>; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/board.cmake b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/board.cmake new file mode 100644 index 000000000000..3452af649bcc --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/board.cmake @@ -0,0 +1,11 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA. +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +if(CONFIG_TFM_FLASH_MERGED_BINARY) + set_property(TARGET runners_yaml_props_target PROPERTY hex_file tfm_merged.hex) +endif() + +board_runner_args(jlink "--device=cortex-m33" "--speed=4000") +board_runner_args(nrfjprog "--softreset") +include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) +include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/dts/bindings/nordic,nrf9160dk-nrf52840-boot.yaml b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/dts/bindings/nordic,nrf9160dk-nrf52840-boot.yaml new file mode 100644 index 000000000000..82b332dc562e --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/dts/bindings/nordic,nrf9160dk-nrf52840-boot.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +# NOTE: This file is replicated in nrf9160dk_nrf9160 and nrf9160dk_nrf52840. +# Any changes should be done in both instances. + +description: GPIO used to control boot mode for nRF52840 on nRF9160 DK + +compatible: "nordic,nrf9160dk-nrf52840-boot" + +include: base.yaml + +properties: + status: + required: true + + gpios: + type: phandle-array + required: true + description: | + GPIO to use as nRF52840 boot mode line: output in nRF9160, input in nRF52840. diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/dts/bindings/nordic,nrf9160dk-nrf52840-interface.yaml b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/dts/bindings/nordic,nrf9160dk-nrf52840-interface.yaml new file mode 100644 index 000000000000..5a841580581a --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/dts/bindings/nordic,nrf9160dk-nrf52840-interface.yaml @@ -0,0 +1,42 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +# NOTE: This file is replicated in nrf9160dk_nrf9160 and nrf9160dk_nrf52840. +# Any changes should be done in both instances. + +description: | + nRF9160 DK GPIO interface between nRF9160 and nRF52840 + + This interface can be used for inter-SoC communication on the DK. + The connections are as follows: + + | nRF9160 | | nRF52840 | + | P0.17 | -- nRF interface line 0 -- | P0.17 | + | P0.18 | -- nRF interface line 1 -- | P0.20 | + | P0.19 | -- nRF interface line 2 -- | P0.15 | + | P0.21 | -- nRF interface line 3 -- | P0.22 | + | P0.22 | -- nRF interface line 4 -- | P1.04 | + | P0.23 | -- nRF interface line 5 -- | P1.02 | + | COEX0 | -- nRF interface line 6 -- | P1.13 | + | COEX1 | -- nRF interface line 7 -- | P1.11 | + | COEX2 | -- nRF interface line 8 -- | P1.15 | + | P0.24 | -- nRF interface line 9 -- | P0.18 (nRESET) | (in v0.14.0 or later) + + Before particular lines of this interface can be used, the corresponding + analog switches that control the routing of involved nRF9160 pins must be + configured to provide the optional routing (i.e. to nRF52840). To achieve + this, set the status of respective devicetree nodes in the firmware for + the nrf9160dk_nrf52840 board to "okay": + - `nrf_interface_pins_0_2_routing` to enable lines 0-2 + - `nrf_interface_pins_3_5_routing` to enable lines 3-5 + - `nrf_interface_pins_6_8_routing` to enable lines 6-8 + - `nrf_interface_pin_9_routing` to enable line 9 (this line is only + available in nRF9160 DK v0.14.0 or later) + + NOTE: In nRF9160 DK revisions earlier than v0.14.0, when the above signals + from nRF9160 are routed to nRF52840, they are not available on the DK + connectors. + +compatible: "nordic,nrf9160dk-nrf52840-interface" + +include: [gpio-nexus.yaml, base.yaml] diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/dts/bindings/nordic,nrf9160dk-nrf52840-reset.yaml b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/dts/bindings/nordic,nrf9160dk-nrf52840-reset.yaml new file mode 100644 index 000000000000..22ee6a22529f --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/dts/bindings/nordic,nrf9160dk-nrf52840-reset.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +# NOTE: This file is replicated in nrf9160dk_nrf9160 and nrf9160dk_nrf52840. +# Any changes should be done in both instances. + +description: GPIO used to reset nRF52840 on nRF9160 DK + +compatible: "nordic,nrf9160dk-nrf52840-reset" + +include: base.yaml + +properties: + status: + required: true + + gpios: + type: phandle-array + required: true + description: | + GPIO to use as nRF52840 reset line: output in nRF9160, input in nRF52840. diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/nrf52840_reset.c b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/nrf52840_reset.c new file mode 100644 index 000000000000..75bd8f3038c9 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/nrf52840_reset.c @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA. + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#define RESET_NODE DT_NODELABEL(nrf52840_reset) +#define BOOT_NODE DT_NODELABEL(nrf52840_boot) + +#if DT_NODE_HAS_STATUS(RESET_NODE, okay) && DT_NODE_HAS_STATUS(BOOT_NODE, okay) + +#define RESET_GPIO_CTRL DT_GPIO_CTLR(RESET_NODE, gpios) +#define RESET_GPIO_PIN DT_GPIO_PIN(RESET_NODE, gpios) +#define RESET_GPIO_FLAGS DT_GPIO_FLAGS(RESET_NODE, gpios) + +#define BOOT_GPIO_CTRL DT_GPIO_CTLR(BOOT_NODE, gpios) +#define BOOT_GPIO_PIN DT_GPIO_PIN(BOOT_NODE, gpios) +#define BOOT_GPIO_FLAGS DT_GPIO_FLAGS(BOOT_NODE, gpios) + +#define WAIT_BOOT_INTERVAL_MS 50 + +static int nrf52840_reset_assert(bool boot_select) +{ + const struct device *reset_port = DEVICE_DT_GET(RESET_GPIO_CTRL); + const struct device *boot_port = DEVICE_DT_GET(BOOT_GPIO_CTRL); + int err; + + if (!reset_port) { + printk("reset_port not found!\n"); + return -EIO; + } + + if (!boot_port) { + printk("boot_port not found!\n"); + return -EIO; + } + + if (!device_is_ready(reset_port)) { + printk("reset_port is not ready!\n"); + return -EIO; + } + + if (!device_is_ready(boot_port)) { + printk("boot_port is not ready!\n"); + return -EIO; + } + + /* Configure pin as output and initialize it to low. */ + err = gpio_pin_configure(reset_port, RESET_GPIO_PIN, GPIO_OUTPUT_LOW); + if (err) { + printk("Reset pin could not be configured! %d\n", err); + return err; + } + + if (boot_select) { + printk("Resetting nrf52840 in MCUboot USB serial update mode\n"); + /* delay to ensure logging finishes before reset */ + k_sleep(K_SECONDS(2)); + } else { + printk("Resetting nrf52840 for normal ops.\n"); + } + err = gpio_pin_configure(boot_port, BOOT_GPIO_PIN, GPIO_OUTPUT | + GPIO_OPEN_DRAIN | GPIO_PULL_UP); + if (err) { + printk("Boot pin could not be configured! %d\n", err); + return err; + } + + err = gpio_pin_set(boot_port, BOOT_GPIO_PIN, boot_select ? 0 : 1); + if (err) { + printk("Boot pin could not be set! %d\n", err); + return err; + } + + err = gpio_pin_set(reset_port, RESET_GPIO_PIN, 1); + if (err) { + printk("Reset pin could not be reset! %d\n", err); + return err; + } + printk("Reset the 52840 with boot set to %d\n", boot_select); + return 0; +} + +static int nrf52840_boot_select_deassert(void) +{ + const struct device *boot_port = DEVICE_DT_GET(BOOT_GPIO_CTRL); + int err; + + if (!boot_port) { + printk("boot_port not found!\n"); + return -EIO; + } + + if (!device_is_ready(boot_port)) { + printk("boot_port is not ready!\n"); + return -EIO; + } + + err = gpio_pin_set(boot_port, BOOT_GPIO_PIN, 1); + if (err) { + printk("Boot pin could not be set! %d\n", err); + return err; + } + + k_sleep(K_MSEC(10)); + + err = gpio_pin_configure(boot_port, BOOT_GPIO_PIN, + GPIO_INPUT | GPIO_PULL_UP); + if (err) { + printk("Boot pin could not be configured! %d\n", err); + return err; + } + printk("Deasserted 52840 boot pin\n"); + return 0; +} + +static int nrf52840_reset_deassert(void) +{ + const struct device *reset_port = DEVICE_DT_GET(RESET_GPIO_CTRL); + int err; + + if (!reset_port) { + printk("reset_port not found!\n"); + return -EIO; + } + + if (!device_is_ready(reset_port)) { + printk("reset_port is not ready!\n"); + return -EIO; + } + + err = gpio_pin_set(reset_port, RESET_GPIO_PIN, 0); + if (err) { + printk("Reset pin could not be reset! %d\n", err); + return err; + } + printk("Deasserted 52840 reset\n"); + return 0; +} + +int nrf52840_wait_boot_complete(int timeout_ms) +{ + const struct device *boot_port = DEVICE_DT_GET(BOOT_GPIO_CTRL); + int total_time; + int err; + + if (!boot_port) { + printk("boot_port not found!\n"); + return -EIO; + } + + if (!device_is_ready(boot_port)) { + printk("boot_port is not ready!\n"); + return -EIO; + } + + /* make the boot select pin a pulled up input, so we can + * read it to see when the 52840 has rebooted into its application + */ + err = gpio_pin_configure(boot_port, BOOT_GPIO_PIN, GPIO_INPUT | GPIO_PULL_UP); + if (err) { + printk("Boot pin could not be configured! %d\n", err); + return err; + } + + printk("Waiting for 52840 to boot...\n"); + total_time = 0; + do { + k_sleep(K_MSEC(WAIT_BOOT_INTERVAL_MS)); + total_time += WAIT_BOOT_INTERVAL_MS; + err = gpio_pin_get_raw(boot_port, BOOT_GPIO_PIN); + if (err < 0) { + return err; + } + if (err && (timeout_ms > 0) && (total_time > timeout_ms)) { + return -ETIMEDOUT; + } + } while (err == 1); + + printk("52840 boot is complete\n"); + return 0; +} + +int nrf52840_reset_to_mcuboot(void) +{ + int err; + + printk("Reset 52840 into MCUboot mode:\n"); + err = nrf52840_reset_assert(true); + if (err) { + return err; + } + k_sleep(K_MSEC(10)); + + err = nrf52840_reset_deassert(); + if (err) { + return err; + } + + k_sleep(K_SECONDS(5)); + return nrf52840_boot_select_deassert(); +} + +int bt_hci_transport_setup(struct device *h4) +{ + char c; + int err; + + /* Reset the nRF52840 and let it wait until the pin is + * pulled low again before running to main to ensure + * that it won't send any data until the H4 device + * is setup and ready to receive. + */ + err = nrf52840_reset_assert(false); + if (err) { + return err; + } + + /* Wait for the nRF52840 peripheral to stop sending data. + * + * It is critical (!) to wait here, so that all bytes + * on the lines are received and drained correctly. + */ + k_sleep(K_MSEC(10)); + + /* Drain bytes */ + while (h4 && uart_fifo_read(h4, &c, 1)) { + continue; + } + + /* We are ready, let the nRF52840 run to main */ + nrf52840_reset_deassert(); + + return 0; +} +#else +#warning "Reset and/or boot node is missing" +#endif /* DT_NODE_HAS_STATUS(RESET_NODE, okay) && DT_NODE_HAS_STATUS(BOOT_NODE, okay) */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/pre_dt_board.cmake b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/pre_dt_board.cmake new file mode 100644 index 000000000000..4959e498ccfe --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/arm/apricity_gateway_nrf9160/pre_dt_board.cmake @@ -0,0 +1,7 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +# Suppress "unique_unit_address_if_enabled" to handle the following overlaps: +# - flash-controller@39000 & kmu@39000 +# - power@5000 & clock@5000 +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9151dk_nrf9151_ns.conf b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9151dk_nrf9151_ns.conf new file mode 100644 index 000000000000..999fc9fc5313 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9151dk_nrf9151_ns.conf @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# This file is merged with prj.conf in the application folder, and options +# set here will take precedence if they are present in both files. + +CONFIG_GPIO=y +CONFIG_LED_GPIO=y + +# Enable external flash +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_SFDP_DEVICETREE=y +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9151dk_nrf9151_ns.overlay b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9151dk_nrf9151_ns.overlay new file mode 100644 index 000000000000..fd6b8357905e --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9151dk_nrf9151_ns.overlay @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + /* Configure partition manager to use gd25wb256 as the external flash */ + chosen { + nordic,pm-ext-flash = &gd25wb256; + }; +}; + +&gd25wb256 { + status = "okay"; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_0_14_0.overlay b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_0_14_0.overlay new file mode 100644 index 000000000000..6d30ec7279d1 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_0_14_0.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +/* + * #include + * #include + * #include + */ +#include + +&nrf52840_reset { + status = "okay"; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_ns.conf b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_ns.conf new file mode 100644 index 000000000000..b3544793546d --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_ns.conf @@ -0,0 +1,18 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# 9160DK +#CONFIG_NRF_SW_LPUART=y +#CONFIG_NRF_SW_LPUART_INT_DRIVEN=y + +# UART_2/LPUART for use with nrf/samples/bluetooth/hci_lpuart on 52840 +#CONFIG_UART_2_ASYNC=y +CONFIG_UART_2_INTERRUPT_DRIVEN=y +#CONFIG_UART_2_NRF_HW_ASYNC=y +#CONFIG_UART_2_NRF_HW_ASYNC_TIMER=2 + +#CONFIG_NRF_CLOUD_HOST_NAME="mqtt.beta.nrfcloud.com" +#CONFIG_NRF_CLOUD_SEC_TAG=44 diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_ns.overlay b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_ns.overlay new file mode 100644 index 000000000000..eaa0e8ac2cd6 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_ns.overlay @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +/* Use the reset line that is available until v0.14.0 of the DK. */ +#include + +/ { + chosen { + zephyr,bt-hci=&bt_hci_uart; + }; +}; + +&gpiote { + interrupts = <49 NRF_DEFAULT_IRQ_PRIORITY>; +}; + +&uart2 { + current-speed = <1000000>; + status = "okay"; + hw-flow-control; + + pinctrl-0 = <&uart2_default_alt>; + pinctrl-1 = <&uart2_sleep_alt>; + pinctrl-names = "default", "sleep"; + bt_hci_uart: bt_hci_uart { + compatible = "zephyr,bt-hci-uart"; + status = "okay"; + }; +}; + +&nrf52840_reset { + status = "okay"; +}; + +&pinctrl { + uart2_default_alt: uart2_default_alt { + group1 { + psels = , + ; + }; + group2 { + psels = , + ; + bias-pull-up; + }; + }; + + uart2_sleep_alt: uart2_sleep_alt { + group1 { + psels = , + , + , + ; + low-power-enable; + }; + }; + +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_ns_0_14_0.overlay b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_ns_0_14_0.overlay new file mode 100644 index 000000000000..00352ac3d37d --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9160dk_nrf9160_ns_0_14_0.overlay @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +/* Use the reset line that is available starting from v0.14.0 of the DK. */ +#include + +/ { + chosen { + nordic,pm-ext-flash = &mx25r64; + }; +}; + +/* External flash device is disabled by default */ +&mx25r64 { + status = "okay"; +}; + +/* Enable high drive mode for the SPI3 pins to get a square signal at 8 MHz */ +&spi3_default { + group1 { + nordic,drive-mode = ; + }; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9161dk_nrf9161_ns.conf b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9161dk_nrf9161_ns.conf new file mode 100644 index 000000000000..65ab00f34619 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9161dk_nrf9161_ns.conf @@ -0,0 +1,17 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# This file is merged with prj.conf in the application folder, and options +# set here will take precedence if they are present in both files. + +CONFIG_GPIO=y +CONFIG_LED_GPIO=y + +# Enable external flash +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_SFDP_DEVICETREE=y +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9161dk_nrf9161_ns.overlay b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9161dk_nrf9161_ns.overlay new file mode 100644 index 000000000000..640c27c87411 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9161dk_nrf9161_ns.overlay @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + /* Configure partition manager to use gd25wb256 as the external flash */ + chosen { + nordic,pm-ext-flash = &gd25wb256; + }; +}; + +&gd25wb256 { + status = "okay"; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9161dk_nrf9161_ns_0_7_0.overlay b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9161dk_nrf9161_ns_0_7_0.overlay new file mode 100644 index 000000000000..09168893d754 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/nrf9161dk_nrf9161_ns_0_7_0.overlay @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/delete-node/ &gd25wb256; + +/ { + chosen { + nordic,pm-ext-flash = &gd25lb256; + }; + + aliases { + ext-flash = &gd25lb256; + }; +}; + +&gd25lb256 { + status = "okay"; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91_nrf9160_ns.conf b/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91_nrf9160_ns.conf new file mode 100644 index 000000000000..be6571b00696 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91_nrf9160_ns.conf @@ -0,0 +1,31 @@ +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Configuration file for Thingy:91. +# This file is merged with prj.conf in the application folder, and options +# set here will take precedence if they are present in both files. + +# Configuration related to external sensors. +CONFIG_SENSOR=y +CONFIG_SPI=y + +# ADXL362 - Low-Power accelerometer used for activity and inactivity detection. +CONFIG_ADXL362=y +CONFIG_ADXL362_TRIGGER_GLOBAL_THREAD=y +CONFIG_ADXL362_INTERRUPT_MODE=1 +CONFIG_ADXL362_ABS_REF_MODE=1 +CONFIG_ADXL362_ACCEL_RANGE_2G=y +CONFIG_ADXL362_ACCEL_ODR_400=y + +# BME680 - Temperature and humidity sensor. +CONFIG_BME680=y + +CONFIG_PWM=y +CONFIG_LED_PWM=y + +# Disable MCUboot DFU -- incompatible with static partitions +CONFIG_SECURE_BOOT=n +CONFIG_BUILD_S1_VARIANT=n diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91_nrf9160_ns.overlay b/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91_nrf9160_ns.overlay new file mode 100644 index 000000000000..e1b15bbefaaa --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91_nrf9160_ns.overlay @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + aliases { + temp-sensor = &bme680; + humidity-sensor = &bme680; + pressure-sensor = &bme680; + iaq-sensor = &bme680; + accelerometer = &adxl362; + impact-sensor = &adxl372; + }; +}; + +&i2c2 { + bme680: bme680@76 {}; +}; + +&spi3 { + adxl362: adxl362@0 { + autosleep; + }; + + adxl372: adxl372@1 { + odr = <4>; + bw = <4>; + hpf = <0>; + }; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91x_nrf9151_ns.conf b/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91x_nrf9151_ns.conf new file mode 100644 index 000000000000..c53a1342a43c --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91x_nrf9151_ns.conf @@ -0,0 +1,75 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Configuration file for Thingy:91 X. +# This file is merged with prj.conf in the application folder, and options +# set here will take precedence if they are present in both files. + +# BME680 - Temperature and humidity sensor. +CONFIG_BME680=y + +# configs for Wi-Fi +CONFIG_WIFI=y +CONFIG_WIFI_NRF70=y +CONFIG_WIFI_NRF70_SKIP_LOCAL_ADMIN_MAC=y +# Align this with CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT +CONFIG_NRF_WIFI_SCAN_MAX_BSS_CNT=10 + +# Wi-Fi location +CONFIG_LOCATION_TRACKING_WIFI=y +CONFIG_LOCATION_METHOD_WIFI=y +CONFIG_LOCATION_REQUEST_DEFAULT_METHOD_FIRST_GNSS=y +CONFIG_LOCATION_REQUEST_DEFAULT_METHOD_SECOND_WIFI=y +CONFIG_LOCATION_REQUEST_DEFAULT_METHOD_THIRD_CELLULAR=y + +# Align this with CONFIG_NRF_WIFI_SCAN_MAX_BSS_CNT +CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT=10 + +# Not for LTE throughput testing +CONFIG_NRF_MODEM_LIB_SHMEM_TX_SIZE=4096 +CONFIG_NRF_MODEM_LIB_SHMEM_RX_SIZE=4096 + +# Scan only using offload API +CONFIG_WIFI_NM_WPA_SUPPLICANT=n + +# For nRF9160 the default is socket interface +CONFIG_NET_DEFAULT_IF_ETHERNET=y +CONFIG_MBEDTLS=n +CONFIG_NORDIC_SECURITY_BACKEND=n + +# Networking +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_NATIVE=y +CONFIG_NET_IPV4=y +CONFIG_NET_DHCPV4=y +CONFIG_NET_STATISTICS=y +CONFIG_NET_STATISTICS_WIFI=y +CONFIG_NET_STATISTICS_USER_API=y +CONFIG_NET_CONTEXT_SYNC_RECV=y + +# We need 2 to ensure nrf9x_socket gets an IP address regardless of order of net_if array +CONFIG_NET_IF_MAX_IPV4_COUNT=2 +CONFIG_NET_IF_MAX_IPV6_COUNT=2 + +# Memory configurations +CONFIG_NET_BUF_RX_COUNT=1 +CONFIG_NET_BUF_TX_COUNT=1 +CONFIG_NET_PKT_RX_COUNT=1 +CONFIG_NET_PKT_TX_COUNT=1 +CONFIG_NET_TX_STACK_SIZE=512 +CONFIG_NET_RX_STACK_SIZE=512 +CONFIG_NET_TC_TX_COUNT=1 +CONFIG_NET_MAX_CONTEXTS=5 + +CONFIG_HEAP_MEM_POOL_SIZE=20000 +CONFIG_HEAP_MEM_POOL_IGNORE_MIN=y +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_NET_MGMT_EVENT_STACK_SIZE=1024 + +# Enable external flash +CONFIG_SPI_NOR_SFDP_DEVICETREE=y +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y +CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91x_nrf9151_ns.overlay b/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91x_nrf9151_ns.overlay new file mode 100644 index 000000000000..359ed5ae667a --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/boards/thingy91x_nrf9151_ns.overlay @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +/ { + aliases { + temp-sensor = &bme680; + ext-flash = &flash_ext; + }; + + chosen { + zephyr,wifi = &nordic_wlan0; + }; +}; + +&bme680 { + status = "okay"; +}; + +/* Switch to nrf7000 emulation so that scan-only mode is used. */ +&nrf70 { + compatible = "nordic,nrf7000-spi"; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay-coap_nrf_provisioning.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay-coap_nrf_provisioning.conf new file mode 100644 index 000000000000..78178da573c5 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay-coap_nrf_provisioning.conf @@ -0,0 +1,65 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Enable provisioning library/provisioning shell dependencies +CONFIG_MODEM_JWT=y +CONFIG_MODEM_ATTEST_TOKEN=y + +# Enable the provisioning library +CONFIG_NRF_PROVISIONING=y +CONFIG_NRF_PROVISIONING_AUTO_INIT=n +CONFIG_NRF_PROVISIONING_COAP=y + +# Enable provisioning shell +CONFIG_SHELL=y +CONFIG_NRF_PROVISIONING_SHELL=y + +# Adjust shell buffer sizes to handle nRF Cloud certs +CONFIG_SHELL_BACKEND_SERIAL_RX_RING_BUFFER_SIZE=4096 +CONFIG_SHELL_CMD_BUFF_SIZE=4096 +CONFIG_SHELL_BACKEND_SERIAL_API_INTERRUPT_DRIVEN=y + +# Disable AT host and switch to using shell +CONFIG_AT_HOST_LIBRARY=n +CONFIG_AT_SHELL=y +CONFIG_SHELL_WILDCARD=n + +# Configure provisioning message formatting +CONFIG_NRF_PROVISIONING_CODEC=y +CONFIG_NRF_PROVISIONING_CBOR=y +CONFIG_ZCBOR=y + +# Include provisioning service certificate as part of the binary. +# Used only if none has been provisioned +CONFIG_NRF_PROVISIONING_WITH_CERT=y +CONFIG_NRF_PROVISIONING_ROOT_CA_SEC_TAG=42 +CONFIG_NRF_CLOUD_CLIENT_ID_SRC_INTERNAL_UUID=y + +# Max provisioning commands per response +CONFIG_NRF_PROVISIONING_CBOR_RECORDS=20 + +# Set the provisioning interval to 60 seconds. +# This is a very frequent interval. To save power, set this to something approaching 24 hours. +# But note that provisioning may take up to twice as long as this interval, since it +# happens in two steps. +CONFIG_NRF_PROVISIONING_INTERVAL_S=60 + +# Request spread factor +CONFIG_NRF_PROVISIONING_SPREAD_S=5 + +# Adjust provisioning and buffer sizes to handle nRF Cloud certs +CONFIG_NRF_PROVISIONING_RX_BUF_SZ=4096 +CONFIG_NRF_PROVISIONING_TX_BUF_SZ=4096 +CONFIG_NRF_PROVISIONING_CODEC_AT_CMD_LEN=2048 +CONFIG_NRF_PROVISIONING_CODEC_RX_SZ_START=2048 + +# CoAP Client +CONFIG_COAP_EXTENDED_OPTIONS_LEN=y +CONFIG_COAP_EXTENDED_OPTIONS_LEN_VALUE=64 +CONFIG_COAP_CLIENT_THREAD_PRIORITY=0 +CONFIG_COAP_CLIENT_BLOCK_SIZE=1024 +CONFIG_COAP_CLIENT_MESSAGE_SIZE=1024 +CONFIG_COAP_CLIENT_STACK_SIZE=6144 diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay-http_nrf_provisioning.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay-http_nrf_provisioning.conf new file mode 100644 index 000000000000..21c25c4a4815 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay-http_nrf_provisioning.conf @@ -0,0 +1,57 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Enable provisioning library/provisioning shell dependencies +CONFIG_MODEM_JWT=y +CONFIG_MODEM_ATTEST_TOKEN=y + +# Enable the provisioning library +CONFIG_NRF_PROVISIONING=y +CONFIG_NRF_PROVISIONING_AUTO_INIT=n +CONFIG_NRF_PROVISIONING_HTTP=y + +# Enable provisioning shell +CONFIG_SHELL=y +CONFIG_NRF_PROVISIONING_SHELL=y + +# Adjust shell buffer sizes to handle nRF Cloud certs +CONFIG_SHELL_BACKEND_SERIAL_RX_RING_BUFFER_SIZE=4096 +CONFIG_SHELL_CMD_BUFF_SIZE=4096 + +# Disable AT host and switch to using shell +CONFIG_AT_HOST_LIBRARY=n +CONFIG_AT_SHELL=y +CONFIG_SHELL_WILDCARD=n + +# Configure provisioning message formatting +CONFIG_NRF_PROVISIONING_CODEC=y +CONFIG_NRF_PROVISIONING_CBOR=y +CONFIG_ZCBOR=y +CONFIG_ZCBOR_CANONICAL=y + +# Include provisioning service certificate as part of the binary. +# Used only if none has been provisioned +CONFIG_NRF_PROVISIONING_WITH_CERT=y +CONFIG_NRF_PROVISIONING_ROOT_CA_SEC_TAG=50 +CONFIG_NRF_CLOUD_CLIENT_ID_SRC_INTERNAL_UUID=y + +# Max provisioning commands per response +CONFIG_NRF_PROVISIONING_CBOR_RECORDS=20 + +# Set the provisioning interval to 60 seconds. +# This is a very frequent interval. To save power, set this to something approaching 24 hours. +# But note that provisioning may take up to twice as long as this interval, since it +# happens in two steps. +CONFIG_NRF_PROVISIONING_INTERVAL_S=60 + +# Request spread factor +CONFIG_NRF_PROVISIONING_SPREAD_S=5 + +# Adjust provisioning and buffer sizes to handle nRF Cloud certs +CONFIG_NRF_PROVISIONING_RX_BUF_SZ=4096 +CONFIG_NRF_PROVISIONING_TX_BUF_SZ=4096 +CONFIG_NRF_PROVISIONING_CODEC_AT_CMD_LEN=2048 +CONFIG_NRF_PROVISIONING_CODEC_RX_SZ_START=2048 diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay-nrf7002ek-wifi-scan-only.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay-nrf7002ek-wifi-scan-only.conf new file mode 100644 index 000000000000..76db31beaa13 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/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_TRACKING_WIFI=y +CONFIG_LOCATION_METHOD_WIFI=y +# Align this with CONFIG_NRF_WIFI_SCAN_MAX_BSS_CNT. +# Also see comments for CONFIG_HEAP_MEM_POOL_SIZE. +CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT=10 + +# Enable Wi-Fi drivers +CONFIG_WIFI=y +CONFIG_WIFI_NRF70=y +CONFIG_WIFI_NRF70_SKIP_LOCAL_ADMIN_MAC=y +# Align this with CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT. +# Also see comments for CONFIG_HEAP_MEM_POOL_SIZE. +CONFIG_NRF_WIFI_SCAN_MAX_BSS_CNT=10 +CONFIG_NET_L2_ETHERNET=y +# We need 2 to ensure nrf9x_socket gets an IP address regardless of order of net_if array +CONFIG_NET_IF_MAX_IPV4_COUNT=2 +CONFIG_NET_IF_MAX_IPV6_COUNT=2 + +# But disable the supplicant, since we don't need connectivity +CONFIG_WIFI_NM_WPA_SUPPLICANT=n +CONFIG_MBEDTLS=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_NRF_WIFI_SCAN_MAX_BSS_CNT (which should be the same value) are changed. +CONFIG_HEAP_MEM_POOL_SIZE=20000 +CONFIG_HEAP_MEM_POOL_IGNORE_MIN=y +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_NET_MGMT_EVENT_STACK_SIZE=1024 + +# NET tweaks needed to support Wi-FI +CONFIG_NET_BUF_RX_COUNT=1 +CONFIG_NET_BUF_TX_COUNT=1 +CONFIG_NET_PKT_RX_COUNT=1 +CONFIG_NET_PKT_TX_COUNT=1 +CONFIG_NET_TX_STACK_SIZE=512 +CONFIG_NET_RX_STACK_SIZE=512 +CONFIG_NET_TC_TX_COUNT=1 +CONFIG_NET_MAX_CONTEXTS=5 + +# Disable LED patterns, enabling WiFi scanning takes control of two LEDs +CONFIG_LED_INDICATION_DISABLED=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay_coap.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay_coap.conf new file mode 100644 index 000000000000..2954beb3748c --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay_coap.conf @@ -0,0 +1,74 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# General config +CONFIG_FPU=y +CONFIG_NEWLIB_LIBC_NANO=n + +# Rate of cloud interactions +# These are faster than one normally would use in a low power device. +# This is strictly for demo purposes. +CONFIG_SENSOR_SAMPLE_INTERVAL_SECONDS=120 +CONFIG_LOCATION_TRACKING_SAMPLE_INTERVAL_SECONDS=360 +CONFIG_COAP_FOTA_JOB_CHECK_RATE_MINUTES=2 +CONFIG_COAP_SHADOW_CHECK_RATE_SECONDS=120 + +# Logs +CONFIG_LOG=y +CONFIG_NET_LOG=y +CONFIG_LOG_PRINTK=y +CONFIG_COAP_LOG_LEVEL_INF=y +CONFIG_LOCATION_LOG_LEVEL_INF=y +CONFIG_NRF_CLOUD_COAP_LOG_LEVEL_INF=y +CONFIG_MULTI_SERVICE_LOG_LEVEL_INF=y + +# LTE link control - used by PGPS and main application +CONFIG_LTE_LINK_CONTROL=y + +# Modem +CONFIG_MODEM_KEY_MGMT=y +CONFIG_MODEM_JWT=y +CONFIG_MODEM_INFO_ADD_DEVICE=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 + +# Network +CONFIG_POSIX_API=y +CONFIG_NET_SOCKETS_TLS_SET_MAX_FRAGMENT_LENGTH=y +CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY=40 + +# CoAP Client +CONFIG_COAP_CLIENT_BLOCK_SIZE=1024 +CONFIG_COAP_CLIENT_STACK_SIZE=2048 +CONFIG_COAP_CLIENT_THREAD_PRIORITY=0 +CONFIG_COAP_CLIENT_MAX_INSTANCES=3 +# The extended options length must be increased to perform FOTA and P-GPS downloads over CoAP. +CONFIG_COAP_EXTENDED_OPTIONS_LEN_VALUE=192 + +# Not compatible with ground-fix +CONFIG_ZCBOR_CANONICAL=n + +# nRF Cloud +CONFIG_NRF_CLOUD_MQTT=n +CONFIG_NRF_CLOUD_COAP=y +CONFIG_NRF_CLOUD_ALERT=y +CONFIG_NRF_CLOUD_LOG_DIRECT=y +CONFIG_NRF_CLOUD_LOG_OUTPUT_LEVEL=3 +CONFIG_NRF_CLOUD_LOG_LOG_LEVEL_INF=y +CONFIG_NRF_CLOUD_FOTA_POLL=y + +# Disable MQTT-specific services; equivalent CoAP versions are used instead. +CONFIG_NRF_CLOUD_FOTA=n +CONFIG_NRF_CLOUD_LOCATION=n + +# Location Services configuration +CONFIG_LOCATION_DATA_DETAILS=y + +# Sample configuration +CONFIG_APPLICATION_THREAD_STACK_SIZE=4096 +CONFIG_TEMP_ALERT_LIMIT=24 diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay_full_modem_fota.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay_full_modem_fota.conf new file mode 100644 index 000000000000..6d461ce8c07e --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay_full_modem_fota.conf @@ -0,0 +1,18 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Full Modem FOTA +CONFIG_NRF_CLOUD_FOTA_FULL_MODEM_UPDATE=y +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 +# FMFU_FDEV requires additional stack space +CONFIG_CONNECTION_THREAD_STACK_SIZE=4096 +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y +CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=n +CONFIG_PM_SINGLE_IMAGE=y +# FMFU requires additional heap space. +# If the heap is too small, a boot loop can occur when the full modem image is installed. +CONFIG_HEAP_MEM_POOL_SIZE=47250 diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay_mcuboot_ext_flash.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay_mcuboot_ext_flash.conf new file mode 100644 index 000000000000..6a9812976106 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay_mcuboot_ext_flash.conf @@ -0,0 +1,12 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y + +CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay_min_coap.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay_min_coap.conf new file mode 100644 index 000000000000..d8eb7f194b9c --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay_min_coap.conf @@ -0,0 +1,78 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Save power by slowing down sample rate, and not using LEDs +CONFIG_SENSOR_SAMPLE_INTERVAL_SECONDS=60 +CONFIG_LED_INDICATION_DISABLED=y +# Enable optional power savings mode for carrier roaming; this is compatible +# with nRF Cloud CoAP because communications are always initiated by the device. +# It is not compatible with MQTT. +CONFIG_LTE_PROPRIETARY_PSM_REQ=y + +# PSM mode parameters +CONFIG_LTE_PSM_REQ_RPTAU="00100001" +CONFIG_LTE_PSM_REQ_RAT="00000000" + +# eDRX +CONFIG_LTE_EDRX_REQ=n +CONFIG_LTE_EDRX_REQ_VALUE_LTE_M="1001" + +# Disable serial logging to ensure minimum power draw +CONFIG_LOG=n +CONFIG_SERIAL=n +CONFIG_UART_INTERRUPT_DRIVEN=n +CONFIG_UART_CONSOLE=n +CONFIG_AT_HOST_LIBRARY=n +CONFIG_TFM_LOG_LEVEL_SILENCE=y +CONFIG_PM_DEVICE=y + +# nRF Cloud +CONFIG_NRF_CLOUD_MQTT=n +CONFIG_NRF_CLOUD_COAP=y + +# Disable most services +CONFIG_LOCATION_TRACKING=n +CONFIG_NRF_CLOUD_AGNSS=n +CONFIG_MODEM_INFO=n +CONFIG_MODEM_INFO_ADD_NETWORK=n +CONFIG_NRF_CLOUD_PGPS=n +CONFIG_NRF_CLOUD_PGPS_REQUEST_UPON_INIT=n +CONFIG_NRF_CLOUD_ALERT=n +CONFIG_NRF_CLOUD_LOG_DIRECT=n +CONFIG_NRF_CLOUD_SEND_DEVICE_STATUS=n +CONFIG_NRF_CLOUD_SEND_DEVICE_STATUS_NETWORK=n +CONFIG_NRF_CLOUD_SEND_DEVICE_STATUS_SIM=n +CONFIG_NRF_CLOUD_FOTA=n +CONFIG_NRF_CLOUD_LOCATION=n +CONFIG_COAP_FOTA=n +CONFIG_COAP_SHADOW=n + +# Enable only temperature +CONFIG_TEMP_TRACKING=y + +# General config +CONFIG_FPU=y +CONFIG_NEWLIB_LIBC_NANO=n + +# LTE link control - used by PGPS and main application +CONFIG_LTE_LINK_CONTROL=y + +# Modem +CONFIG_MODEM_KEY_MGMT=y +CONFIG_MODEM_JWT=y + +# Network +CONFIG_POSIX_API=y +CONFIG_NET_SOCKETS_TLS_SET_MAX_FRAGMENT_LENGTH=y +CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY=40 + +# CoAP Client +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_APPLICATION_THREAD_STACK_SIZE=4096 diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay_min_mqtt.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay_min_mqtt.conf new file mode 100644 index 000000000000..d499ba5c527f --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay_min_mqtt.conf @@ -0,0 +1,49 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Save power by slowing down sample rate, and not using LEDs +CONFIG_SENSOR_SAMPLE_INTERVAL_SECONDS=60 +CONFIG_LED_INDICATION_DISABLED=y + +# PSM mode parameters +CONFIG_LTE_PSM_REQ_RPTAU="00100001" +CONFIG_LTE_PSM_REQ_RAT="00000000" + +# eDRX +CONFIG_LTE_EDRX_REQ=n +CONFIG_LTE_EDRX_REQ_VALUE_LTE_M="1001" + +# Disable serial logging to ensure minimum power draw +CONFIG_LOG=n +CONFIG_SERIAL=n +CONFIG_UART_INTERRUPT_DRIVEN=n +CONFIG_UART_CONSOLE=n +CONFIG_AT_HOST_LIBRARY=n +CONFIG_TFM_LOG_LEVEL_SILENCE=y +CONFIG_PM_DEVICE=y + +# Disable most services +CONFIG_LOCATION_TRACKING=n +CONFIG_NRF_CLOUD_AGNSS=n +CONFIG_MODEM_INFO=n +CONFIG_MODEM_INFO_ADD_NETWORK=n +CONFIG_NRF_CLOUD_PGPS=n +CONFIG_NRF_CLOUD_PGPS_REQUEST_UPON_INIT=n +CONFIG_NRF_CLOUD_ALERT=n +CONFIG_NRF_CLOUD_LOG_DIRECT=n +CONFIG_NRF_CLOUD_SEND_DEVICE_STATUS=n +CONFIG_NRF_CLOUD_SEND_DEVICE_STATUS_NETWORK=n +CONFIG_NRF_CLOUD_SEND_DEVICE_STATUS_SIM=n +CONFIG_NRF_CLOUD_FOTA=n +CONFIG_AT_CMD_REQUESTS=n +CONFIG_LOCATION=n +CONFIG_LOCATION_METHOD_GNSS=n +CONFIG_LOCATION_METHOD_CELLULAR=n +CONFIG_NRF_CLOUD_LOCATION=n +CONFIG_LOCATION_SERVICE_NRF_CLOUD=n + +# Enable only temperature +CONFIG_TEMP_TRACKING=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay_nrf700x_wifi_coap_no_lte.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay_nrf700x_wifi_coap_no_lte.conf new file mode 100644 index 000000000000..585dc1d67f16 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay_nrf700x_wifi_coap_no_lte.conf @@ -0,0 +1,240 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Note that this overlay is not compatible with nrf91 Series! Use something such as the nrf5340 as +# the host microcontroller. It also requires the use of an _ns board / non-secure target. + +## Disable Modem/LTE/nrf91-specific features +CONFIG_NRF_MODEM_LIB=n +CONFIG_MODEM_INFO=n +CONFIG_MODEM_INFO_ADD_NETWORK=n +CONFIG_AT_HOST_LIBRARY=n +CONFIG_LTE_LINK_CONTROL=n +CONFIG_LTE_PSM_REQ=n + +## Disable offloaded sockets (we are going to switch to the native networking stack) +CONFIG_NET_SOCKETS_OFFLOAD=n + +## Disable cloud features reliant on or specific to nrf91 modem features +CONFIG_NRF_CLOUD_AGNSS=n +CONFIG_NRF_CLOUD_PGPS=n +CONFIG_NRF_CLOUD_PGPS_REQUEST_UPON_INIT=n +CONFIG_LOCATION_METHOD_GNSS=n +CONFIG_LOCATION_METHOD_CELLULAR=n + +## Disable FOTA to conserve memory +CONFIG_NRF_CLOUD_FOTA=n +CONFIG_COAP_FOTA=n +CONFIG_BOOTLOADER_MCUBOOT=n +CONFIG_IMG_MANAGER=n +CONFIG_MCUBOOT_IMG_MANAGER=n + +## Enable TFM and Trusted Execution so that we can use Protected Storage for credentials storage. +## Please note that even when using Protected Storage, credentials are still retrieved into insecure +## memory when in use. +CONFIG_BUILD_WITH_TFM=y +CONFIG_TRUSTED_EXECUTION_NONSECURE=y + +## Enable Protected Storage +CONFIG_TFM_PARTITION_PROTECTED_STORAGE=y + +## Configure TFM Profile. The NOT_SET profile will enable all features. +## We then reduce some settings to save flash and RAM. +CONFIG_TFM_PROFILE_TYPE_NOT_SET=y +CONFIG_TFM_CRYPTO_CONC_OPER_NUM=4 +CONFIG_TFM_CRYPTO_ASYM_SIGN_MODULE_ENABLED=n + +## Configure TFM partitions +CONFIG_PM_PARTITION_SIZE_TFM_INTERNAL_TRUSTED_STORAGE=0x2000 +CONFIG_PM_PARTITION_SIZE_TFM_OTP_NV_COUNTERS=0x2000 +CONFIG_PM_PARTITION_SIZE_TFM_PROTECTED_STORAGE=0x4000 +CONFIG_PM_PARTITION_SIZE_TFM_SRAM=0xC000 +# This is larger than needed under normal conditions, but is the minimum size necessary to +# support enabling CONFIG_DEBUG_OPTIMIZATIONS (useful for debugging) +CONFIG_PM_PARTITION_SIZE_TFM=0x24000 + +## Configure credentials shells and dependencies +CONFIG_SHELL=y +CONFIG_WIFI_CREDENTIALS_SHELL=y +CONFIG_TLS_CREDENTIALS_SHELL=y +CONFIG_TLS_CREDENTIALS_BACKEND_PROTECTED_STORAGE=y +# Increased stack size needed for wifi_cred auto_connect command +CONFIG_SHELL_STACK_SIZE=4850 +# nRFCloud credentials can exceed 1024 bytes +CONFIG_TLS_CREDENTIALS_SHELL_CRED_BUF_SIZE=3072 +# Needed by the TLS credentials shell +CONFIG_BASE64=y + +## Disable LTE-event-driven date_time updates (the date_time library will still periodically +## refresh its timestamp, this setting only controls whether LTE-event-driven date_time updates +## are enabled) +CONFIG_DATE_TIME_AUTO_UPDATE=n +CONFIG_DATE_TIME_MODEM=n +CONFIG_DATE_TIME_NTP=y +CONFIG_DATE_TIME_LOG_LEVEL_DBG=y + +## Enable date_time retries for Wi-Fi +## This prevents occasional DNS lookup flukes from +## rendering the device unable to connect until the next +## CONFIG_DATE_TIME_UPDATE_INTERVAL_SECONDS elapses (default 4 hours) +CONFIG_DATE_TIME_RETRY_COUNT=5 +# Use a really short retry interval for demo purposes +# 60 seconds to 15 minutes is probably more appropriate for +# real-world applications +CONFIG_DATE_TIME_RETRY_INTERVAL_SECONDS=20 + +## Disable LED patterns since the WiFi shield is not compatible +CONFIG_LED_INDICATION_DISABLED=y + +## Disable LTE conn_mgr bindings +CONFIG_NRF_MODEM_LIB_NET_IF=n + +## This had to be disabled for LTE with conn_mgr, but there is no reason to disable for Wi-Fi +CONFIG_DNS_RESOLVER=y +CONFIG_NET_SOCKETS_DNS_TIMEOUT=30000 + +## Enable Wi-Fi drivers, (and the native NET stack so that the location library can access them) +CONFIG_WIFI=y +CONFIG_WIFI_NRF70=y +CONFIG_WIFI_NRF70_SKIP_LOCAL_ADMIN_MAC=y + +## Enable Wi-Fi conn_mgr bindings +CONFIG_L2_WIFI_CONNECTIVITY=y +CONFIG_NET_CONNECTION_MANAGER_MONITOR_STACK_SIZE=4850 + +## Enable WIFI_MGMT_EXT and WIFI_CREDENTIALS for simplified Wi-Fi connection setup +CONFIG_WIFI_CREDENTIALS=y +CONFIG_WIFI_MGMT_EXT=y + +## Enable flash and NVS settings, required by WIFI_CREDENTIALS +CONFIG_SETTINGS=y +CONFIG_SETTINGS_NVS=y +CONFIG_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y + +## Enable Wi-Fi networking and native networking stack +# Note: WIFI_NM_WPA_SUPPLICANT requires 24kB of unused RAM in the final build. +# Memory allocations in this overlay are fine-tuned with that fact in mind. +CONFIG_WIFI_NM_WPA_SUPPLICANT=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_NATIVE=y +CONFIG_NRF_SECURITY=y +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y +CONFIG_NET_CONTEXT_SNDTIMEO=y +CONFIG_NET_TCP=y +CONFIG_NET_DHCPV4=y + +## Configure native MBEDTLS +CONFIG_MQTT_LIB_TLS=n +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_RSA_C=y +CONFIG_MBEDTLS_SSL_SERVER_NAME_INDICATION=y +CONFIG_MBEDTLS_HEAP_SIZE=80000 +CONFIG_MBEDTLS_DTLS=y +CONFIG_MBEDTLS_SSL_SRV_C=y +CONFIG_MBEDTLS_SSL_COOKIE_C=y +CONFIG_MBEDTLS_SSL_DTLS_HELLO_VERIFY=y +CONFIG_MBEDTLS_SSL_DTLS_CONNECTION_ID=y +CONFIG_NET_SOCKETS_ENABLE_DTLS=y + +## Disable unneeded MBEDTLS features to save flash and RAM +CONFIG_MBEDTLS_CHACHA20_C=n +CONFIG_MBEDTLS_CHACHA20_C=n +CONFIG_MBEDTLS_CHACHAPOLY_C=n +CONFIG_MBEDTLS_POLY1305_C=n +CONFIG_MBEDTLS_SHA1_C=n +CONFIG_MBEDTLS_MAC_SHA256_ENABLED=n +CONFIG_MBEDTLS_CIPHER_MODE_CBC=n + +## Enable Wi-Fi location tracking +CONFIG_LOCATION_TRACKING_WIFI=y +CONFIG_LOCATION_METHOD_WIFI=y +# Keep CONFIG_NRF_WIFI_SCAN_MAX_BSS_CNT and CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT +# set to the same value. +CONFIG_NRF_WIFI_SCAN_MAX_BSS_CNT=10 +CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT=10 +# Add 256 bytes for each additional scanning result, assuming sane SSID lengths +CONFIG_HEAP_MEM_POOL_SIZE=153000 +CONFIG_HEAP_MEM_POOL_IGNORE_MIN=y + +## Miscellaneous resource allocation tweaks needed to support Wi-Fi. +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +CONFIG_NET_MGMT_EVENT_QUEUE_SIZE=30 +CONFIG_APPLICATION_THREAD_STACK_SIZE=3072 +CONFIG_MESSAGE_THREAD_STACK_SIZE=3072 +CONFIG_CONNECTION_THREAD_STACK_SIZE=4500 +CONFIG_DATE_TIME_THREAD_STACK_SIZE=2048 +CONFIG_NET_TX_STACK_SIZE=2048 +CONFIG_NET_RX_STACK_SIZE=2048 +CONFIG_ZVFS_OPEN_MAX=16 +CONFIG_NET_SOCKETS_POLL_MAX=8 +CONFIG_NET_MGMT_EVENT_STACK_SIZE=4000 + +# nRF Cloud: CoAP +CONFIG_NRF_CLOUD_MQTT=n +CONFIG_NRF_CLOUD_COAP=y +CONFIG_NRF_CLOUD_ALERT=y +CONFIG_NRF_CLOUD_LOCATION=n +CONFIG_NRF_CLOUD_JWT_SOURCE_CUSTOM=y +CONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y +# User must set their compile time client ID +CONFIG_NRF_CLOUD_CLIENT_ID="" + +# General config +CONFIG_FPU=y +CONFIG_NEWLIB_LIBC_NANO=n + +# Rate of cloud interactions +# These are faster than one normally would use in a low power device. +# This is strictly for demo purposes. +CONFIG_SENSOR_SAMPLE_INTERVAL_SECONDS=120 +CONFIG_LOCATION_TRACKING_SAMPLE_INTERVAL_SECONDS=360 + +# Logs +CONFIG_LOG=y +CONFIG_NET_LOG=y +CONFIG_LOG_PRINTK=y +CONFIG_COAP_LOG_LEVEL_INF=y +CONFIG_LOCATION_LOG_LEVEL_INF=y +CONFIG_MULTI_SERVICE_LOG_LEVEL_INF=y + +# LTE link control - used by main application +CONFIG_LTE_LINK_CONTROL=n + +# Modem +CONFIG_MODEM_KEY_MGMT=n +CONFIG_MODEM_JWT=n +CONFIG_MODEM_INFO_ADD_DEVICE=n +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 + +# Network +CONFIG_POSIX_API=y +CONFIG_NET_SOCKETS_TLS_SET_MAX_FRAGMENT_LENGTH=y +CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY=40 + +# CoAP Client +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 + +# Location Services configuration +CONFIG_LOCATION_DATA_DETAILS=n + +CONFIG_APPLICATION_THREAD_STACK_SIZE=8192 +CONFIG_TEMP_ALERT_LIMIT=24 + +CONFIG_AT_MONITOR=n + +# Disabling to prevent IPv6 error logs +CONFIG_NET_IPV6=n diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay_nrf700x_wifi_mqtt_no_lte.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay_nrf700x_wifi_mqtt_no_lte.conf new file mode 100644 index 000000000000..fe0aad1a5da1 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay_nrf700x_wifi_mqtt_no_lte.conf @@ -0,0 +1,177 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Note that this overlay is not compatible with nrf91 Series! Use something such as the nrf5340 as +# the host microcontroller. It also requires the use of an _ns board / non-secure target. + +## Disable Modem/LTE/nrf91-specific features +CONFIG_NRF_MODEM_LIB=n +CONFIG_MODEM_INFO=n +CONFIG_MODEM_INFO_ADD_NETWORK=n +CONFIG_AT_HOST_LIBRARY=n +CONFIG_LTE_LINK_CONTROL=n +CONFIG_LTE_PSM_REQ=n + +## Disable offloaded sockets (we are going to switch to the native networking stack) +CONFIG_NET_SOCKETS_OFFLOAD=n + +## Disable cloud features reliant on or specific to nrf91 modem features +CONFIG_NRF_CLOUD_AGNSS=n +CONFIG_NRF_CLOUD_PGPS=n +CONFIG_NRF_CLOUD_PGPS_REQUEST_UPON_INIT=n +CONFIG_LOCATION_METHOD_GNSS=n +CONFIG_LOCATION_METHOD_CELLULAR=n + +## Disable FOTA to conserve memory +CONFIG_NRF_CLOUD_FOTA=n +CONFIG_BOOTLOADER_MCUBOOT=n +CONFIG_IMG_MANAGER=n +CONFIG_MCUBOOT_IMG_MANAGER=n + +## Enable TFM and Trusted Execution so that we can use Protected Storage for credentials storage. +## Please note that even when using Protected Storage, credentials are still retrieved into insecure +## memory when in use. +CONFIG_BUILD_WITH_TFM=y +CONFIG_TRUSTED_EXECUTION_NONSECURE=y + +## Enable Protected Storage +CONFIG_TFM_PARTITION_PROTECTED_STORAGE=y + +## Configure TFM Profile. The NOT_SET profile will enable all features. +## We then reduce some settings to save flash and RAM. +CONFIG_TFM_PROFILE_TYPE_NOT_SET=y +CONFIG_TFM_CRYPTO_CONC_OPER_NUM=4 +CONFIG_TFM_CRYPTO_ASYM_SIGN_MODULE_ENABLED=n + +## Configure TFM partitions +CONFIG_PM_PARTITION_SIZE_TFM_INTERNAL_TRUSTED_STORAGE=0x2000 +CONFIG_PM_PARTITION_SIZE_TFM_OTP_NV_COUNTERS=0x2000 +CONFIG_PM_PARTITION_SIZE_TFM_PROTECTED_STORAGE=0x4000 +CONFIG_PM_PARTITION_SIZE_TFM_SRAM=0xC000 +# This is larger than needed under normal conditions, but is the minimum size necessary to +# support enabling CONFIG_DEBUG_OPTIMIZATIONS (useful for debugging) +CONFIG_PM_PARTITION_SIZE_TFM=0x24000 + +## Configure credentials shells and dependencies +CONFIG_SHELL=y +CONFIG_WIFI_CREDENTIALS_SHELL=y +CONFIG_TLS_CREDENTIALS_SHELL=y +CONFIG_TLS_CREDENTIALS_BACKEND_PROTECTED_STORAGE=y +# Increased stack size needed for wifi_cred auto_connect command +CONFIG_SHELL_STACK_SIZE=4850 +# nRFCloud credentials can exceed 1024 bytes +CONFIG_TLS_CREDENTIALS_SHELL_CRED_BUF_SIZE=2048 +# Needed by the TLS credentials shell +CONFIG_BASE64=y + +## Disable LTE-event-driven date_time updates (the date_time library will still periodically +## refresh its timestamp, this setting only controls whether LTE-event-driven date_time updates +## are enabled) +CONFIG_DATE_TIME_AUTO_UPDATE=n + +## Enable date_time retries for Wi-Fi +## This prevents occasional DNS lookup flukes from +## rendering the device unable to connect until the next +## CONFIG_DATE_TIME_UPDATE_INTERVAL_SECONDS elapses (default 4 hours) +CONFIG_DATE_TIME_RETRY_COUNT=5 +# Use a really short retry interval for demo purposes +# 60 seconds to 15 minutes is probably more appropriate for +# real-world applications +CONFIG_DATE_TIME_RETRY_INTERVAL_SECONDS=20 + +## Disable LED patterns since the WiFi shield is not compatible +CONFIG_LED_INDICATION_DISABLED=y + +## Disable LTE conn_mgr bindings +CONFIG_NRF_MODEM_LIB_NET_IF=n + +## These had to be disabled for LTE with conn_mgr, but there is no reason to disable them for +## wifi with conn_mgr, so re-enable them. +CONFIG_NET_IPV6_NBR_CACHE=y +CONFIG_NET_IPV6_MLD=y +CONFIG_DNS_RESOLVER=y +CONFIG_NET_SOCKETS_DNS_TIMEOUT=30000 + +## Enable Wi-Fi drivers, (and the native NET stack so that the location library can access them) +CONFIG_WIFI=y +CONFIG_WIFI_NRF70=y +CONFIG_WIFI_NRF70_SKIP_LOCAL_ADMIN_MAC=y + +## Enable Wi-Fi conn_mgr bindings +CONFIG_L2_WIFI_CONNECTIVITY=y +CONFIG_NET_CONNECTION_MANAGER_MONITOR_STACK_SIZE=4850 + +## Enable WIFI_MGMT_EXT and WIFI_CREDENTIALS for simplified Wi-Fi connection setup +CONFIG_WIFI_CREDENTIALS=y +CONFIG_WIFI_MGMT_EXT=y + +## Use compile-time client ID for nRF Cloud +CONFIG_NRF_CLOUD_CLIENT_ID_SRC_COMPILE_TIME=y + +## Enable flash and NVS settings, required by WIFI_CREDENTIALS +CONFIG_SETTINGS=y +CONFIG_SETTINGS_NVS=y +CONFIG_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y + +## Enable Wi-Fi networking and native networking stack +# Note: WIFI_NM_WPA_SUPPLICANT requires 24kB of unused RAM in the final build. +# Memory allocations in this overlay are fine-tuned with that fact in mind. +CONFIG_WIFI_NM_WPA_SUPPLICANT=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_NATIVE=y +CONFIG_NRF_SECURITY=y +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y +CONFIG_NET_CONTEXT_SNDTIMEO=y +CONFIG_NET_TCP=y +CONFIG_NET_DHCPV4=y + +## Configure native MBEDTLS to support nRF Cloud MQTT +CONFIG_MQTT_LIB_TLS=y +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_RSA_C=y +CONFIG_MBEDTLS_SSL_SERVER_NAME_INDICATION=y +CONFIG_MBEDTLS_HEAP_SIZE=80000 + +## Disable unneeded MBEDTLS features to save flash and RAM +CONFIG_MBEDTLS_CHACHA20_C=n +CONFIG_MBEDTLS_CHACHA20_C=n +CONFIG_MBEDTLS_CHACHAPOLY_C=n +CONFIG_MBEDTLS_POLY1305_C=n +CONFIG_MBEDTLS_SHA1_C=n +CONFIG_MBEDTLS_MAC_SHA256_ENABLED=n +CONFIG_MBEDTLS_CIPHER_MODE_CBC=n +CONFIG_MBEDTLS_SSL_SRV_C=n +CONFIG_MBEDTLS_SSL_COOKIE_C=n + +## Enable Wi-Fi location tracking +CONFIG_LOCATION_TRACKING_WIFI=y +CONFIG_LOCATION_METHOD_WIFI=y +# Keep CONFIG_NRF_WIFI_SCAN_MAX_BSS_CNT and CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT +# set to the same value. +CONFIG_NRF_WIFI_SCAN_MAX_BSS_CNT=10 +CONFIG_LOCATION_METHOD_WIFI_SCANNING_RESULTS_MAX_CNT=10 +# Add 256 bytes for each additional scanning result, assuming sane SSID lengths +CONFIG_HEAP_MEM_POOL_SIZE=153000 +CONFIG_HEAP_MEM_POOL_IGNORE_MIN=y + +## Miscellaneous resource allocation tweaks needed to support Wi-Fi. +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +CONFIG_NRF_CLOUD_CONNECTION_POLL_THREAD_STACK_SIZE=8192 +CONFIG_APPLICATION_THREAD_STACK_SIZE=3072 +CONFIG_MESSAGE_THREAD_STACK_SIZE=3072 +CONFIG_CONNECTION_THREAD_STACK_SIZE=4500 +CONFIG_DATE_TIME_THREAD_STACK_SIZE=2048 +CONFIG_NET_TX_STACK_SIZE=2048 +CONFIG_NET_RX_STACK_SIZE=2048 +CONFIG_ZVFS_OPEN_MAX=16 +CONFIG_NET_SOCKETS_POLL_MAX=8 +CONFIG_NET_MGMT_EVENT_QUEUE_SIZE=30 +CONFIG_NET_MGMT_EVENT_STACK_SIZE=4000 diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay_nrfcloud_logging.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay_nrfcloud_logging.conf new file mode 100644 index 000000000000..05083c5721a6 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay_nrfcloud_logging.conf @@ -0,0 +1,30 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +CONFIG_NRF_CLOUD_LOG_BACKEND=y +CONFIG_NRF_CLOUD_LOG_OUTPUT_LEVEL=3 +CONFIG_NRF_CLOUD_LOG_LOG_LEVEL_INF=y +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_NRF_CLOUD_LOG_INCLUDE_LEVEL_0=y + +# If enabled, log levels for each log source can be set per log backend, and +# can be changed dynamically. +CONFIG_LOG_RUNTIME_FILTERING=n + +CONFIG_LOG_BUFFER_SIZE=4096 + +# Select which format to generate. Dictionary logs are compact but require +# decoding on the server or user PC. Text logs are verbose but immediately +# visible in the nRF Cloud web portal. Unless you disable UART text logging, +# you must set CONFIG_LOG_FMT_SECTION_STRIP=n when +# CONFIG_LOG_BACKEND_NRF_CLOUD_OUTPUT_DICTIONARY=y, or else you will get a +# build assert warning you to do so. This is to prevent a hard crash at runtime. +CONFIG_LOG_BACKEND_NRF_CLOUD_OUTPUT_TEXT=y +#CONFIG_LOG_BACKEND_NRF_CLOUD_OUTPUT_DICTIONARY=y +#CONFIG_LOG_FMT_SECTION_STRIP=n + +CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=4096 +CONFIG_LOG_PROCESS_THREAD_SLEEP_MS=30000 +CONFIG_TEMP_ALERT_LIMIT=24 diff --git a/samples/cellular/nrf_cloud_ble_gateway/overlay_pgps_ext_flash.conf b/samples/cellular/nrf_cloud_ble_gateway/overlay_pgps_ext_flash.conf new file mode 100644 index 000000000000..0614caf95eec --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/overlay_pgps_ext_flash.conf @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y + +CONFIG_NRF_CLOUD_PGPS_STORAGE_PARTITION=y +CONFIG_PM_PARTITION_REGION_PGPS_EXTERNAL=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/prj.conf b/samples/cellular/nrf_cloud_ble_gateway/prj.conf new file mode 100644 index 000000000000..2bfa6cfe9d4f --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/prj.conf @@ -0,0 +1,250 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Log level +# For more verbose and detailed log output, set the log level to +# CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL_DBG=y instead. +CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL_DBG=y + +# General config +CONFIG_EVENTS=y +CONFIG_PICOLIBC_IO_FLOAT=y +CONFIG_RESET_ON_FATAL_ERROR=y +CONFIG_NCS_SAMPLES_DEFAULTS=y +CONFIG_DK_LIBRARY=y + +# LED indication +# Uncomment these to minimize LED state indication when power savings are necessary +# CONFIG_LED_VERBOSE_INDICATION=n +# CONFIG_LED_CONTINUOUS_INDICATION=n + +# Or, uncomment this to completely disable LED state indication +# CONFIG_LED_INDICATION_DISABLED=y + +# Improved Logging +# CONFIG_LOG_MODE_DEFERRED allows logging from multiple threads simultaneously without creating +# splintered log entries, at the cost of needing a buffer for storing logs. +CONFIG_LOG_MODE_DEFERRED=y + +# CONFIG_LOG_BUFFER_SIZE is the aforementioned buffer. A size of 12000 bytes is useful for debugging +# and prototyping, but is probably more than necessary for production-ready firmware. +CONFIG_LOG_BUFFER_SIZE=12000 +CONFIG_LOG_SPEED=y +CONFIG_LOG_FUNC_NAME_PREFIX_INF=y +CONFIG_LOG_FUNC_NAME_PREFIX_ERR=y +CONFIG_LOG_PRINTK=y + +# Heap and stacks +# Extended AT host/monitor stack/heap sizes since some nrf_cloud credentials are longer than 1024 bytes. +CONFIG_AT_MONITOR_HEAP_SIZE=2048 +CONFIG_AT_HOST_STACK_SIZE=2048 +# Extended memory heap size needed both for PGPS and for encoding JSON-based nRF Cloud Device Messages. +CONFIG_HEAP_MEM_POOL_SIZE=20480 +CONFIG_MAIN_STACK_SIZE=3072 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +CONFIG_APPLICATION_WORKQUEUE_STACK_SIZE=4096 +# The default modem shared memory buffer for TX is significantly larger than required. +# RX buffer has to be larger because the sample reads certificates so using default value. +CONFIG_NRF_MODEM_LIB_SHMEM_TX_SIZE=4096 +CONFIG_HW_STACK_PROTECTION=y +CONFIG_EXTRA_EXCEPTION_INFO=y + +# Enable Networking and Connection Manager. +CONFIG_NETWORKING=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_OFFLOAD=y +CONFIG_NET_MGMT_EVENT_STACK_SIZE=2048 +CONFIG_NET_CONNECTION_MANAGER_MONITOR_STACK_SIZE=1024 +CONFIG_MAIN_STACK_SIZE=2048 + +# Enable LTE Connectivity using Connection Manager +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_IPV6_NBR_CACHE=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NRF_MODEM_LIB_NET_IF=y +CONFIG_NRF_MODEM_LIB_NET_IF_AUTO_DOWN=y +CONFIG_NRF_MODEM_LIB_NET_IF_DOWN_DEFAULT_LTE_DISCONNECT=y + +# Enable power savings mode +CONFIG_LTE_PSM_REQ=n +# Set the PSM Requested Active Time to 20 seconds +#CONFIG_LTE_PSM_REQ_RAT="00001010" + +# Modem library +CONFIG_NRF_MODEM_LIB=y + +# AT commands interface +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_AT_HOST_LIBRARY=y + +# Wait for credential installation to be complete before connecting to nRF Cloud +CONFIG_NRF_CLOUD_CHECK_CREDENTIALS=y + +# nRF Cloud +CONFIG_NRF_CLOUD_MQTT=y # This also enables FOTA by implicitly setting CONFIG_NRF_CLOUD_FOTA=y + +# MQTT +CONFIG_MQTT_KEEPALIVE=120 + +# MCUBOOT - Needed by FOTA +CONFIG_BOOTLOADER_MCUBOOT=y +CONFIG_IMG_MANAGER=y +CONFIG_MCUBOOT_IMG_MANAGER=y +CONFIG_STREAM_FLASH_ERASE=y + +# not enough flash to enable these currently: +#CONFIG_SECURE_BOOT=y +#CONFIG_BUILD_S1_VARIANT=y +#CONFIG_SPM_SERVICE_S0_ACTIVE=y + +# Services configuration +CONFIG_TEMP_TRACKING=n +CONFIG_LOCATION_TRACKING=n +CONFIG_LOCATION=n +CONFIG_LOCATION_METHOD_GNSS=n +CONFIG_LOCATION_METHOD_CELLULAR=n +CONFIG_NRF_CLOUD_AGNSS=n +CONFIG_NRF_CLOUD_LOCATION=n +CONFIG_MODEM_INFO=y +CONFIG_MODEM_INFO_ADD_NETWORK=y +CONFIG_MODEM_INFO_ADD_SIM=y +CONFIG_NRF_CLOUD_PGPS=n +#CONFIG_NRF_CLOUD_PGPS_REPLACEMENT_THRESHOLD=4 +#CONFIG_NRF_CLOUD_PGPS_REQUEST_UPON_INIT=y +CONFIG_NRF_CLOUD_MQTT_SHADOW_TRANSFORMS=y + +# Date Time lib - Used by PGPS and main application +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 +CONFIG_SETTINGS_FCB=y +CONFIG_FCB=y + +# Download Client - used by FOTA and PGPS +CONFIG_DOWNLOADER=y +CONFIG_DOWNLOADER_STACK_SIZE=4096 +CONFIG_DOWNLOADER_MAX_HOSTNAME_SIZE=128 + +# 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_NRF_CLOUD_ALERT=n +CONFIG_NRF_CLOUD_LOG_DIRECT=n +CONFIG_NRF_CLOUD_LOG_OUTPUT_LEVEL=3 + +# On initial connection to the cloud, add info sections to the shadow +CONFIG_NRF_CLOUD_SEND_DEVICE_STATUS=y +CONFIG_NRF_CLOUD_SEND_DEVICE_STATUS_NETWORK=y +CONFIG_NRF_CLOUD_SEND_DEVICE_STATUS_SIM=y +CONFIG_NRF_CLOUD_SEND_SERVICE_INFO_FOTA=y +CONFIG_NRF_CLOUD_SEND_SERVICE_INFO_UI=n + +# Instrument the heap +CONFIG_MEM_SLAB_TRACE_MAX_UTILIZATION=y +CONFIG_SYS_HEAP_RUNTIME_STATS=y + +CONFIG_GATEWAY_BLE_FOTA=n + +# Enable Bluetooth stack and libraries +#DT_HAS_ZEPHYR_BT_HCI_UART_ENABLED && BT_HCI && BT_DRIVERS + +CONFIG_BT=y +CONFIG_BT_H4=y +CONFIG_BT_HCI=y +CONFIG_BT_CTLR=n +CONFIG_BT_CENTRAL=y +CONFIG_BT_DRIVERS=y +CONFIG_BT_WAIT_NOP=y +CONFIG_BT_GATT_CLIENT=y +CONFIG_BT_GATT_DM=y +CONFIG_BT_SCAN=y +CONFIG_BT_SCAN_FILTER_ENABLE=y +CONFIG_BT_SCAN_UUID_CNT=1 +CONFIG_BT_SCAN_NAME_CNT=0 +CONFIG_BT_MAX_CONN=8 +CONFIG_BT_BUF_ACL_RX_COUNT_EXTRA=1 +CONFIG_BT_GATT_DM_MAX_ATTRS=100 +CONFIG_BT_FILTER_ACCEPT_LIST=y +CONFIG_BT_SETTINGS=n +CONFIG_BT_EXT_ADV=y +CONFIG_BT_HCI_VS=y +CONFIG_BT_REMOTE_VERSION=y + +#CONFIG_BT_DEBUG_LOG=y +#CONFIG_BT_DEBUG_CONN=y +#CONFIG_BT_DEBUG_GATT=n +#CONFIG_BT_DEBUG_HCI_CORE=y +#CONFIG_BT_DEBUG_HCI_DRIVER=y +CONFIG_BT_LOG_LEVEL_INF=y + +CONFIG_BT_HCI_TX_STACK_SIZE=2048 +CONFIG_BT_HCI_TX_STACK_SIZE_WITH_PROMPT=y +CONFIG_BT_RX_STACK_SIZE=2048 + +# Gateway +CONFIG_NRF_CLOUD_GATEWAY=y +CONFIG_FLASH_TEST=n +CONFIG_AT_CMD_REQUESTS=n + +# for customer builds, set below to n +CONFIG_GATEWAY_DBG_CMDS=y + +# to enable AT command handling unfettered by the shell, turn the below +# option on, and CONFIG_GATEWAY_SHELL off (ignore warnings about the symbols +# in the block below that, or comment them out) +CONFIG_AT_HOST_LIBRARY=n +CONFIG_GATEWAY_SHELL=y + +# set various buffer sizes in shell +CONFIG_SHELL_STACK_SIZE=6144 +CONFIG_SHELL_CMD_BUFF_SIZE=3072 +CONFIG_SHELL_PRINTF_BUFF_SIZE=4096 +CONFIG_SHELL_BACKEND_SERIAL_API_INTERRUPT_DRIVEN=y +CONFIG_SHELL_BACKEND_SERIAL_RX_RING_BUFFER_SIZE=3072 +CONFIG_SHELL_PROMPT_UART="login: " +CONFIG_SHELL_START_OBSCURED=n +CONFIG_SHELL_PROMPT_SECURE="gateway:# " + +# turn on for low level BLE commands +CONFIG_BT_SHELL=n + +# force these off -- they default on +CONFIG_LOG_CMDS=n +CONFIG_PWM_SHELL=n +CONFIG_BOOT_BANNER=n +CONFIG_FLASH_SHELL=n +CONFIG_DEVICE_SHELL=n +CONFIG_DEVMEM_SHELL=n +#CONFIG_SHELL_CMDS=n +CONFIG_SHELL_WILDCARD=n +CONFIG_SHELL_CMDS_RESIZE=y +CONFIG_CLOCK_CONTROL_NRF_SHELL=n + +CONFIG_DEBUG_COREDUMP=n +#CONFIG_DEBUG_COREDUMP_BACKEND_LOGGING=y +#CONFIG_DEBUG_COREDUMP_MEMORY_DUMP_MIN=y + +# change logging level when at login prompt +CONFIG_STARTING_LOG_OVERRIDE=n +CONFIG_STARTING_LOG_LEVEL_INF=y +CONFIG_NRF_CLOUD_LOG_LEVEL_INF=y +CONFIG_NET_LOG=y +CONFIG_MQTT_LOG_LEVEL_INF=y + +CONFIG_DEBUG_OPTIMIZATIONS=y +CONFIG_DEBUG_THREAD_INFO=y +CONFIG_THREAD_MONITOR=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/sample.yaml b/samples/cellular/nrf_cloud_ble_gateway/sample.yaml new file mode 100644 index 000000000000..7c161bea745f --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sample.yaml @@ -0,0 +1,109 @@ +sample: + name: nRF Cloud BLE Gateway Sample +tests: + sample.cellular.nrf_cloud_ble_gateway.mqtt: + sysbuild: true + build_only: true + platform_allow: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + - thingy91/nrf9160/ns + - thingy91x/nrf9151/ns + integration_platforms: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + - thingy91/nrf9160/ns + - thingy91x/nrf9151/ns + tags: ci_build sysbuild ci_samples_cellular + sample.cellular.nrf_cloud_ble_gateway.mqtt.full: + sysbuild: true + build_only: true + platform_allow: nrf9160dk/nrf9160/ns + integration_platforms: + - nrf9160dk/nrf9160/ns + extra_args: "EXTRA_CONF_FILE=\"overlay_full_modem_fota.conf;\ + overlay_pgps_ext_flash.conf;overlay_mcuboot_ext_flash.conf\"" + tags: ci_build sysbuild ci_samples_cellular + sample.cellular.nrf_cloud_ble_gateway.mqtt.min: + sysbuild: true + build_only: true + platform_allow: nrf9160dk/nrf9160/ns + integration_platforms: + - nrf9160dk/nrf9160/ns + extra_args: EXTRA_CONF_FILE="overlay_min_mqtt.conf" + tags: ci_build sysbuild ci_samples_cellular + sample.cellular.nrf_cloud_ble_gateway.coap: + sysbuild: true + build_only: true + platform_allow: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + - thingy91/nrf9160/ns + - thingy91x/nrf9151/ns + integration_platforms: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + - thingy91/nrf9160/ns + - thingy91x/nrf9151/ns + extra_args: EXTRA_CONF_FILE="overlay_coap.conf" + tags: ci_build sysbuild ci_samples_cellular + sample.cellular.nrf_cloud_ble_gateway.coap.min: + sysbuild: true + build_only: true + platform_allow: nrf9160dk/nrf9160/ns + integration_platforms: + - nrf9160dk/nrf9160/ns + extra_args: EXTRA_CONF_FILE="overlay_coap.conf;overlay_min_coap.conf" + tags: ci_build sysbuild ci_samples_cellular + sample.cellular.nrf_cloud_ble_gateway.coap.trace: + sysbuild: true + build_only: true + platform_allow: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + integration_platforms: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + extra_args: EXTRA_CONF_FILE="overlay-coap_nrf_provisioning.conf;overlay_coap.conf" + nrf_cloud_ble_gateway_SNIPPET=nrf91-modem-trace-uart + tags: ci_build sysbuild ci_samples_cellular + sample.cellular.nrf7002ek_wifi.scan: + sysbuild: true + build_only: true + integration_platforms: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + platform_allow: + - nrf9151dk/nrf9151/ns + - nrf9160dk/nrf9160/ns + - nrf9161dk/nrf9161/ns + extra_args: nrf_cloud_ble_gateway_SHIELD=nrf7002ek_nrf7000 + EXTRA_CONF_FILE="overlay-nrf7002ek-wifi-scan-only.conf" + SB_CONF_FILE="sysbuild_nrf700x-wifi-scan.conf" + tags: ci_build sysbuild ci_samples_cellular + sample.cellular.nrf7002ek_wifi.conn: + sysbuild: true + build_only: true + integration_platforms: + - nrf5340dk/nrf5340/cpuapp/ns + platform_allow: nrf5340dk/nrf5340/cpuapp/ns + extra_args: nrf_cloud_ble_gateway_SHIELD=nrf7002ek + EXTRA_CONF_FILE="overlay_nrf700x_wifi_mqtt_no_lte.conf" + SB_CONF_FILE="sysbuild_nrf700x-wifi-conn.conf" + tags: ci_build sysbuild ci_samples_cellular + sample.cellular.nrf7002dk_wifi.conn: + sysbuild: true + build_only: true + integration_platforms: + - nrf7002dk/nrf5340/cpuapp/ns + platform_allow: nrf7002dk/nrf5340/cpuapp/ns + extra_args: EXTRA_CONF_FILE="overlay_nrf700x_wifi_mqtt_no_lte.conf" + SB_CONF_FILE="sysbuild_nrf700x-wifi-conn.conf" + tags: ci_build sysbuild ci_samples_cellular diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/application.c b/samples/cellular/nrf_cloud_ble_gateway/src/application.c new file mode 100644 index 000000000000..592aaff98a92 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/application.c @@ -0,0 +1,473 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_NRF_CLOUD_COAP) +#include +#endif +#if defined(CONFIG_LOCATION_TRACKING) +#include +#include "location_tracking.h" +#endif +#include +#include "application.h" +#include "temperature.h" +#include "cloud_connection.h" +#include "message_queue.h" +#include "led_control.h" +#include "at_commands.h" +#include "shadow_config.h" +#if defined(CONFIG_NRF_CLOUD_GATEWAY) +#include "gateway.h" +#endif + +LOG_MODULE_REGISTER(application, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +/* Timer used to time the sensor sampling rate. */ +static K_TIMER_DEFINE(sensor_sample_timer, NULL, NULL); + +/* AT command request error handling */ +#define AT_CMD_REQUEST_ERR_FORMAT "Error while processing AT command request: %d" +#define AT_CMD_REQUEST_ERR_MAX_LEN (sizeof(AT_CMD_REQUEST_ERR_FORMAT) + 20) +BUILD_ASSERT(CONFIG_AT_CMD_REQUEST_RESPONSE_BUFFER_LENGTH >= AT_CMD_REQUEST_ERR_MAX_LEN, + "Not enough AT command response buffer for printing error events."); + +/* Temperature alert limits. */ +#define TEMP_ALERT_LIMIT ((double)CONFIG_TEMP_ALERT_LIMIT) +#define TEMP_ALERT_HYSTERESIS 1.5 +#define TEMP_ALERT_LOWER_LIMIT (TEMP_ALERT_LIMIT - TEMP_ALERT_HYSTERESIS) + +/* State of the test counter. This can be changed using the configuration in the shadow */ +static bool test_counter_enabled; + +/** + * @brief Construct a device message object with automatically generated timestamp + * + * The resultant JSON object will be conformal to the General Message Schema described in the + * application-protocols repo: + * + * https://github.com/nRFCloud/application-protocols + * + * @param msg - The object to contain the message + * @param appid - The appId for the device message + * @param msg_type - The messageType for the device message + * @return int - 0 on success, negative error code otherwise. + */ +static int create_timestamped_device_message(struct nrf_cloud_obj *const msg, + const char *const appid, + const char *const msg_type) +{ + int err; + int64_t timestamp; + + /* Acquire timestamp */ + err = date_time_now(×tamp); + if (err) { + LOG_ERR("Failed to obtain current time, error %d", err); + return -ETIME; + } + + /* Create message object */ + err = nrf_cloud_obj_msg_init(msg, appid, + IS_ENABLED(CONFIG_NRF_CLOUD_COAP) ? NULL : msg_type); + if (err) { + LOG_ERR("Failed to initialize message with appid %s and msg type %s", + appid, msg_type); + return err; + } + + /* Add timestamp to message object */ + err = nrf_cloud_obj_ts_add(msg, timestamp); + if (err) { + LOG_ERR("Failed to add timestamp to data message with appid %s and msg type %s", + appid, msg_type); + nrf_cloud_obj_free(msg); + return err; + } + + return 0; +} + +/** + * @brief Transmit a collected sensor sample to nRF Cloud. + * + * @param sensor - The name of the sensor which was sampled. + * @param value - The sampled sensor value. + * @return int - 0 on success, negative error code otherwise. + */ +static int send_sensor_sample(const char *const sensor, double value) +{ + int ret; + + MSG_OBJ_DEFINE(msg_obj); + + /* Create a timestamped message container object for the sensor sample. */ + ret = create_timestamped_device_message(&msg_obj, sensor, + NRF_CLOUD_JSON_MSG_TYPE_VAL_DATA); + if (ret) { + return -EINVAL; + } + + /* Populate the container object with the sensor value. */ + ret = nrf_cloud_obj_num_add(&msg_obj, NRF_CLOUD_JSON_DATA_KEY, value, false); + if (ret) { + LOG_ERR("Failed to append value to %s sample container object ", + sensor); + nrf_cloud_obj_free(&msg_obj); + return -ENOMEM; + } + + /* Send the sensor sample container object as a device message. */ + return send_device_message(&msg_obj); +} + +#if defined(CONFIG_LOCATION_TRACKING) +/** + * @brief Transmit a collected GNSS sample to nRF Cloud. + * + * @param loc_gnss - GNSS location data. + * @return int - 0 on success, negative error code otherwise. + */ +static int send_gnss(const struct location_event_data * const loc_gnss) +{ + int ret; + + if (!loc_gnss || (loc_gnss->method != LOCATION_METHOD_GNSS)) { + return -EINVAL; + } + + struct nrf_cloud_gnss_data gnss_pvt = { + .type = NRF_CLOUD_GNSS_TYPE_PVT, + .ts_ms = NRF_CLOUD_NO_TIMESTAMP, + .pvt = { + .lon = loc_gnss->location.longitude, + .lat = loc_gnss->location.latitude, + .accuracy = loc_gnss->location.accuracy, + .has_alt = 0, + .has_speed = 0, + .has_heading = 0 + } + }; + MSG_OBJ_DEFINE(msg_obj); + + /* Add the timestamp */ + (void)date_time_now(&gnss_pvt.ts_ms); + + /* Encode the location data into a device message */ + ret = nrf_cloud_obj_gnss_msg_create(&msg_obj, &gnss_pvt); + + if (ret == 0) { + /* Send the location message */ + ret = send_device_message(&msg_obj); + } + + return ret; +} + +/** + * @brief Callback to receive periodic location updates from location_tracking.c and forward them + * to nRF Cloud. + * + * Note that cellular positioning (MCELL/Multi-Cell and SCELL/Single-Cell) is sent to nRF + * Cloud automatically (since the Location library and nRF Cloud must work together to + * determine them in the first place). GNSS positions, on the other hand, must be + * sent manually, since they are determined entirely on-device. + * + * @param location_data - The received location update. + * + */ +static void on_location_update(const struct location_event_data * const location_data) +{ + LOG_INF("Location Updated: %.06f N %.06f W, accuracy: %.01f m, Method: %s", + location_data->location.latitude, + location_data->location.longitude, + (double)location_data->location.accuracy, + location_method_str(location_data->method)); + + /* If the position update was derived using GNSS, send it onward to nRF Cloud. */ + if (location_data->method == LOCATION_METHOD_GNSS) { + LOG_INF("GNSS Position Update! Sending to nRF Cloud..."); + send_gnss(location_data); + } +} +#endif /* CONFIG_LOCATION_TRACKING */ + +/** + * @brief Receives general device messages from nRF Cloud, checks if they are AT command requests, + * and performs them if so, transmitting the modem response back to nRF Cloud. + * + * Try sending {"appId":"MODEM", "messageType":"CMD", "data":"AT+CGMR"} + * in the nRF Cloud Portal Terminal card. + * + * @param msg - The device message to check. + */ +static void handle_at_cmd_requests(const struct nrf_cloud_data *const dev_msg) +{ + char *cmd; + struct nrf_cloud_obj msg_obj; + int err = nrf_cloud_obj_input_decode(&msg_obj, dev_msg); + + if (err) { + /* The message isn't JSON or otherwise couldn't be parsed. */ + LOG_DBG("A general topic device message of length %d could not be parsed.", + dev_msg->len); + return; + } + + /* Confirm app ID and message type */ + err = nrf_cloud_obj_msg_check(&msg_obj, NRF_CLOUD_JSON_APPID_VAL_MODEM, + NRF_CLOUD_JSON_MSG_TYPE_VAL_CMD); + if (err) { + goto cleanup; + } + + /* Get the command string */ + err = nrf_cloud_obj_str_get(&msg_obj, NRF_CLOUD_JSON_DATA_KEY, &cmd); + if (err) { + /* Missing or invalid command value will be treated as a blank command */ + cmd = ""; + } + + /* Execute the command and receive the result */ + char *response = execute_at_cmd_request(cmd); + + /* To re-use msg_obj for the response message we must first free its memory and + * reset its state. + * The cmd string will no longer be valid after msg_obj is freed. + */ + cmd = NULL; + /* Free the object's allocated memory */ + err = nrf_cloud_obj_free(&msg_obj); + if (err) { + LOG_ERR("Failed to free AT CMD request"); + return; + } + + /* Reset the object's state */ + err = nrf_cloud_obj_reset(&msg_obj); + if (err) { + LOG_ERR("Failed to reset AT CMD request message object for reuse"); + return; + } + + err = create_timestamped_device_message(&msg_obj, NRF_CLOUD_JSON_APPID_VAL_MODEM, + NRF_CLOUD_JSON_MSG_TYPE_VAL_DATA); + if (err) { + return; + } + + /* Populate response with command result */ + err = nrf_cloud_obj_str_add(&msg_obj, NRF_CLOUD_JSON_DATA_KEY, response, false); + if (err) { + LOG_ERR("Failed to populate AT CMD response with modem response data"); + goto cleanup; + } + + /* Send the response */ + err = send_device_message(&msg_obj); + if (err) { + LOG_ERR("Failed to send AT CMD request response, error: %d", err); + } + + return; + +cleanup: + (void)nrf_cloud_obj_free(&msg_obj); +} + +/** @brief Check whether temperature is acceptable. + * If the device exceeds a temperature limit, send the temperature alert one time. + * Once the temperature falls below a lower limit, re-enable the temperature alert + * so it will be sent if limit is exceeded again. + * + * The difference between the two limits should be sufficient to prevent sending + * new alerts if the temperature value oscillates between two nearby values. + * + * @param temp - The current device temperature. + */ +static void monitor_temperature(double temp) +{ + static bool temperature_alert_active; + + if ((temp > TEMP_ALERT_LIMIT) && !temperature_alert_active) { + temperature_alert_active = true; + (void)nrf_cloud_alert_send(ALERT_TYPE_TEMPERATURE, (float)temp, + "Temperature over limit!"); + LOG_INF("Temperature limit %f C exceeded: now %f C.", + TEMP_ALERT_LIMIT, temp); + } else if ((temp < TEMP_ALERT_LOWER_LIMIT) && temperature_alert_active) { + temperature_alert_active = false; + LOG_INF("Temperature now below limit: %f C.", temp); + } +} + +/** @brief Send an incrementing test counter message to nRF Cloud. + * If CONFIG_TEST_COUNTER_MULTIPLIER is greater than 1, the counter will be incremented + * that many times in a row, and a separate test counter message will be sent for each increment. + */ +static void test_counter_send(void) +{ + static int counter; + + for (int i = 0; i < CONFIG_TEST_COUNTER_MULTIPLIER; i++) { + LOG_INF("Sent test counter = %d", counter); + (void)send_sensor_sample("COUNT", counter++); + } +} + +static void button_handler(uint32_t button_state, uint32_t has_changed) +{ + if (has_changed & DK_BTN1_MSK) { + if ((button_state & DK_BTN1_MSK) == DK_BTN1_MSK) { + LOG_INF("Button pressed"); + (void)nrf_cloud_alert_send(ALERT_TYPE_MSG, 0, "Button pressed"); + } + } +} + +static void print_reset_reason(void) +{ + uint32_t reset_reason; + + reset_reason = nrfx_reset_reason_get(); + LOG_INF("Reset reason: 0x%x", reset_reason); +} + +static void report_startup(void) +{ + if (IS_ENABLED(CONFIG_SEND_ONLINE_ALERT)) { + uint32_t reset_reason; + + reset_reason = nrfx_reset_reason_get(); + nrfx_reset_reason_clear(reset_reason); + (void)nrf_cloud_alert_send(ALERT_TYPE_DEVICE_NOW_ONLINE, reset_reason, NULL); + } +} + +void main_application_thread_fn(void) +{ + print_reset_reason(); + + if (IS_ENABLED(CONFIG_AT_CMD_REQUESTS)) { + /* Register with connection.c to receive general device messages and check them for + * AT command requests. + */ + register_general_dev_msg_handler(handle_at_cmd_requests); + } + + dk_buttons_init(button_handler); + +#if defined(CONFIG_NRF_CLOUD_GATEWAY) + init_gateway(); +#endif + + /* Wait for first connection before starting the application. */ + (void)await_cloud_ready(K_FOREVER); + + report_startup(); + + /* Wait for the date and time to become known. + * This is needed both for location services and for sensor sample timestamping. + */ + LOG_INF("Waiting for modem to determine current date and time"); + if (!await_date_time_known(K_SECONDS(CONFIG_DATE_TIME_ESTABLISHMENT_TIMEOUT_SECONDS))) { + LOG_WRN("Failed to determine valid date time. Proceeding anyways"); + } else { + LOG_INF("Current date and time determined"); + } + + const char *protocol = ""; + + if (IS_ENABLED(CONFIG_NRF_CLOUD_MQTT)) { + protocol = "MQTT"; + } else if (IS_ENABLED(CONFIG_NRF_CLOUD_COAP)) { + protocol = "CoAP"; + } + nrf_cloud_log_init(); + nrf_cloud_log_control_set(CONFIG_NRF_CLOUD_LOG_OUTPUT_LEVEL); + /* Send a direct log to the nRF Cloud web portal indicating the sample has started up. */ + (void)nrf_cloud_log_send(LOG_LEVEL_INF, + "nRF Cloud multi-service sample has started, " + "version: %s, protocol: %s", + CONFIG_APP_VERSION, protocol); + +#if defined(CONFIG_LOCATION_TRACKING) + /* Begin tracking location at the configured interval. */ + (void)start_location_tracking(on_location_update, + CONFIG_LOCATION_TRACKING_SAMPLE_INTERVAL_SECONDS); +#endif + + /* Begin sampling sensors. */ + while (true) { + /* Start the sensor sample interval timer. + * We use a timer here instead of merely sleeping the thread, because the + * application thread can be preempted by other threads performing long tasks + * (such as periodic location acquisition), and we want to account for these + * delays when metering the sample send rate. + */ + k_timer_start(&sensor_sample_timer, + K_SECONDS(CONFIG_SENSOR_SAMPLE_INTERVAL_SECONDS), K_FOREVER); + + if (IS_ENABLED(CONFIG_TEMP_TRACKING)) { + double temp = -1; + + if (get_temperature(&temp) == 0) { + LOG_INF("Temperature is %d degrees C", (int)temp); + (void)send_sensor_sample(NRF_CLOUD_JSON_APPID_VAL_TEMP, temp); + + monitor_temperature(temp); + } + } + + if (test_counter_enable_get()) { + test_counter_send(); + } + + /* Wait out any remaining time on the sample interval timer. */ + k_timer_status_sync(&sensor_sample_timer); + + /* If cloud stops being ready due to network trouble or device being deleted + * from the cloud, turn off sensors and wait, then restart sensors. + */ + if (!await_cloud_ready(K_NO_WAIT) && is_device_deleted()) { +#if defined(CONFIG_LOCATION_TRACKING) + (void)location_request_cancel(); +#endif + LOG_INF("Cloud not ready. Pausing sensors."); + (void)await_cloud_ready(K_FOREVER); + LOG_INF("Cloud is ready. Enabling sensors."); +#if defined(CONFIG_LOCATION_TRACKING) + /* Begin tracking location at the configured interval. */ + (void)start_location_tracking(on_location_update, + CONFIG_LOCATION_TRACKING_SAMPLE_INTERVAL_SECONDS); +#endif + } + } +} + +void test_counter_enable_set(const bool enable) +{ + if (IS_ENABLED(CONFIG_TEST_COUNTER)) { + LOG_DBG("CONFIG_TEST_COUNTER is enabled, ignoring state change request"); + } else { + LOG_DBG("Test counter %s", enable ? "enabled" : "disabled"); + test_counter_enabled = enable; + } +} + +bool test_counter_enable_get(void) +{ + /* When CONFIG_TEST_COUNTER is enabled the test counter is always enabled */ + return (test_counter_enabled || IS_ENABLED(CONFIG_TEST_COUNTER)); +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/application.h b/samples/cellular/nrf_cloud_ble_gateway/src/application.h new file mode 100644 index 000000000000..46c227f6c02a --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/application.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _APPLICATION_H_ +#define _APPLICATION_H_ + +#if defined(CONFIG_NRF_CLOUD_MQTT) +#define MSG_OBJ_DEFINE(_obj_name) \ + NRF_CLOUD_OBJ_JSON_DEFINE(_obj_name); +#elif defined(CONFIG_NRF_CLOUD_COAP) +#define MSG_OBJ_DEFINE(_obj_name) \ + NRF_CLOUD_OBJ_COAP_CBOR_DEFINE(_obj_name); +#endif + +/** + * @brief Main application -- Wait for valid connection, start location tracking, and then + * periodically sample sensors and send them to nRF Cloud. + */ +void main_application_thread_fn(void); + +void test_counter_enable_set(const bool enable); +bool test_counter_enable_get(void); + +#endif /* _APPLICATION_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/at_commands.c b/samples/cellular/nrf_cloud_ble_gateway/src/at_commands.c new file mode 100644 index 000000000000..e36b94411020 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/at_commands.c @@ -0,0 +1,67 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include "nrf_cloud_codec_internal.h" +#include "cloud_connection.h" +#include "at_commands.h" + +LOG_MODULE_REGISTER(at_cmd_execution, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +/* AT command request error handling */ +#define AT_CMD_REQUEST_ERR_FORMAT "Error while processing AT command request: %d" +#define AT_CMD_REQUEST_ERR_MAX_LEN (sizeof(AT_CMD_REQUEST_ERR_FORMAT) + 20) +BUILD_ASSERT(CONFIG_AT_CMD_REQUEST_RESPONSE_BUFFER_LENGTH >= AT_CMD_REQUEST_ERR_MAX_LEN, + "Not enough AT command response buffer for printing error events."); + +/* Buffer to contain modem responses when performing AT command requests */ +static char at_cmd_resp_buf[CONFIG_AT_CMD_REQUEST_RESPONSE_BUFFER_LENGTH]; + +char *execute_at_cmd_request(const char *const cmd) +{ + LOG_DBG("Modem AT command requested: %s", cmd); + + /* Clear the response buffer */ + memset(at_cmd_resp_buf, 0, sizeof(at_cmd_resp_buf)); + + /* Pass the command off to the modem. + * + * We must pass the command in using a format specifier it might contain special characters + * such as %. + * + * We subtract 1 from the passed-in response buffer length to ensure that the response is + * always null-terminated, even when the response is longer than the response buffer size. + */ + + int err = nrf_modem_at_cmd(at_cmd_resp_buf, sizeof(at_cmd_resp_buf) - 1, "%s", cmd); + + /* Post-process the response */ + LOG_DBG("Modem AT command response (%d, %d): %s", + nrf_modem_at_err_type(err), nrf_modem_at_err(err), at_cmd_resp_buf); + + /* Trim \r\n from modem response for better readability in the portal. */ + at_cmd_resp_buf[MAX(0, strlen(at_cmd_resp_buf) - 2)] = '\0'; + + /* If an error occurred with the request, report it */ + if (err < 0) { + /* Negative error codes indicate an error with the modem lib itself, so the + * response buffer will be empty (or filled with junk). Thus, we can print the + * error message directly into it. + */ + snprintf(at_cmd_resp_buf, sizeof(at_cmd_resp_buf), AT_CMD_REQUEST_ERR_FORMAT, err); + LOG_ERR("%s", at_cmd_resp_buf); + } + + /* Return a pointer to the start of the command response buffer (which is now populated with + * the AT command response) + */ + return &at_cmd_resp_buf[0]; +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/at_commands.h b/samples/cellular/nrf_cloud_ble_gateway/src/at_commands.h new file mode 100644 index 000000000000..5ddd5577279e --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/at_commands.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _AT_CMD_REQUESTS_H_ +#define _AT_CMD_REQUESTS_H_ + +/** + * @brief Forwards an AT command string to the modem, and returns a pointer + * to a buffer containing the modem's response, or a human readable error. + * + * If CONFIG_NRF_MODEM_LIB is not enabled, the function is not compiled and always + * returns an error string. + * + * Please note that the returned response buffer becomes unsafe to use as soon as + * the next AT command request is made, so be sure to copy or handle the response before performing + * another request. + * + * Do not modify the string in the returned response buffer. + * + * @param cmd - The AT command to execute. + * @return char* a pointer into a NULL-terminated buffer containing the command response. + */ +#if defined(CONFIG_NRF_MODEM_LIB) +char *execute_at_cmd_request(const char *const cmd); +#else /* CONFIG_NRF_MODEM_LIB */ +#define execute_at_cmd_request(...) "AT command requests are not supported by this build!" +#endif /* CONFIG_NRF_MODEM_LIB */ + +#endif /* _AT_CMD_REQUESTS_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/README.rst b/samples/cellular/nrf_cloud_ble_gateway/src/ble/README.rst new file mode 100644 index 000000000000..5764b7f54ee4 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/README.rst @@ -0,0 +1,546 @@ +.. _nrfcloud_ble_gateway: + +nRF9160: nRF Cloud BLE Gateway +############################## + +The nRF Cloud BLE Gateway uses the `lib_nrf_cloud`_ to connect an nRF9160-based board to `nRF Cloud`_ via LTE, connnect to multiple Bluetooth LE peripherals, and transmit their data to the cloud. +Therefore, this application acts as a gateway between Bluetooth LE and the LTE connection to nRF Cloud. + +Overview +******** + +The application uses the LTE link control driver to establish a network connection. +It is then able to connect to multiple Bluetooth LE peripherals, and can then transmit the peripheral data to Nordic Semiconductor's cloud solution, `nRF Cloud`_. +The data is visualized in nRF Cloud's web interface. + +Programs such as PuTTY_, `Tera Term`_, or the `LTE Link Monitor`_ application, implemented as part of `nRF Connect for Desktop`_, can be used to interact with the included shell. +You can also send AT commands from the **Terminal** card on nRF Cloud when the device is connected. + +By default, the gateway supports firmware updates through `lib_nrf_cloud_fota`_. + +.. _nrfcloud_ble_gateway_requirements: + +Requirements +************ + +* One of the following boards: + + * Apricity Gateway + * `nRF9160 DK `_ + +* For the Apricity Gateway nRF9160, `nrfcloud_gateway_controller`_ must be programmed in the nRF52 board controller. +* For the nRF9160 DK, `hci_lpuart`_ must instead be programmed in the nRF52 board controller. +* 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. + + +.. _nrfcloud_ble_gateway_user_interface: + +Key Features +************ +Hardware +-------- +- Runs on either the Apricity Gateway hardware or the nRF9160DK +- 700-960 MHz + 1710-2200 MHz LTE band support. + The following bands, based on geographic regions, are used: + - USA 2, 4, 12, and 13 + - EU 3, 8, 20, and 28 +- Certifications: CE, FCC +- LTE-M/NB-IoT and Bluetooth LE antennas +- Nano/4FF Subscriber Identity Module (SIM) card slot +- 64 megabit SPI serial Flash memory +- PC connection through USB +- Normal operating temperature range: 5C ~ 35C + +Apricity Gateway hardware distinguishing features: + +- Single button with multiple uses power, reset, and update enable +- Dual RGB LEDs for status indication of LTE and BLE connections +- Larger LTE and BLE antennas +- Rechargeable 3.7V Li-Po battery with 2000 mAh capacity +- Charging through Universal Serial Bus (USB) or external power supply with barrel jack connector +- Rigid, wall mountable enclosure + +Firmware +-------- +- Supports up to 8 [#]_ BLE Devices +- Supports FOTA (firmware over the air) updates of the nRF9160 modem, bootloader, and application +- Supports FOTA updates of select BLE devices +- Supports USB updates of nRF52840 processor +- Offers shell interface with secure login for USB serial management of gateway + +.. [#] design improvements are planned to enable more. + +User interface +************** + +One can locally manage the gateway with a USB connection and a terminal program. +See :ref:`Shell` for details. + +The Apricity Gateway button has the following functions: + + * Power off device when held for more than one second and released before reset. + * Short press again will power on the device. + * Reset device when held for > 7.5 seconds. + * Enter USB MCUboot update mode for nrf52840 when held for > 11.5 seconds. + +The application state is indicated by the LEDs. + +.. list-table:: + :header-rows: 1 + :align: center + + * - LTE LED 1 color + - State + * - Off + - Not connected to LTE carrier + * - Slow White Pulse + - Connecting to LTE carrier + * - Slow Yellow Pulse + - Associating with nRF Cloud + * - Slow Cyan Pulse + - Connecting to nRF Cloud + * - Solid Blue + - Connected, ready for BLE connections + * - Red Pulse + - Error + +.. list-table:: + :header-rows: 1 + :align: center + + * - BLE LED 2 color + - State + * - Slow Purple Pulse + - Button being held; continue to hold to enter nRF52840 USB MCUboot update mode + * - Rapid Purple Pulse + - in nRF52840 USB MCUboot update mode + * - Slow Yellow Pulse + - Waiting for Bluetooth LE device to connect + * - Solid White + - Bluetooth LE connection established + +Building and running +******************** + +In order to Flash the first firmware image to the Apricity Gateway, you will need one of the following connections: + + - An nRF9160 DK with VDDIO set to 3V, a 10 pin ribbon connected to Debug out, and an adapter from that to a 6 pin Tag Connect connector. + - A Segger J-Link with an adapter to a 6 pin Tag Connect. + - For either method, connect the Tag Connect to ``NRF91:J1`` on the PCB. + +For programming to run on the nRF9160 DK, set ``PROG/DEBUG`` to ``nRF91``. + +Program nRF9160 Application Processor +------------------------------------- + +1. Checkout this repository. +#. Execute the following to pull down all other required repositories:: + + west update + +#. Execute the following to build for the Apricity Gateway hardware:: + + west build -d build_ag -b apricity_gateway_nrf9160ns + +#. Or execute this, to build for the nRF9160 DK:: + + west build -d build_dk -b nrf9160dk_nrf9160_ns + +#. Flash to either board, replacing with the value above after the ``-d`` option:: + + west flash -d --erase --force + +Program nRF52840 Board Controller +--------------------------------- + +`nrfcloud_gateway_controller`_ + +For the Apricity Gateway hardware, follow the same instructions as above in the folder for its repository, except use ``apricity_gateway_nrf52840`` instead of ``apricity_gateway_nrf9160ns``, and connect the Tag Connect to ``NRF52:J1``. + +For the nRF9160 DK, `hci_lpuart`_ must instead be programmed in the nRF52 board controller. +This should be done from the root of the lte-gateway repo so that the required device tree overlays in the `boards <./boards>`_ folder are utilized. + +Program The nRF9160 Modem Processor +----------------------------------- + +`Modem Firmware v1.3.0`_ + +For either the Apricity Gateway or the nRF9160 DK, you must also flash the modem firmware. +Version ``mfw_1.3.0`` or higher is required. +Program this using `nRF Connect Programmer`_ application. + + +Generating Certificates +*********************** + +An nRF Cloud BLE Gateway must have proper security certificates in order to connect to nRF Cloud. + +Create Self-Signed CA Certs +--------------------------- +This step is done using the `Create CA Cert`_ Python 3 script. + +Check out this repository, install the specified prerequisite Python 3 packages, and then follow the instructions to create a CA cert. +This only needs to be done once per customer. + +Install Device Certificates +--------------------------- +This step is done using the `Device Credentials Installer`_ Python 3 script. + +Here is an example of running this script (replace the CA0x522... values with the file names for your CA certs):: + + $ python3 device_credentials_installer.py -g --ca CA0x522400c80ef6d95ea65ef4860d12adc1b031aa9_ca.pem --ca_key CA0x522400c80ef6d95ea65ef4860d12adc1b031aa9_prv.pem --csv provision.csv -d -F "APP|MODEM|BOOT" + +Using the generated ``provision.csv`` file, go on to the next step. + +NOTES: + +- The ``-A`` (all ports) option will be necessary if using the nRF9160DK, in order to find the board. + If you have more than one board powered on and connected to your PC via USB, you will need to select which board to use. + Otherwise, it will use the first one detected. +- The ``-g`` (gateway) option forces the program to assume this device has a shell which uses the expected Gateway commands in order to send and receive modem ``AT`` commands. + Usually the program will detect this automatically. +- The ``-a`` (append) option allows you to build up a CSV file for multiple devices, and then add them all at once to your account with a single curl command. +- The ``-d`` (delete) option will remove any previous certificates from the ``SECTAG`` being used. + A ``SECTAG`` is a programming slot for certificates in the modem. + The ``SECTAG`` here must match the ``CONFIG_NRF_CLOUD_SEC_TAG`` Kconfig option in the prj.conf file. + +The provision.csv file lists the device ID (UUID) in the first column. +It is also displayed by the Device Credentials Installer script. + +Provisioning and Associating with nRF Cloud +******************************************* + +Once you are signed in, perform the following steps to add the gateway to your nRF Cloud account. + +There are two ways to provision and associate using the provision.csv file you generated:: + +1. Via the nRF Cloud website: `nRF Cloud Provision Devices`_ +#. Programmatically using `nRF Cloud ProvisionDevices REST API`_ + +On the `nRF Cloud Provision Devices`_ page, you can drag and drop the CSV file, or click the button to browse for and select it. +Click the Upload and Provision button to begin the process. +The status will be displayed in the table below. + +Instead of using the website, you can instead use curl or Postman to submit the csv file to the `nRF Cloud ProvisionDevices REST API`_ directly. +You will need to find your nRF Cloud account API Key on your account settings page, and use it in place of $API_KEY below. + +e.g.:: + + $ curl --location --request POST 'https://api.nrfcloud.com/v1/devices' --header 'Authorization: Bearer $API_KEY' --header 'Content-Type: text/csv' --data-binary '@provision.csv' + +*returns*:: + + {"bulkOpsRequestId":"01FE6M2552H7YZQ4XAGWJPR2TW"} + $ + +You can then determine if it succeeded by passing the bulkOpsRequestId returned to the `nRF Cloud FetchBulkOpsRequest REST API`_. + +e.g.:: + + $ curl --location --request GET 'https://api.nrfcloud.com/v1/bulk-ops-requests/01FE6M2552H7YZQ4XAGWJPR2TW' --header 'Authorization: Bearer $API_KEY' + +*returns*:: + + {"bulkOpsRequestId":"01FE6M2552H7YZQ4XAGWJPR2TW","status":"SUCCEEDED","endpoint":"PROVISION_DEVICES","requestedAt":"2021-10-08T19:42:45.992Z","completedAt":"2021-10-08T19:42:49.069Z","uploadedDataUrl":"https://bulk-ops-requests.nrfcloud.com/f08f15c3-b523-7841-ec5a-b277610ade88/provision_devices/01FE6M2552H7YZQ4XAGWJPR2TW.csv"} + $ + +Testing +******* + +After programming the application and all prerequisites to your board, test the Apricity Gateway application by performing the following steps: + +1. Connect the board to the computer using a USB cable. The board is assigned a COM port (Windows) or ttyACM or ttyS device (Linux). + +#. Connect to the board with a terminal emulator, for example, PuTTY, Tera Term, or LTE Link Monitor. + Turn off local echo. Output CR and LF when either is received. + The shell uses VT100-compatible escape sequences for coloration. +#. Reset the board if it was already on. +#. Observe in the terminal window that the board starts up in the Secure Partition Manager and that the application starts. + This is indicated by output similar to the following lines:: + + *** Booting Zephyr OS build v2.6.99-ncs1-rc2-5-ga64e96d17cc7 *** + ... + SPM: prepare to jump to Non-Secure image. + + login: + +#. For PuTTY_ or `LTE Link Monitor`_, you will need to reconnect terminal. + (Bluetooth LE HCI control resets the terminal output and needs to be reconnected). + `Tera Term`_ automatically reconnects. +#. Log in with the default password:: + + nordic + +#. If you wish to see logging messages other than ERROR, such as INFO, execute:: + + log enable inf + +#. Open a web browser and navigate to https://nrfcloud.com/. + Click on Device Management then Gateways. + Click on your device's Device ID (UUID), which takes you to the detailed view of your gateway. +#. The first time you start the application, the device will be added to your account automatically. + + a. Observe that the LED(s) indicate that the device is connected. + #. If the LED(s) indicate an error, check the details of the error in the terminal window. + +#. Add BLE devices by clicking on the + sign. + Read, write, and enable notifications on connected peripheral and observe data being received on the nRF Cloud. +#. Optionally send AT commands from the terminal, and observe that the response is received. + + +.. _Shell: + +Using the Management Interface (Shell) +************************************** +The shell is available via a USB serial port, through one of the two provided serial connections. +Using a 3rd party terminal program such as PuTTY_ or `Tera Term`_, you can log in and administer the gateway directly. + +Once logged in at the login: prompt, you can get help using the tab key or by typing help. :: + + login: ****** + nRF Cloud Gateway + Hit tab for help. + gateway:# + at ble clear cpu_load date exit fota + help history info kernel log login logout + passwd reboot resize session shell shutdown + gateway:# + +Many commands have sub commands. +The shell offers command completion. +Type the start of a command and hit tab, and it will offer available choices. :: + + at - | exit> Execute an AT command. Use + first to remain in AT command mode until 'exit'. + +This command, plus the parameter 'enable' places the shell into AT command mode. +In this mode, the only available commands are nrf9160 +modem AT commands or 'exit' to return to normal shell mode. +See the `nRF91 AT Command Reference`_ for more information about AT commands. :: + + ble - Bluetooth commands + Subcommands: + scan: Scan for BLE devices. + save: Save desired connections to shadow on nRF Cloud. + conn: Connect to BLE device(s). + disc: Disconnect BLE device(s). + en: Enable notifications on + BLE device(s). + dis: Disable notifications on + BLE device(s). + fota: [ver] [crc] [sec_tag] + [frag_size] [apn] start BLE firmware over-the-air update. + test: Set BLE FOTA download test mode. + + clear - Clear the terminal. + cpu_load - debug command to see how busy the nrf9160 is. + date - > - display or change the current + date and time. + exit - leave AT command mode. + fota - [sec_tag] [frag_size] [apn] start firmware + over-the-air update. + history - display previous commands entered in the shell. Up arrow + will move backward one by one, making it easy to repeat + previous commands. + + info - Informational commands + Subcommands: + cloud: Cloud information. + ctlr: BLE controller information. + conn: Connected Bluetooth devices information. + gateway: Gateway information. + irq: Dump IRQ table. + list: List known BLE MAC addresses. + modem: Modem information. + param: List parameters. + scan: Bluetooth scan results. + + kernel - Kernel commands + Subcommands: + cycles: Kernel cycles. + reboot: Reboot. + stacks: List threads stack usage. + threads: List kernel threads. + uptime: Kernel uptime. + version: Kernel version. + + log - Commands for controlling logger + Subcommands: + backend: Logger backends commands. + disable: 'log disable .. ' disables + logs in specified modules (all if no modules + specified). + enable: 'log enable ... ' + enables logs up to given level in specified modules + (all if no modules specified). + go: Resume logging + halt: Halt logging + list_backends: Lists logger backends. + status: Logger status + + login - the default first command after reboot and after logout. + logout - lock shell until user logs in again with correct password. + passwd - change the password. + reboot - disconnect from nRF Cloud and the LTE network, then restart. + resize - update the shell's information about the current dimensions + of the terminal window. + session - display or change the persistent sessions setting. + + shell - Useful, not Unix-like shell commands. + Subcommands: + backspace_mode: Toggle backspace key mode. + Some terminals are not sending separate escape code + for backspace and delete button. This command forces + shell to interpret delete key as backspace. + colors: Toggle colored syntax. + echo: Toggle shell echo. + stats: Shell statistics. + + shutdown - Disconnect from nRF Cloud and the LTE network, then + power down the gateway. Press the button to restart. + + +Updating Firmware with FOTA +*************************** + +The nRF9160 modem, application firmware, and bootloader, can all be updated over the air by nRF Cloud. +This can be done when the gateway is connected to the cloud via https://nrfcloud.com/#/updates-dashboard. + +Modem +----- +Incremental modem updates are supported. +Full modem updates using external flash memory on board the Apricity gateway hardware is possible but not enabled yet. + +nRF9160 +------- +Application and MCUboot firmware updates are supported. + +nRF52840 +-------- +The nRF52840 cannot be updated over the air yet; it must be updated over USB on the Apricity Gateway. +See the Button Behavior and LED Indicator Behavior sections for the process to enter MCUboot mode. +Once in that mode, nRF Connect Desktop Programmer can update the firmware using the MCUBoot mode, by clicking the Enable MCUBoot checkbox. + +The nRF52840 can be updated on an nRF9160DK using the built-in Segger J-Link programming hardware and the already established methods for flashing (nRF Connect for Desktop Programmer, west, nrfjprog, etc.). + +BLE Devices +----------- +Bluetooth devices running the nRF5SDK and its buttonless secure DFU bootloader can be updated as well. +Create a device group in nRF Cloud for one or more indentical Bluetooth peripherals that are connected to your gateway. +Then in the Updates Dashboard, upload the firmware bundle using the Bundles ... menu, then click Create Update. +Select the device group and the firmware bundle, and click Save. +Then click Apply Update. + +Known Issues and Limitations +**************************** + +Hardware +-------- +1. Battery unplugged from PCB for safe shipping +#. Power button and control is implemented in software; no hard on/off provided +#. SIM card is not accessible from outside the enclosure, though it can be replaced by opening the enclosure on the antenna side +#. JTAG programming of onboard nRF9160 and nRF52840 require special connectors and adapters +#. While there is an U.FL RF connector provided for GPS, there is no mounting hole in the enclosure for an SMA connector and external antenna +#. The cables for the BLE and LTE antennas are easily damaged by the edge of the PCB if they are not routed properly prior to screwing either end cap back on +#. It would be better to have a bigger, more professional-looking waterproof enclosure + +Firmware +-------- +1. Modification and storage of login password by user not yet implemented +#. LTE-M / TLS / MQTT is the only tested configuration (NBIoT and LWM2M not tested and/or implemented) +#. Maximum number of BLE device characteristic notifications per second limited to (approximately 10) +#. BLE Beacons are not supported +#. BLE devices which frequently disconnect and reconnect to save power will not work well if at all +#. Only BLE devices which implement Nordic nRF5 SDK-style Secure DFU can be updated using FOTA +#. FOTA updates of the on-board nRF52840 is not implemented (must be done over USB) +#. Bonding is not yet implemented, so devices which require it will not work well if at all +#. External Flash is not utilized yet to implement full modem updates +#. GPS not supported (single and/or multi cell location services could be supported in the future) + +Cloud +----- +1. Frontend facilities for helping easily provision a gateway using device certificates are not yet released. +#. Frontend graphics are not appropriate for BLE devices or for nRF Cloud BLE Gateway vs. Phone Gateway +#. Bandwidth is used inefficiently by requiring frequent complete rediscovery of BLE device services and characteristics +#. JSON format is overly verbose which wastes gateway SRAM and bandwidth, limiting the number of connected BLE devices and rate of notifications + +Dependencies +************ + +This application uses the following nRF Connect SDK libraries and drivers: + +* `lib_nrf_cloud`_ +* `modem_info_readme`_ +* `at_cmd_parser_readme`_ +* ``lib/modem_lib`` +* `dk_buttons_and_leds_readme`_ +* ``drivers/lte_link_control`` +* ``drivers/flash`` +* ``bluetooth/gatt_dm`` +* ``bluetooth/scan`` + +From Zephyr: + * `zephyr:bluetooth_api`_ + +In addition, it uses the Secure Partition Manager sample: + +* `secure_partition_manager`_ + +For nrf52840: + +* `nrfcloud_gateway_controller`_ +* `hci_lpuart`_ + +History +******* + +The Apricity Gateway application was created using the following nRF Connect SDK sample applications: + + * `lte_ble_gateway`_ + * `asset_tracker`_ + +From Zephyr: + * `zephyr:bluetooth-hci-uart-sample`_ + + +.. ### These are links used in gateway docs. + +.. _PuTTY: https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html + +.. _`Tera Term`: https://ttssh2.osdn.jp/index.html.en + +.. _`LTE Link Monitor`: https://infocenter.nordicsemi.com/topic/ug_link_monitor/UG/link_monitor/lm_intro.html + +.. _`nRF Connect Programmer`: https://infocenter.nordicsemi.com/topic/ug_nc_programmer/UG/nrf_connect_programmer/ncp_introduction.html + +.. _`nRF Connect for Desktop`: https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-desktop + +.. _`nRF Cloud`: https://nrfcloud.com/ +.. _`nRF Cloud Provision Devices`: https://nrfcloud.com/#/provision-devices +.. _`nRF Cloud ProvisionDevices REST API`: https://api.nrfcloud.com/v1#operation/ProvisionDevices +.. _`nRF Cloud FetchBulkOpsRequest REST API`: https://api.nrfcloud.com/v1#operation/FetchBulkOpsRequest + +.. _`nrfcloud_gateway_controller`: https://github.com/nRFCloud/lte-gateway-ble + +.. _`Modem Firmware v1.3.0`: https://www.nordicsemi.com/-/media/Software-and-other-downloads/Dev-Kits/nRF9160-DK/nRF9160-modem-FW/mfw_nrf9160_1.3.0.zip + +.. _`nRF91 AT Command Reference`: https://infocenter.nordicsemi.com/topic/ref_at_commands/REF/at_commands/intro.html + +.. _`Create CA Cert`: https://github.com/nRFCloud/utils/tree/master/python/modem-firmware-1.3%2B#create-ca-cert + +.. _`Device Credentials Installer`: https://github.com/nRFCloud/utils/tree/master/python/modem-firmware-1.3%2B#device-credentials-installer + +.. _`asset_tracker`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/applications/asset_tracker/README.html +.. _`lte_ble_gateway`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/samples/nrf9160/lte_ble_gateway/README.html +.. _`lib_nrf_cloud_fota`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/networking/nrf_cloud.html#firmware-over-the-air-fota-updates +.. _`nRF9160 DK `: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_nrf9160.html#ug-nrf9160 +.. _`hci_lpuart`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/samples/bluetooth/hci_lpuart/README.html +.. _`secure_partition_manager`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/samples/spm/README.html#secure-partition-manager +.. _`lib_nrf_cloud`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/networking/nrf_cloud.html +.. _`modem_info_readme`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/modem/modem_info.html +.. _`at_cmd_parser_readme`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/modem/at_cmd_parser.html +.. _`dk_buttons_and_leds_readme`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/others/dk_buttons_and_leds.html + +.. _`zephyr:bluetooth_api`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/reference/bluetooth/index.html +.. _`zephyr:bluetooth-hci-uart-sample`: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/samples/bluetooth/hci_uart/README.html diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble.c b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble.c new file mode 100644 index 000000000000..508ecf6c37ae --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble.c @@ -0,0 +1,1598 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net/nrf_cloud.h" +#include "ble.h" + +#include "ble_codec.h" +#include "gateway.h" +#include "ctype.h" +#include "nrf_cloud_transport.h" +#include "ble_conn_mgr.h" + +#define SEND_NOTIFY_STACK_SIZE 2048 +#define SEND_NOTIFY_PRIORITY 5 +#define SUBSCRIPTION_LIMIT 16 +#define NOTIFICATION_QUEUE_LIMIT 10 +#define MAX_BUF_SIZE 11000 +#define STR(x) #x +#define BT_UUID_GATT_CCC_VAL_STR STR(BT_UUID_GATT_CCC_VAL) + +#include +LOG_MODULE_REGISTER(ble, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +static char buffer[MAX_BUF_SIZE]; +static struct gw_msg output = { + .data.ptr = buffer, + .data.len = 0, + .data_max_len = MAX_BUF_SIZE +}; + +static bool discover_in_progress; +static bool scan_waiting; +static bool print_scan_results; + +static int num_devices_found; +static int num_names_found; + +struct k_timer rec_timer; +struct k_timer scan_timer; +struct k_timer auto_conn_start_timer; + +struct k_work scan_off_work; +struct k_work ble_device_encode_work; +struct k_work start_auto_conn_work; + +static atomic_t queued_notifications; + +struct ble_scanned_dev ble_scanned_devices[MAX_SCAN_RESULTS]; + +/* Must be statically allocated */ +/* TODO: The array needs to remain the entire time the sub exists. + * Should probably be stored with the conn manager. + */ +static struct bt_gatt_subscribe_params sub_param[BT_MAX_SUBSCRIBES]; +/* Array of connections corresponding to subscriptions above */ +static struct bt_conn *sub_conn[BT_MAX_SUBSCRIBES]; +static uint16_t sub_value[BT_MAX_SUBSCRIBES]; +static uint8_t curr_subs; +static int next_sub_index; + +static notification_cb_t notify_callback; + +struct rec_data_t { + void *fifo_reserved; + struct ble_device_conn *connected_ptr; + struct bt_gatt_subscribe_params sub_params; + struct bt_gatt_read_params read_params; + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + uint8_t data[256]; + bool read; + uint16_t length; +}; + +K_FIFO_DEFINE(rec_fifo); + +struct gatt_read_cache_entry { + uint16_t handle; + struct ble_device_conn *connected_ptr; + const struct bt_conn *conn; + struct uuid_handle_pair *uhp; + bool pending; + bool last; +} gatt_read_cache[CONFIG_BT_MAX_CONN]; + +/* Convert ble address string to uppcase */ +void bt_to_upper(char *ble_mac_addr_str, uint8_t addr_len) +{ + for (int i = 0; i < addr_len; i++) { + ble_mac_addr_str[i] = toupper(ble_mac_addr_str[i]); + } +} + +/* Get uuid string from bt_uuid object */ +void bt_uuid_get_str(const struct bt_uuid *uuid, char *str, size_t len) +{ + uint32_t tmp1, tmp5; + uint16_t tmp0, tmp2, tmp3, tmp4; + + switch (uuid->type) { + case BT_UUID_TYPE_16: + snprintk(str, len, "%04x", BT_UUID_16(uuid)->val); + break; + case BT_UUID_TYPE_32: + snprintk(str, len, "%08x", BT_UUID_32(uuid)->val); + break; + case BT_UUID_TYPE_128: + memcpy(&tmp0, &BT_UUID_128(uuid)->val[0], sizeof(tmp0)); + memcpy(&tmp1, &BT_UUID_128(uuid)->val[2], sizeof(tmp1)); + memcpy(&tmp2, &BT_UUID_128(uuid)->val[6], sizeof(tmp2)); + memcpy(&tmp3, &BT_UUID_128(uuid)->val[8], sizeof(tmp3)); + memcpy(&tmp4, &BT_UUID_128(uuid)->val[10], sizeof(tmp4)); + memcpy(&tmp5, &BT_UUID_128(uuid)->val[12], sizeof(tmp5)); + + snprintk(str, len, "%08x%04x%04x%04x%08x%04x", + tmp5, tmp4, tmp3, tmp2, tmp1, tmp0); + break; + default: + (void)memset(str, 0, len); + return; + } +} + +static int svc_attr_data_add(const struct bt_gatt_service_val *gatt_service, + uint16_t handle, struct ble_device_conn *ble_conn_ptr) +{ + char str[UUID_STR_LEN]; + + bt_uuid_get_str(gatt_service->uuid, str, sizeof(str)); + + bt_to_upper(str, strlen(str)); + LOG_INF("Service %s", str); + + return ble_conn_mgr_add_uuid_pair(gatt_service->uuid, handle, 0, 0, + BT_ATTR_SERVICE, ble_conn_ptr, true); +} + +static int chrc_attr_data_add(const struct bt_gatt_chrc *gatt_chrc, + struct ble_device_conn *ble_conn_ptr) +{ + uint16_t handle = gatt_chrc->value_handle; + + return ble_conn_mgr_add_uuid_pair(gatt_chrc->uuid, handle, 1, + gatt_chrc->properties, BT_ATTR_CHRC, + ble_conn_ptr, false); +} + +static int ccc_attr_data_add(const struct bt_uuid *uuid, uint16_t handle, + struct ble_device_conn *ble_conn_ptr) +{ + LOG_DBG("\tHandle: %d", handle); + + return ble_conn_mgr_add_uuid_pair(uuid, handle, 2, 0, BT_ATTR_CCC, + ble_conn_ptr, false); +} + +/* Add attributes to the connection manager objects */ +static int attr_add(const struct bt_gatt_dm *dm, + const struct bt_gatt_dm_attr *attr, + struct ble_device_conn *ble_conn_ptr) +{ + const struct bt_gatt_service_val *gatt_service = + bt_gatt_dm_attr_service_val(attr); + const struct bt_gatt_chrc *gatt_chrc = + bt_gatt_dm_attr_chrc_val(attr); + int err = 0; + + if ((bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) == 0) || + (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY) == 0)) { + err = svc_attr_data_add(gatt_service, attr->handle, + ble_conn_ptr); + } else if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) == 0) { + err = chrc_attr_data_add(gatt_chrc, ble_conn_ptr); + } else if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) == 0) { + err = ccc_attr_data_add(attr->uuid, attr->handle, ble_conn_ptr); + } + return err; +} + +static void full_addr_to_mac_str(const char *raw_addr, char *ble_mac_addr_str) +{ + memcpy(ble_mac_addr_str, raw_addr, BT_ADDR_LE_DEVICE_LEN); + ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN] = 0; + bt_to_upper(ble_mac_addr_str, BT_ADDR_LE_DEVICE_LEN); +} + +static void le_addr_to_mac_addr_str(const bt_addr_le_t *le_addr, char *ble_mac_addr_str) +{ + char raw_addr[BT_ADDR_LE_STR_LEN + 1]; + + bt_addr_le_to_str(le_addr, raw_addr, BT_ADDR_LE_STR_LEN); + full_addr_to_mac_str(raw_addr, ble_mac_addr_str); +} + +static void conn_to_mac_addr_str(const struct bt_conn *conn, char *ble_mac_addr_str) +{ + return le_addr_to_mac_addr_str(bt_conn_get_dst(conn), ble_mac_addr_str); +} + +static int ble_dm_data_add(struct bt_gatt_dm *dm) +{ + const struct bt_gatt_dm_attr *attr = NULL; + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + struct ble_device_conn *ble_conn_ptr; + struct bt_conn *conn; + int err; + + conn = bt_gatt_dm_conn_get(dm); + conn_to_mac_addr_str(conn, ble_mac_addr_str); + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &ble_conn_ptr); + if (err) { + LOG_ERR("Connection not found for addr %s", ble_mac_addr_str); + return err; + } + + discover_in_progress = true; + + attr = bt_gatt_dm_service_get(dm); + + err = attr_add(dm, attr, ble_conn_ptr); + if (err) { + LOG_ERR("Unable to add attribute: %d", err); + return err; + } + + while (NULL != (attr = bt_gatt_dm_attr_next(dm, attr))) { + err = attr_add(dm, attr, ble_conn_ptr); + if (err) { + LOG_ERR("Unable to add attribute: %d", err); + return err; + } + } + return 0; +} + +void ble_register_notify_callback(notification_cb_t callback) +{ + notify_callback = callback; +} + +/* Thread responsible for transferring ble data over MQTT */ +static void send_ble_read_data(int unused1, int unused2, int unused3) +{ + ARG_UNUSED(unused1); + ARG_UNUSED(unused2); + ARG_UNUSED(unused3); + char uuid[BT_UUID_STR_LEN]; + char path[BT_MAX_PATH_LEN]; + + memset(uuid, 0, BT_UUID_STR_LEN); + memset(path, 0, BT_MAX_PATH_LEN); + + struct ble_device_conn *connected_ptr; + + while (1) { + int err; + uint16_t handle; + struct rec_data_t *rx_data = k_fifo_get(&rec_fifo, K_FOREVER); + + if (rx_data == NULL) { + continue; + } + atomic_dec(&queued_notifications); + LOG_DBG("Queued: %ld", atomic_get(&queued_notifications)); + + connected_ptr = rx_data->connected_ptr; + if (connected_ptr == NULL) { + LOG_ERR("Bad connected_ptr!"); + goto cleanup; + } + + if (rx_data->read) { + handle = rx_data->read_params.single.handle; + LOG_INF("Dequeued gatt_read results, addr %s handle %d", + rx_data->ble_mac_addr_str, handle); + } else { + handle = rx_data->sub_params.value_handle; + LOG_DBG("Dequeued BLE notification data, addr %s handle %d", + rx_data->ble_mac_addr_str, handle); + } + /* LOG_HEXDUMP_DBG(rx_data->data, rx_data->length, "notify"); */ + + err = ble_conn_mgr_get_uuid_by_handle(handle, uuid, connected_ptr); + if (err) { + if (discover_in_progress) { + LOG_INF("Ignoring notification on %s due to BLE" + " discovery in progress", + rx_data->ble_mac_addr_str); + } else { + LOG_ERR("Unable to convert handle: %d", err); + } + goto cleanup; + } + + bool ccc = !rx_data->read; + + if (strcmp(uuid, BT_UUID_GATT_CCC_VAL_STR) == 0) { + ccc = true; + handle--; + LOG_INF("Force ccc for handle %u", handle); + } + + err = ble_conn_mgr_generate_path(connected_ptr, handle, path, ccc); + if (err) { + LOG_ERR("Unable to generate path: %d", err); + goto cleanup; + } + + if (!rx_data->read && notify_callback) { + err = notify_callback(rx_data->ble_mac_addr_str, uuid, + rx_data->data, rx_data->length); + if (err) { + /* callback should return 0 if it did not + * process the data + */ + goto cleanup; + } + } + if (connected_ptr && connected_ptr->hidden) { + LOG_DBG("Suppressing notification write to cloud"); + goto cleanup; + } + + k_mutex_lock(&output.lock, K_FOREVER); + if (rx_data->read && !ccc) { + err = device_chrc_read_encode(rx_data->ble_mac_addr_str, + uuid, path, ((char *)rx_data->data), + rx_data->length, &output); + } else if (rx_data->read && ccc) { + err = device_descriptor_value_encode(rx_data->ble_mac_addr_str, + BT_UUID_GATT_CCC_VAL_STR, + path, + ((char *)rx_data->data), + rx_data->length, + &output, false); + } else { + err = device_value_changed_encode(rx_data->ble_mac_addr_str, + uuid, path, ((char *)rx_data->data), + rx_data->length, &output); + } + if (err) { + k_mutex_unlock(&output.lock); + LOG_ERR("Unable to encode: %d", err); + goto cleanup; + } + err = g2c_send(&output.data); + k_mutex_unlock(&output.lock); + if (err) { + LOG_ERR("Unable to send: %d", err); + } + +cleanup: + k_free(rx_data); + } +} + +K_THREAD_DEFINE(ble_rec_thread, SEND_NOTIFY_STACK_SIZE, + send_ble_read_data, NULL, NULL, NULL, + SEND_NOTIFY_PRIORITY, 0, 0); + +static void discovery_completed(struct bt_gatt_dm *disc, void *ctx) +{ + LOG_DBG("Attribute count: %d", bt_gatt_dm_attr_cnt(disc)); + + int err; + + err = ble_dm_data_add(disc); + /* TBD: how can we cancel remainder of discovery if we get an error? */ + + bt_gatt_dm_data_release(disc); + + bt_gatt_dm_continue(disc, NULL); +} + +static void discovery_service_complete(struct bt_conn *conn, void *ctx) +{ + LOG_DBG("Discovery complete."); + + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + struct ble_device_conn *connected_ptr; + int err; + + conn_to_mac_addr_str(conn, ble_mac_addr_str); + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ptr); + if (err) { + LOG_ERR("Connection not found for addr %s", ble_mac_addr_str); + } else { + /* only set discovered true and send results if it seems we were + * successful at doing a full discovery + */ + if (connected_ptr->connected && connected_ptr->num_pairs) { + connected_ptr->encode_discovered = true; + connected_ptr->discovered = true; + } else { + LOG_WRN("Discovery not completed"); + } + connected_ptr->discovering = false; + } + discover_in_progress = false; + + /* check scan waiting */ + if (scan_waiting) { + scan_start(print_scan_results); + } +} + +static void discovery_error_found(struct bt_conn *conn, int err, void *ctx) +{ + LOG_ERR("The discovery procedure failed, err %d", err); + + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + struct ble_device_conn *connected_ptr; + + conn_to_mac_addr_str(conn, ble_mac_addr_str); + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ptr); + if (err) { + LOG_ERR("Connection not found for addr %s", ble_mac_addr_str); + } else { + connected_ptr->num_pairs = 0; + connected_ptr->discovering = false; + connected_ptr->discovered = false; + } + discover_in_progress = false; + + if (IS_ENABLED(CONFIG_SETTINGS)) { + LOG_INF("Saving settings"); + settings_save(); + } + + /* Disconnect? */ + bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + + /* check scan waiting */ + if (scan_waiting) { + scan_start(print_scan_results); + } +} + +static int find_gatt_read_cache_entry(const struct bt_conn *conn, uint16_t handle) +{ + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if ((gatt_read_cache[i].conn == conn) && + (gatt_read_cache[i].handle == handle)) { + return i; + } + } + return -1; +} + +static bool store_gatt_read_cache(struct ble_device_conn *connected_ptr, + const struct bt_conn *conn, uint16_t handle, + struct uuid_handle_pair *uhp) +{ + int entry = find_gatt_read_cache_entry(conn, handle); + + if (entry == -1) { + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (!gatt_read_cache[i].pending) { + entry = i; + break; + } + } + if (entry == -1) { + LOG_ERR("OUT OF GATT READ CACHE ENTRIES!"); + return false; + } + } + struct gatt_read_cache_entry *c = &gatt_read_cache[entry]; + + c->pending = true; + c->connected_ptr = connected_ptr; + c->conn = conn; + c->handle = handle; + c->uhp = uhp; + c->last = false; + + return true; +} + +static uint8_t gatt_read_callback(struct bt_conn *conn, uint8_t err, + struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + int ret = BT_GATT_ITER_CONTINUE; + int entry = find_gatt_read_cache_entry(conn, params->single.handle); + + if (entry == -1) { + return BT_GATT_ITER_STOP; + } + struct gatt_read_cache_entry *c = &gatt_read_cache[entry]; + struct uuid_handle_pair *uhp = c->uhp; + + if ((length > 0) && (data != NULL)) { + /* Store value so next discovery of this device sends the + * most recent value for each readable characteristic, + * rather than 0. + */ + uint16_t offset = MIN(params->single.offset, BT_MAX_VALUE_LEN); + uint16_t this_len = MIN(length, (BT_MAX_VALUE_LEN - offset)); + + uhp->value_len = offset + this_len; + memcpy(&uhp->value[offset], data, this_len); + } + + /* End of read */ + if ((data == NULL) || (length < 22)) { + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + size_t size = sizeof(struct rec_data_t); + struct rec_data_t *read_data = (struct rec_data_t *)k_malloc(size); + + if (read_data == NULL) { + LOG_ERR("Out of memory error in gatt_read_callback(): " + "%ld queued notifications", + atomic_get(&queued_notifications)); + ret = BT_GATT_ITER_STOP; + return ret; + } + + conn_to_mac_addr_str(conn, ble_mac_addr_str); + + memset(&read_data->sub_params, 0, sizeof(read_data->sub_params)); + memcpy(&read_data->read_params, params, sizeof(struct bt_gatt_read_params)); + memcpy(&read_data->ble_mac_addr_str, ble_mac_addr_str, BT_ADDR_LE_DEVICE_LEN + 1); + memcpy(&read_data->data, uhp->value, uhp->value_len); + read_data->fifo_reserved = NULL; + read_data->read = true; + read_data->length = uhp->value_len; + read_data->connected_ptr = c->connected_ptr; + + atomic_inc(&queued_notifications); + k_fifo_put(&rec_fifo, read_data); + LOG_DBG("Enqueued gatt_read of %d bytes; queued:%ld", + read_data->length, atomic_get(&queued_notifications)); + c->pending = false; + + ret = BT_GATT_ITER_STOP; + } + + return ret; +} + +static int gatt_read_handle(struct ble_device_conn *connected_ptr, + struct uuid_handle_pair *uhp, bool ccc) +{ + int err; + static struct bt_gatt_read_params params; + struct bt_conn *conn; + uint16_t handle = uhp->handle; + + if (ccc) { + handle++; + } + params.handle_count = 1; + params.single.handle = handle; + params.single.offset = 0; + params.func = gatt_read_callback; + + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &connected_ptr->bt_addr); + if (conn == NULL) { + LOG_ERR("Null Conn object"); + return -EINVAL; + } + + (void)store_gatt_read_cache(connected_ptr, conn, handle, uhp); + err = bt_gatt_read(conn, ¶ms); + bt_conn_unref(conn); + return err; +} + +int gatt_read(const char *ble_mac_addr_str, const char *chrc_uuid, bool ccc) +{ + int err; + struct ble_device_conn *connected_ptr; + struct uuid_handle_pair *uhp; + + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ptr); + if (err) { + return err; + } + + uhp = ble_conn_mgr_get_uhp_by_uuid(chrc_uuid, connected_ptr); + if (!uhp) { + return 1; + } + + LOG_DBG("Read %s, 0x%02X, %d", chrc_uuid, + ccc ? uhp->handle : uhp->handle + 1, ccc); + return gatt_read_handle(connected_ptr, uhp, ccc); +} + +static void on_sent(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + ARG_UNUSED(conn); + + LOG_DBG("Sent Data of Length: %d", params->length); +} + +int gatt_write(const char *ble_mac_addr_str, const char *chrc_uuid, uint8_t *data, + uint16_t data_len, bt_gatt_write_func_t cb) +{ + int err; + struct bt_conn *conn; + struct ble_device_conn *connected_ptr; + uint16_t handle; + static struct bt_gatt_write_params params; + + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ptr); + if (err) { + return err; + } + + err = ble_conn_mgr_get_handle_by_uuid(&handle, chrc_uuid, + connected_ptr); + if (err) { + return err; + } + + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &connected_ptr->bt_addr); + if (conn == NULL) { + LOG_ERR("Null Conn object"); + return -EINVAL; + } + + LOG_DBG("Writing to addr: %s to chrc %s with handle %d", + ble_mac_addr_str, chrc_uuid, handle); + LOG_HEXDUMP_DBG(data, data_len, "Data to write"); + + params.func = cb ? cb : on_sent; + params.handle = handle; + params.offset = 0; + params.data = data; + params.length = data_len; + + err = bt_gatt_write(conn, ¶ms); + bt_conn_unref(conn); + return err; +} + +int gatt_write_without_response(const char *ble_mac_addr_str, const char *chrc_uuid, + uint8_t *data, uint16_t data_len) +{ + int err; + + struct bt_conn *conn; + struct ble_device_conn *connected_ptr; + uint16_t handle; + /* static struct bt_gatt_write_params params; */ + + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ptr); + if (err) { + return err; + } + + err = ble_conn_mgr_get_handle_by_uuid(&handle, chrc_uuid, + connected_ptr); + if (err) { + return err; + } + + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &connected_ptr->bt_addr); + if (conn == NULL) { + LOG_ERR("Null Conn object"); + return -EINVAL; + } + + LOG_DBG("Writing w/o resp to addr: %s to chrc %s with handle %d", + ble_mac_addr_str, chrc_uuid, handle); + LOG_HEXDUMP_DBG(data, data_len, "Data to write"); + + err = bt_gatt_write_without_response(conn, handle, data, data_len, + false); + bt_conn_unref(conn); + return err; +} + +static uint8_t on_notification_received(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + int ret = BT_GATT_ITER_CONTINUE; + + if (!data) { + return BT_GATT_ITER_STOP; + } + + if ((length > 0) && (data != NULL)) { + conn_to_mac_addr_str(conn, ble_mac_addr_str); + struct ble_device_conn *connected_ptr; + int err; + + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ptr); + if (err) { + return err; + } + + struct rec_data_t tx_data = { + .connected_ptr = connected_ptr, + .read = false, + .length = length + }; + + memcpy(&tx_data.ble_mac_addr_str, ble_mac_addr_str, BT_ADDR_LE_DEVICE_LEN + 1); + memcpy(&tx_data.data, data, length); + memcpy(&tx_data.sub_params, params, + sizeof(struct bt_gatt_subscribe_params)); + + size_t size = sizeof(struct rec_data_t); + + if (atomic_get(&queued_notifications) >= + NOTIFICATION_QUEUE_LIMIT) { + struct rec_data_t *rx_data = k_fifo_get(&rec_fifo, K_NO_WAIT); + + LOG_INF("Dropping oldest message"); + if (rx_data != NULL) { + uint16_t h; + + if (rx_data->read) { + h = rx_data->read_params.single.handle; + } else { + h = rx_data->sub_params.value_handle; + } + LOG_INF("Addr %s Handle %d Queued %ld", + rx_data->ble_mac_addr_str, + h, + atomic_get(&queued_notifications)); + k_free(rx_data); + } + atomic_dec(&queued_notifications); + } + + char *mem_ptr = k_malloc(size); + + if (mem_ptr == NULL) { + LOG_ERR("Out of memory error in on_received(): " + "%ld queued notifications", + atomic_get(&queued_notifications)); + ret = BT_GATT_ITER_STOP; + } else { + atomic_inc(&queued_notifications); + memcpy(mem_ptr, &tx_data, size); + k_fifo_put(&rec_fifo, mem_ptr); + LOG_DBG("Enqueued BLE notification data %ld", + atomic_get(&queued_notifications)); + } + } + + return ret; +} + +static void send_sub(const char *ble_mac_addr_str, const char *path, + struct gw_msg *out, uint8_t *value) +{ + k_mutex_lock(&out->lock, K_FOREVER); + device_descriptor_value_encode(ble_mac_addr_str, + BT_UUID_GATT_CCC_VAL_STR, + path, value, sizeof(value), + out, true); + g2c_send(&out->data); + device_value_write_result_encode(ble_mac_addr_str, + BT_UUID_GATT_CCC_VAL_STR, + path, value, sizeof(value), + out); + g2c_send(&out->data); + k_mutex_unlock(&out->lock); +} + +int ble_subscribe(const char *ble_mac_addr_str, const char *chrc_uuid, uint8_t value_type) +{ + int err; + char path[BT_MAX_PATH_LEN]; + struct bt_conn *conn = NULL; + struct ble_device_conn *connected_ptr; + uint16_t handle; + bool subscribed; + uint8_t param_index; + + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ptr); + if (err) { + LOG_ERR("Connection not found for addr %s", ble_mac_addr_str); + return -ENOLINK; + } + + ble_conn_mgr_get_handle_by_uuid(&handle, chrc_uuid, connected_ptr); + + ble_conn_mgr_get_subscribed(handle, connected_ptr, &subscribed, + ¶m_index); + + if (connected_ptr->connected) { + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &connected_ptr->bt_addr); + if (conn == NULL) { + /* work around strange error on Flic button -- + * type changes when it should not + */ + connected_ptr->bt_addr.type = 0; + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &connected_ptr->bt_addr); + if (conn == NULL) { + LOG_ERR("Null Conn object"); + err = -EINVAL; + goto end; + } + } + } + + err = ble_conn_mgr_generate_path(connected_ptr, handle, path, true); + if (err) { + return err; + } + + if (subscribed && (value_type == 0)) { + /* If subscribed then unsubscribe. */ + if (conn != NULL) { + bt_gatt_unsubscribe(conn, &sub_param[param_index]); + } + ble_conn_mgr_remove_subscribed(handle, connected_ptr); + + uint8_t value[2] = {0, 0}; + + if (connected_ptr && connected_ptr->hidden) { + LOG_DBG("suppressing value_changed"); + } else { + send_sub(ble_mac_addr_str, path, &output, value); + } + LOG_INF("Unsubscribe: Addr %s Handle %d", + ble_mac_addr_str, handle); + + sub_value[param_index] = 0; + sub_conn[param_index] = NULL; + if (curr_subs) { + curr_subs--; + } + } else if (subscribed && (value_type != 0)) { + uint8_t value[2] = { + value_type, + 0 + }; + + if (connected_ptr && connected_ptr->hidden) { + LOG_DBG("suppressing value_changed"); + } else { + send_sub(ble_mac_addr_str, path, &output, value); + } + LOG_INF("Subscribe Dup: Addr %s Handle %d %s", + ble_mac_addr_str, handle, + (value_type == BT_GATT_CCC_NOTIFY) ? + "Notify" : "Indicate"); + + } else if (value_type == 0) { + LOG_DBG("Unsubscribe N/A: Addr %s Handle %d", + ble_mac_addr_str, handle); + } else if (curr_subs < SUBSCRIPTION_LIMIT) { + if (conn != NULL) { + sub_param[next_sub_index].notify = on_notification_received; + sub_param[next_sub_index].value = value_type; + sub_param[next_sub_index].value_handle = handle; + sub_param[next_sub_index].ccc_handle = handle + 1; + sub_value[next_sub_index] = value_type; + sub_conn[next_sub_index] = conn; + err = bt_gatt_subscribe(conn, &sub_param[next_sub_index]); + if (err) { + LOG_ERR("Subscribe failed (err %d)", err); + goto end; + } + } + ble_conn_mgr_set_subscribed(handle, next_sub_index, + connected_ptr); + + uint8_t value[2] = { + value_type, + 0 + }; + + if (connected_ptr && connected_ptr->hidden) { + LOG_DBG("suppressing value_changed"); + } else { + send_sub(ble_mac_addr_str, path, &output, value); + } + LOG_INF("Subscribe: Addr %s Handle %d %s", + ble_mac_addr_str, handle, + (value_type == BT_GATT_CCC_NOTIFY) ? + "Notify" : "Indicate"); + + curr_subs++; + next_sub_index++; + } else { + char msg[64]; + + sprintf(msg, "Reached subscription limit of %d", + SUBSCRIPTION_LIMIT); + + /* Send error when limit is reached. */ + k_mutex_lock(&output.lock, K_FOREVER); + device_error_encode(ble_mac_addr_str, msg, &output); + g2c_send(&output.data); + k_mutex_unlock(&output.lock); + } + +end: + if (conn != NULL) { + bt_conn_unref(conn); + } + return err; +} + +int ble_subscribe_handle(const char *ble_mac_addr_str, uint16_t handle, uint8_t value_type) +{ + char uuid[BT_UUID_STR_LEN]; + struct ble_device_conn *conn_ptr; + int err; + + LOG_DBG("Addr %s handle %u value_type %u", ble_mac_addr_str, + handle, (unsigned int)value_type); + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &conn_ptr); + if (err == 0) { + err = ble_conn_mgr_get_uuid_by_handle(handle, uuid, conn_ptr); + if (err == 0) { + LOG_DBG("Subscribing uuid %s", + uuid); + err = ble_subscribe(ble_mac_addr_str, uuid, value_type); + } + } + return err; +} + +int ble_subscribe_all(const char *ble_mac_addr_str, uint8_t value_type) +{ + unsigned int i; + struct ble_device_conn *conn_ptr; + struct uuid_handle_pair *up; + int err; + + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &conn_ptr); + if (err) { + return err; + } + + for (i = 0; i < conn_ptr->num_pairs; i++) { + up = conn_ptr->uuid_handle_pairs[i]; + if (up == NULL) { + continue; + } + if ((up->properties & BT_GATT_CHRC_NOTIFY) != + BT_GATT_CHRC_NOTIFY) { + continue; + } + err = ble_subscribe_handle(ble_mac_addr_str, up->handle, value_type); + if (err) { + break; + } + } + return err; +} + + +static int ble_subscribe_device(struct bt_conn *conn, bool subscribe) +{ + uint16_t handle; + int i; + int count = 0; + struct ble_device_conn *connected_ptr; + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + int err; + + if (conn == NULL) { + return -EINVAL; + } + conn_to_mac_addr_str(conn, ble_mac_addr_str); + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ptr); + if (err) { + LOG_ERR("Connection not found for addr %s", ble_mac_addr_str); + return -EINVAL; + } + + for (i = 0; i < BT_MAX_SUBSCRIBES; i++) { + if (conn == sub_conn[i]) { + handle = sub_param[i].value_handle; + if (subscribe && sub_value[i]) { + sub_param[i].value = sub_value[i]; + bt_gatt_subscribe(conn, &sub_param[i]); + ble_conn_mgr_set_subscribed(handle, i, + connected_ptr); + LOG_INF("Subscribe: Addr %s Handle %d Idx %d", + ble_mac_addr_str, handle, i); + count++; + } else { + bt_gatt_unsubscribe(conn, &sub_param[i]); + ble_conn_mgr_remove_subscribed(handle, + connected_ptr); + sub_conn[i] = NULL; + if (curr_subs) { + curr_subs--; + } + LOG_INF("Unsubscribe: Addr %s Handle %d Idx %d", + ble_mac_addr_str, handle, i); + count++; + } + } + } + LOG_INF("Subscriptions changed for %d handles", count); + return 0; +} + +static struct bt_gatt_dm_cb discovery_cb = { + .completed = discovery_completed, + .service_not_found = discovery_service_complete, + .error_found = discovery_error_found, +}; + +int ble_discover(struct ble_device_conn *connection_ptr) +{ + int err = 0; + char *ble_mac_addr_str = connection_ptr->ble_mac_addr_str; + struct bt_conn *conn = NULL; + + LOG_INF("Discovering: %s\n", ble_mac_addr_str); + + if (!discover_in_progress) { + + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &connection_ptr->bt_addr); + if (conn == NULL) { + LOG_DBG("ERROR: Null Conn object"); + return -EINVAL; + } + + if (!connection_ptr->discovered) { + if (connection_ptr->num_pairs) { + LOG_INF("Marking device as discovered; " + "num pairs = %u", + connection_ptr->num_pairs); + connection_ptr->discovering = false; + connection_ptr->discovered = true; + connection_ptr->encode_discovered = true; + bt_conn_unref(conn); + return 0; + } + discover_in_progress = true; + connection_ptr->discovering = true; + + for (int i = 1; i < 3; i++) { + if (i > 1) { + LOG_INF("Retrying..."); + } + err = bt_gatt_dm_start(conn, NULL, &discovery_cb, NULL); + if (!err) { + break; + } + LOG_WRN("Service discovery err %d", err); + k_sleep(K_MSEC(500)); + } + if (err) { + LOG_ERR("Aborting discovery. Disconnecting from device..."); + connection_ptr->discovering = false; + discover_in_progress = false; + bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(conn); + ble_conn_set_connected(connection_ptr, false); + return err; + } + } else { + connection_ptr->encode_discovered = true; + } + } else { + return -EBUSY; + } + + bt_conn_unref(conn); + return err; +} + +int disconnect_device_by_addr(const char *ble_mac_addr_str) +{ + int err; + struct bt_conn *conn; + bt_addr_le_t bt_addr; + + err = bt_addr_le_from_str(ble_mac_addr_str, "random", &bt_addr); + if (err) { + LOG_ERR("Address from string failed (err %d)", err); + return err; + } + + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &bt_addr); + if (conn == NULL) { + LOG_DBG("ERROR: Null Conn object"); + return -EINVAL; + } + + /* cloud commanded this, so remove any notifications or indications */ + err = ble_subscribe_device(conn, false); + if (err) { + LOG_ERR("Error unsubscribing device: %d", err); + } + + /* Disconnect device. */ + err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) { + LOG_ERR("Error disconnecting: %d", err); + } + bt_conn_unref(conn); + + return err; +} + +int set_shadow_ble_conn(const char *ble_mac_addr_str, bool connecting, bool connected) +{ + int err; + + LOG_INF("Connecting=%u, connected=%u", connecting, connected); + k_mutex_lock(&output.lock, K_FOREVER); + err = device_shadow_data_encode(ble_mac_addr_str, connecting, connected, + &output); + if (!err) { + err = gw_shadow_publish(&output.data); + if (err) { + LOG_ERR("nrf_cloud_gw_shadow_publish() failed %d", err); + } + } else { + LOG_ERR("device_shadow_data_encode() failed %d", err); + } + k_mutex_unlock(&output.lock); + return err; +} + +int set_shadow_desired_conn(const struct desired_conn *desired, int num_desired) +{ + int err; + + k_mutex_lock(&output.lock, K_FOREVER); + err = gateway_desired_list_encode(desired, + num_desired, + &output); + if (!err) { + err = gw_shadow_publish(&output.data); + if (err) { + LOG_ERR("gw_shadow_publish() failed %d", err); + } + } else { + LOG_ERR("Failed %d", err); + } + k_mutex_unlock(&output.lock); + + return err; +} + +static void auto_conn_start_work_handler(struct k_work *work) +{ + int err; + + /* Restart to scanning for allowlisted devices */ + struct bt_conn_le_create_param param = BT_CONN_LE_CREATE_PARAM_INIT( + BT_CONN_LE_OPT_NONE, + BT_GAP_SCAN_FAST_INTERVAL, + BT_GAP_SCAN_FAST_WINDOW); + + err = bt_conn_le_create_auto(¶m, BT_LE_CONN_PARAM_DEFAULT); + + if (err == -EALREADY) { + LOG_INF("Auto connect already running"); + } else if (err == -EINVAL) { + LOG_INF("Auto connect not necessary or invalid"); + } else if (err == -EAGAIN) { + LOG_WRN("Device not ready; try starting auto connect later"); + k_timer_start(&auto_conn_start_timer, K_SECONDS(3), K_SECONDS(0)); + } else if (err == -ENOMEM) { + LOG_ERR("Out of memory enabling auto connect"); + } else if (err) { + LOG_ERR("Error enabling auto connect: %d", err); + } else { + LOG_INF("Auto connect started"); + } +} + +K_WORK_DEFINE(auto_conn_start_work, auto_conn_start_work_handler); + +static void auto_conn_start_timer_handler(struct k_timer *timer) +{ + k_work_submit(&auto_conn_start_work); +} + +K_TIMER_DEFINE(auto_conn_start_timer, auto_conn_start_timer_handler, NULL); + +static void connected(struct bt_conn *conn, uint8_t conn_err) +{ + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + struct ble_device_conn *connection_ptr; + int err; + + conn_to_mac_addr_str(conn, ble_mac_addr_str); + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connection_ptr); + if (err) { + LOG_ERR("Connection not found for addr %s", ble_mac_addr_str); + } + if (conn_err || err) { + LOG_ERR("Failed to connect to %s (%u)", ble_mac_addr_str, + conn_err); + if (connection_ptr) { + ble_conn_set_connected(connection_ptr, false); + } + return; + } + + if (connection_ptr && connection_ptr->hidden) { + LOG_DBG("suppressing device_connect"); + } else { + k_mutex_lock(&output.lock, K_FOREVER); + device_connect_result_encode(ble_mac_addr_str, true, &output); + g2c_send(&output.data); + k_mutex_unlock(&output.lock); + } + + if (connection_ptr->added_to_allowlist) { + if (!ble_add_to_allowlist(ble_mac_addr_str, false)) { + connection_ptr->added_to_allowlist = false; + } + } + + /* TODO: set LED pattern indicating BLE device is connected */ + if (!connection_ptr->connected) { + LOG_INF("Connected: %s", ble_mac_addr_str); + ble_conn_set_connected(connection_ptr, true); + ble_subscribe_device(conn, true); + if (!connection_ptr->hidden) { + set_shadow_ble_conn(ble_mac_addr_str, false, true); + } + } else { + LOG_INF("Reconnected: %s", ble_mac_addr_str); + } + + /* Start the timer to begin scanning again. */ + k_timer_start(&auto_conn_start_timer, K_SECONDS(3), K_SECONDS(0)); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + struct ble_device_conn *connection_ptr = NULL; + + conn_to_mac_addr_str(conn, ble_mac_addr_str); + ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connection_ptr); + + if (!ble_conn_mgr_is_desired(ble_mac_addr_str)) { + LOG_INF("suppressing device_disconnect"); + } else { + k_mutex_lock(&output.lock, K_FOREVER); + device_disconnect_result_encode(ble_mac_addr_str, false, &output); + g2c_send(&output.data); + k_mutex_unlock(&output.lock); + } + + /* if device disconnected on purpose, don't bother updating + * shadow; it will likely reconnect shortly + */ + if (reason != BT_HCI_ERR_REMOTE_USER_TERM_CONN) { + LOG_INF("Disconnected: %s (reason 0x%02x)", ble_mac_addr_str, + reason); + if (connection_ptr && !connection_ptr->hidden) { + (void)set_shadow_ble_conn(ble_mac_addr_str, false, false); + } + if (connection_ptr) { + ble_conn_set_connected(connection_ptr, false); + } + } else { + LOG_INF("Disconnected: temporary"); + } + + if (connection_ptr && + !connection_ptr->free && + !connection_ptr->added_to_allowlist) { + if (!ble_add_to_allowlist(connection_ptr->ble_mac_addr_str, true)) { + connection_ptr->added_to_allowlist = true; + } + } + + /* TODO: set BLE device disconnected LED pattern */ + + /* Start the timer to begin scanning again. */ + k_timer_start(&auto_conn_start_timer, K_SECONDS(3), K_SECONDS(0)); +} + +static struct bt_conn_cb conn_callbacks = { + .connected = connected, + .disconnected = disconnected, +}; + +static bool data_cb(struct bt_data *data, void *user_data) +{ + char *name = user_data; + int len = MIN(data->data_len, NAME_LEN - 1); + + switch (data->type) { + case BT_DATA_NAME_SHORTENED: + case BT_DATA_NAME_COMPLETE: + memcpy(name, data->data, len); + name[len] = '\0'; + return false; + default: + return true; + } +} + +static void ble_device_found_enc_handler(struct k_work *work) +{ + LOG_DBG("Encoding scan..."); + k_mutex_lock(&output.lock, K_FOREVER); + device_found_encode(num_devices_found, &output); + LOG_DBG("Sending scan..."); + g2c_send(&output.data); + k_mutex_unlock(&output.lock); +} + +K_WORK_DEFINE(ble_device_encode_work, ble_device_found_enc_handler); + +static void device_found(const bt_addr_le_t *bt_addr, int8_t rssi, uint8_t type, + struct net_buf_simple *ad) +{ + char name[NAME_LEN]; + struct ble_scanned_dev *scanned = &ble_scanned_devices[num_devices_found]; + char ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN + 1]; + + if (num_devices_found >= MAX_SCAN_RESULTS) { + return; + } + + /* We're only interested in connectable events */ + if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) { + return; + } + + le_addr_to_mac_addr_str(bt_addr, ble_mac_addr_str); + + /* Check for duplicate addresses before adding to results. */ + for (int j = 0; j < num_devices_found; j++) { + if (!(strncasecmp(ble_mac_addr_str, + ble_scanned_devices[j].ble_mac_addr_str, + BT_ADDR_LE_STR_LEN))) { + return; /* no need to continue; we saw this */ + } + } + /* Update scan results to include this new device's MAC address. */ + memcpy(scanned->ble_mac_addr_str, ble_mac_addr_str, BT_ADDR_LE_DEVICE_LEN); + scanned->ble_mac_addr_str[BT_ADDR_LE_DEVICE_LEN] = '\0'; + + /* Add the device name, if known */ + memset(name, 0, sizeof(name)); + bt_data_parse(ad, data_cb, name); + strcpy(scanned->name, name); + if (strlen(name)) { + num_names_found++; + } + + scanned->rssi = (int)rssi; + num_devices_found++; + + LOG_INF("%d. %s, %d, %s", num_devices_found, scanned->ble_mac_addr_str, + rssi, scanned->name); +} + +struct ble_scanned_dev *get_scanned_device(unsigned int i) +{ + if (i < num_devices_found) { + return &ble_scanned_devices[i]; + } else { + return NULL; + } +} + +int get_num_scan_results(void) +{ + return num_devices_found; +} + +int get_num_scan_names(void) +{ + return num_names_found; +} + +static void scan_off_handler(struct k_work *work) +{ + int err; + + LOG_INF("Stopping scan..."); + + err = bt_le_scan_stop(); + if (err) { + LOG_INF("Stopping scanning failed (err %d)", err); + } else { + LOG_INF("Scan successfully stopped"); + } + + /* Start the timer to begin scanning again. */ + k_timer_start(&auto_conn_start_timer, K_SECONDS(3), K_SECONDS(0)); + + LOG_DBG("Submitting scan..."); + k_work_submit(&ble_device_encode_work); + + if (print_scan_results) { + print_scan_results = false; + + struct ble_scanned_dev *scanned; + int i; + + printk("Scan results:\n"); + for (i = 0, scanned = ble_scanned_devices; + i < num_devices_found; i++, scanned++) { + printk("%d. %s, %d, %s\n", i, scanned->ble_mac_addr_str, + (int)scanned->rssi, scanned->name); + k_sleep(K_MSEC(50)); + } + } +} + +K_WORK_DEFINE(scan_off_work, scan_off_handler); + +static void scan_timer_handler(struct k_timer *timer) +{ + k_work_submit(&scan_off_work); +} + +K_TIMER_DEFINE(scan_timer, scan_timer_handler, NULL); + +int ble_add_to_allowlist(const char *addr_str, bool add) +{ + int err; + bt_addr_le_t bt_addr; + + LOG_INF("%s allowlist: %s", add ? "Adding address to" : "Removing address from", + addr_str); + + bt_conn_create_auto_stop(); + + err = bt_addr_le_from_str(addr_str, "random", &bt_addr); + if (err) { + LOG_ERR("Invalid peer address: %d", err); + goto done; + } + + err = add ? bt_le_filter_accept_list_add(&bt_addr) : + bt_le_filter_accept_list_remove(&bt_addr); + if (err) { + LOG_ERR("Error %s allowlist: %d", add ? "adding to" : "removing from", err); + } + +done: + /* Start the timer to begin scanning again. */ + k_timer_start(&auto_conn_start_timer, K_SECONDS(3), K_SECONDS(0)); + + return err; +} + +void ble_stop_activity(void) +{ + int err; + + LOG_INF("Stop scan..."); + err = bt_le_scan_stop(); + if (err) { + LOG_DBG("Error stopping scan: %d", err); + } + + LOG_INF("Stop autoconnect..."); + err = bt_conn_create_auto_stop(); + if (err) { + LOG_DBG("Error stopping autoconnect: %d", err); + } + + LOG_INF("Clear allowlist..."); + err = bt_le_filter_accept_list_clear(); + if (err) { + LOG_DBG("Error clearing allowlist: %d", err); + } + + struct ble_device_conn *conn; + uint8_t ret; + + LOG_INF("Disconnect devices..."); + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + conn = get_connected_device(i); + if (conn && (conn->connected)) { + ret = disconnect_device_by_addr(conn->ble_mac_addr_str); + if (ret) { + LOG_DBG("Error disconnecting %s", + conn->ble_mac_addr_str); + } + } + } +} + +int device_discovery_send(struct ble_device_conn *conn_ptr) +{ + if (conn_ptr && conn_ptr->hidden) { + LOG_DBG("suppressing device_discovery_send"); + return 0; + } + k_mutex_lock(&output.lock, K_FOREVER); + int ret = device_discovery_encode(conn_ptr, &output); + + if (!ret) { + LOG_INF("Sending discovery; JSON Size: %d", output.data.len); + g2c_send(&output.data); + } + memset((char *)output.data.ptr, 0, output.data.len); + k_mutex_unlock(&output.lock); + + return ret; +} + +void scan_start(bool print) +{ + int err; + + print_scan_results = print; + num_devices_found = 0; + num_names_found = 0; + memset(ble_scanned_devices, 0, sizeof(ble_scanned_devices)); + + struct bt_le_scan_param param = { + .type = BT_LE_SCAN_TYPE_ACTIVE, + .options = BT_LE_SCAN_OPT_FILTER_DUPLICATE, + .interval = 0x0010, + .window = 0x0010, + }; + + if (!discover_in_progress) { + /* Stop the auto connect */ + bt_conn_create_auto_stop(); + + err = bt_le_scan_start(¶m, device_found); + if (err) { + LOG_ERR("Bluetooth set active scan failed " + "(err %d)\n", err); + } else { + LOG_INF("Bluetooth active scan enabled"); + + /* TODO: Get scan timeout from scan message */ + k_timer_start(&scan_timer, K_SECONDS(10), K_SECONDS(0)); + } + + scan_waiting = false; + } else { + LOG_INF("Scan waiting... Discover in progress"); + scan_waiting = true; + } +} + +static void ble_ready(int err) +{ + LOG_INF("Bluetooth ready"); + + bt_conn_cb_register(&conn_callbacks); +} + +int ble_init(void) +{ + int err; + + LOG_INF("Initializing Bluetooth.."); + k_mutex_init(&output.lock); + + err = bt_enable(ble_ready); + if (err) { + LOG_ERR("Bluetooth init failed (err %d)", err); + return err; + } + + for (int i = 0; i < MAX_SCAN_RESULTS; i++) { + memset(ble_scanned_devices[i].name, 0, + sizeof(ble_scanned_devices[1].name)); + } + + return 0; +} + +#if defined(CONFIG_BOARD_NRF9160DK_NRF9160_NS) +/* This function is missing in the Zephyr file + * boards/nordic/nrf9160dk/nrf52840_reset.c. It is now + * required by the Bluetooth stack. + */ +int bt_h4_vnd_setup(const struct device *dev) +{ + return 0; +} +#endif diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble.h b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble.h new file mode 100644 index 000000000000..885a0ccbb969 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _BLE_H_ +#define _BLE_H_ + +#include +#include + +#define MAX_SCAN_RESULTS 10 +#define BT_ATTR_SERVICE 0 +#define BT_ATTR_CHRC 1 +#define BT_ATTR_CCC 3 +#define BT_ADDR_LE_DEVICE_LEN 17 +#define NAME_LEN 30 +#define UUID_STR_LEN 37 +#define BT_MAX_SUBSCRIBES 25 + +struct ble_scanned_dev { + int rssi; + char name[NAME_LEN]; + char ble_mac_addr_str[BT_ADDR_LE_STR_LEN + 1]; +}; + +struct ble_device_conn; +struct desired_conn; + +typedef int (*notification_cb_t)(const char *ble_mac_addr_str, const char *chrc_uuid, + uint8_t *data, uint16_t len); + +int ble_init(void); +int ble_add_to_allowlist(const char *addr_str, bool add); +void scan_start(bool print_scan); +void ble_register_notify_callback(notification_cb_t callback); +int ble_subscribe(const char *ble_mac_addr_str, const char *chrc_uuid, uint8_t value_type); +int ble_subscribe_handle(const char *ble_mac_addr_str, uint16_t handle, uint8_t value_type); +int ble_subscribe_all(const char *ble_mac_addr_str, uint8_t value_type); +int gatt_read(const char *ble_mac_addr_str, const char *chrc_uuid, bool ccc); +int gatt_write(const char *ble_mac_addr_str, const char *chrc_uuid, uint8_t *data, + uint16_t data_len, bt_gatt_write_func_t cb); +int gatt_write_without_response(const char *ble_mac_addr_str, const char *chrc_uuid, uint8_t *data, + uint16_t data_len); +int ble_discover(struct ble_device_conn *conn_ptr); +void bt_uuid_get_str(const struct bt_uuid *uuid, char *str, size_t len); +void bt_to_upper(char *ble_mac_addr_str, uint8_t addr_len); +int disconnect_device_by_addr(const char *ble_mac_addr_str); +int device_discovery_send(struct ble_device_conn *conn_ptr); +struct ble_scanned_dev *get_scanned_device(unsigned int i); +int get_num_scan_results(void); +int get_num_scan_names(void); +void ble_stop_activity(void); +int set_shadow_desired_conn(const struct desired_conn *desired, int num_desired); +int set_shadow_ble_conn(const char *ble_address, bool connecting, bool connected); + +#endif /* _BLE_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_codec.c b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_codec.c new file mode 100644 index 000000000000..f8ed5f6dce8c --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_codec.c @@ -0,0 +1,1315 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#undef __XSI_VISIBLE +#define __XSI_VISIBLE 1 +#include +#include +#include +#include +#if defined(CONFIG_NRF_MODEM_LIB) +#include +#endif /* CONFIG_NRF_MODEM_LIB */ +#include +#include +#include + +#include "cJSON.h" +#include "cJSON_os.h" +#include "ble_codec.h" +#include "ble_conn_mgr.h" +#include "ble.h" +#include "gateway.h" +#include "nrf_cloud_mem.h" +#include "nrf_cloud_transport.h" + +#define MAX_SERVICE_BUF_SIZE 300 + +#include +#include +LOG_MODULE_REGISTER(ble_codec, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +typedef int (*gateway_state_handler_t)(void *root_obj); + +extern void nrf_cloud_register_gateway_state_handler(gateway_state_handler_t handler); +extern int nrf_cloud_modem_info_json_encode(const struct nrf_cloud_modem_info * const mod_inf, + cJSON * const mod_inf_obj); +extern int nrf_cloud_service_info_json_encode(const struct nrf_cloud_svc_info * const svc_inf, + cJSON * const svc_inf_obj); +extern int nrf_cloud_shadow_delta_response_encode(cJSON *input_obj, + bool accept, + struct nrf_cloud_data *const output); + +extern struct ble_scanned_dev ble_scanned_devices[MAX_SCAN_RESULTS]; + +static char service_buffer[MAX_SERVICE_BUF_SIZE]; + +static bool first_service = true; +static bool first_chrc = true; +static bool desired_conns_strings; + +#define JSON_KEY_SRVC_INFO "serviceInfo" + +/* define macros to enable memory allocation error checking and + * cleanup, and also improve readability + */ +#define CJCREATE(_a_) do { \ + (_a_) = cJSON_CreateObject(); \ + if ((_a_) == NULL) { \ + goto cleanup; \ + } \ +} while (0) + +#define CJADDITEM cJSON_AddItemToObject +#define CJADDITEMCS cJSON_AddItemToObjectCS +#define CJADDREF cJSON_AddItemReferenceToObject +#define CJADDREFCS cJSON_AddItemReferenceToObjectCS + +#define CJPRINT(_a_, _b_, _c_, _d_) do { \ + int _e_ = cJSON_PrintPreallocated((_a_), (_b_), (_c_), (_d_)); \ + if (!_e_) { \ + LOG_ERR("Insufficient buffer size %d", (_c_)); \ + goto cleanup; \ + } \ +} while (0) + +#define CJCHK(_a_) do { \ + if ((_a_) == NULL) { \ + LOG_ERR("cJSON out of memory in %s", __func__); \ + goto cleanup; \ + } \ +} while (0) + +#define CJADDCHK(_a_, _b_, _c_) do { \ + CJCHK(_c_); \ + cJSON_AddItemToObject((_a_), (_b_), (_c_)); \ +} while (0) + +#define CJADDBOOL(_a_, _b_, _c_) CJCHK( \ + cJSON_AddBoolToObject((_a_), (_b_), (_c_))) +#define CJADDBOOLCS(_a_, _b_, _c_) CJCHK( \ + cJSON_AddBoolToObjectCS((_a_), (_b_), (_c_))) + +#define CJADDSTR(_a_, _b_, _c_) CJCHK( \ + cJSON_AddStringToObject((_a_), (_b_), (_c_))) +#define CJADDSTRCS(_a_, _b_, _c_) CJCHK( \ + cJSON_AddStringToObjectCS((_a_), (_b_), (_c_))) + +#define CJADDNUM(_a_, _b_, _c_) CJCHK( \ + cJSON_AddNumberToObject((_a_), (_b_), (_c_))) +#define CJADDNUMCS(_a_, _b_, _c_) CJCHK( \ + cJSON_AddNumberToObjectCS((_a_), (_b_), (_c_))) + +#define CJADDNULL(_a_, _b_) CJCHK( \ + cJSON_AddNullToObject((_a_), (_b_))) +#define CJADDNULLCS(_a_, _b_) CJCHK( \ + cJSON_AddNullToObjectCS((_a_), (_b_))) + +#define CJADDARROBJ(_a_, _b_) do { \ + (_b_) = cJSON_CreateObject(); \ + CJCHK(_b_); \ + cJSON_AddItemToArray((_a_), (_b_)); \ +} while (0) + +#define CJADDARRNUM(_a_, _b_) do { \ + cJSON *tmp = cJSON_CreateNumber((_b_)); \ + CJCHK(tmp); \ + cJSON_AddItemToArray((_a_), tmp); \ +} while (0) + +#define CJADDARRSTR(_a_, _b_) do { \ + cJSON *tmp = cJSON_CreateString((_b_)); \ + CJCHK(tmp); \ + cJSON_AddItemToArray((_a_), tmp); \ +} while (0) + + +char *get_time_str(char *dst, size_t len) +{ + int64_t unix_time_ms; + int err; + +#ifdef CONFIG_DATE_TIME + err = date_time_now(&unix_time_ms); + if (!err) { + time_t unix_time; + struct tm *time; + + unix_time = unix_time_ms / MSEC_PER_SEC; + LOG_DBG("Unix time %lld", unix_time); + time = gmtime(&unix_time); + if (time) { + /* 2020-02-19T18:38:50.363Z */ + strftime(dst, len, "%Y-%m-%dT%H:%M:%S.000Z", time); + LOG_DBG("Date/time %s", dst); + } else { + LOG_WRN("Time not valid"); + dst = NULL; + } + } else { + dst = NULL; + LOG_ERR("Date/time not available: %d", err); + } +#else + struct tm tm; + struct timespec ts; + time_t t; + + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + LOG_DBG("Unix time %lld", ts.tv_sec); + t = (time_t)ts.tv_sec; + tm = *(gmtime(&t)); + /* 2020-02-19T18:38:50.363Z */ + strftime(dst, len, "%Y-%m-%dT%H:%M:%S.000Z", &tm); + LOG_DBG("Date/time %s", dst); + } +#endif + return dst; +} + +int device_error_encode(const char *ble_address, const char *error_msg, + struct gw_msg *msg) +{ + /* TODO: Front end doesn't handle error messages yet. + * This format may change. + */ + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *event = cJSON_CreateObject(); + cJSON *device = cJSON_CreateObject(); + cJSON *error = cJSON_CreateObject(); + char str[64]; + + if ((root_obj == NULL) || (event == NULL) || (device == NULL) || + (error == NULL)) { + goto cleanup; + } + + CJADDSTRCS(root_obj, "type", "event"); + CJADDSTRCS(root_obj, "gatewayId", gateway_id); + CJADDSTRCS(root_obj, "timestamp", get_time_str(str, sizeof(str))); + + CJADDSTRCS(event, "type", "error"); + CJADDSTRCS(error, "description", error_msg); + CJADDSTRCS(device, "deviceAddress", ble_address); + + /* use references so deleting is cleaner below; must be last + * operation on any given cJSON object + */ + CJADDREFCS(event, "device", device); + CJADDREFCS(event, "error", error); + CJADDREFCS(root_obj, "event", event); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + ret = 0; + +cleanup: + cJSON_Delete(error); + cJSON_Delete(device); + cJSON_Delete(event); + cJSON_Delete(root_obj); + return ret; +} + +int device_found_encode(uint8_t num_devices_found, struct gw_msg *msg) +{ + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *event = cJSON_CreateObject(); + cJSON *devices = cJSON_CreateArray(); + cJSON *device = NULL; + cJSON *address = NULL; + char str[64]; + + if ((root_obj == NULL) || (event == NULL) || (devices == NULL)) { + goto cleanup; + } + + CJADDSTRCS(root_obj, "type", "event"); + CJADDSTRCS(root_obj, "gatewayId", gateway_id); + CJADDNULLCS(root_obj, "requestId"); + + CJADDSTRCS(event, "type", "scan_result"); + CJADDSTRCS(event, "timestamp", get_time_str(str, sizeof(str))); + CJADDSTRCS(event, "subType", "instant"); + CJADDBOOLCS(event, "timeout", true); + + for (int i = 0; i < num_devices_found; i++) { + /* TODO: Update for beacons */ + CJADDARROBJ(devices, device); + CJADDSTRCS(device, "deviceType", "BLE"); + CJADDNUMCS(device, "rssi", ble_scanned_devices[i].rssi); + if (strlen(ble_scanned_devices[i].name) > 0) { + CJADDSTRCS(device, "name", ble_scanned_devices[i].name); + } + + CJCREATE(address); + CJADDSTRCS(address, "address", ble_scanned_devices[i].ble_mac_addr_str); + CJADDITEMCS(device, "address", address); + address = NULL; + } + + /* Figure out a messageId: + * CJADDNUMCS(root_obj, "messageId", 1); + */ + CJADDREFCS(event, "devices", devices); + CJADDREFCS(root_obj, "event", event); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + ret = 0; + +cleanup: + if (address) { + cJSON_Delete(address); + } + cJSON_Delete(devices); + cJSON_Delete(event); + cJSON_Delete(root_obj); + return ret; +} + +int device_connect_result_encode(char *ble_address, bool conn_status, + struct gw_msg *msg) +{ + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *event = cJSON_CreateObject(); + cJSON *device = cJSON_CreateObject(); + cJSON *address = cJSON_CreateObject(); + cJSON *status = cJSON_CreateObject(); + char str[64]; + + if ((root_obj == NULL) || (event == NULL) || (device == NULL) || + (address == NULL) || (status == NULL)) { + goto cleanup; + } + + CJADDSTRCS(root_obj, "type", "event"); + CJADDSTRCS(root_obj, "gatewayId", gateway_id); + CJADDNULLCS(root_obj, "requestId"); + + CJADDSTRCS(event, "type", "device_connect_result"); + CJADDSTRCS(event, "timestamp", get_time_str(str, sizeof(str))); + + CJADDSTRCS(device, "id", ble_address); + CJADDSTRCS(address, "address", ble_address); + CJADDBOOLCS(status, "connected", conn_status); + + CJADDREFCS(device, "address", address); + CJADDREFCS(device, "status", status); + CJADDREFCS(event, "device", device); + CJADDREFCS(root_obj, "event", event); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + ret = 0; + +cleanup: + cJSON_Delete(status); + cJSON_Delete(address); + cJSON_Delete(device); + cJSON_Delete(event); + cJSON_Delete(root_obj); + return ret; +} + +int device_disconnect_result_encode(char *ble_address, bool conn_status, + struct gw_msg *msg) +{ + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *event = cJSON_CreateObject(); + cJSON *device = cJSON_CreateObject(); + cJSON *address = cJSON_CreateObject(); + cJSON *status = cJSON_CreateObject(); + char str[64]; + + if ((root_obj == NULL) || (event == NULL) || (device == NULL) || + (address == NULL) || (status == NULL)) { + goto cleanup; + } + + CJADDSTRCS(root_obj, "type", "event"); + CJADDSTRCS(root_obj, "gatewayId", gateway_id); + CJADDNULLCS(root_obj, "requestId"); + + CJADDSTRCS(event, "type", "device_disconnect"); + CJADDSTRCS(event, "timestamp", get_time_str(str, sizeof(str))); + + CJADDSTRCS(device, "id", ble_address); + CJADDSTRCS(address, "address", ble_address); + CJADDBOOLCS(status, "connected", conn_status); + + CJADDREFCS(device, "status", status); + CJADDREFCS(device, "address", address); + CJADDREFCS(event, "device", device); + CJADDREFCS(root_obj, "event", event); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + ret = 0; + +cleanup: + cJSON_Delete(status); + cJSON_Delete(address); + cJSON_Delete(device); + cJSON_Delete(event); + cJSON_Delete(root_obj); + return ret; +} + +int device_value_changed_encode(char *ble_address, char *uuid, char *path, + char *value, uint16_t value_length, + struct gw_msg *msg) +{ + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *event = cJSON_CreateObject(); + cJSON *device = cJSON_CreateObject(); + cJSON *address = cJSON_CreateObject(); + cJSON *chrc = cJSON_CreateObject(); + cJSON *value_arr = cJSON_CreateArray(); + char str[64]; + + if ((root_obj == NULL) || (event == NULL) || (device == NULL) || + (address == NULL) || (chrc == NULL) || (value_arr == NULL)) { + goto cleanup; + } + + CJADDSTRCS(root_obj, "type", "event"); + CJADDSTRCS(root_obj, "gatewayId", gateway_id); + CJADDNULLCS(root_obj, "requestId"); + + CJADDSTRCS(event, "type", "device_characteristic_value_changed"); + CJADDSTRCS(event, "timestamp", get_time_str(str, sizeof(str))); + + CJADDSTRCS(device, "id", ble_address); + CJADDSTRCS(address, "address", ble_address); + /* TODO: Get Type; */ + CJADDSTRCS(address, "type", "random"); + + CJADDSTRCS(chrc, "uuid", uuid); + CJADDSTRCS(chrc, "path", path); + + for (int i = 0; i < value_length; i++) { + CJADDARRNUM(value_arr, value[i]); + } + + CJADDREFCS(device, "address", address); + CJADDREFCS(chrc, "value", value_arr); + CJADDREFCS(event, "device", device); + CJADDREFCS(event, "characteristic", chrc); + CJADDREFCS(root_obj, "event", event); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + ret = 0; + +cleanup: + cJSON_Delete(value_arr); + cJSON_Delete(chrc); + cJSON_Delete(address); + cJSON_Delete(device); + cJSON_Delete(event); + cJSON_Delete(root_obj); + return ret; +} + +int device_value_write_result_encode(const char *ble_address, const char *uuid, const char *path, + const char *value, uint16_t value_length, + struct gw_msg *msg) +{ + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *event = cJSON_CreateObject(); + cJSON *device = cJSON_CreateObject(); + cJSON *address = cJSON_CreateObject(); + cJSON *desc = cJSON_CreateObject(); + cJSON *value_arr = cJSON_CreateArray(); + char str[64]; + + if ((root_obj == NULL) || (event == NULL) || (device == NULL) || + (address == NULL) || (desc == NULL) || (value_arr == NULL)) { + goto cleanup; + } + + CJADDSTRCS(root_obj, "type", "event"); + CJADDSTRCS(root_obj, "gatewayId", gateway_id); + CJADDNULLCS(root_obj, "requestId"); + + CJADDSTRCS(event, "type", "device_descriptor_value_write_result"); + CJADDSTRCS(event, "timestamp", get_time_str(str, sizeof(str))); + + CJADDSTRCS(device, "id", ble_address); + CJADDSTRCS(address, "address", ble_address); + /* TODO: Get Type; */ + CJADDSTRCS(address, "type", "random"); + + CJADDSTRCS(desc, "uuid", uuid); + CJADDSTRCS(desc, "path", path); + + for (int i = 0; i < value_length; i++) { + CJADDARRNUM(value_arr, value[i]); + } + + CJADDREFCS(device, "address", address); + CJADDREFCS(desc, "value", value_arr); + CJADDREFCS(event, "device", device); + CJADDREFCS(event, "descriptor", desc); + CJADDREFCS(root_obj, "event", event); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + ret = 0; + +cleanup: + cJSON_Delete(value_arr); + cJSON_Delete(desc); + cJSON_Delete(address); + cJSON_Delete(device); + cJSON_Delete(event); + cJSON_Delete(root_obj); + return ret; +} + +int device_descriptor_value_encode(const char *ble_address, char *uuid, + const char *path, char *value, + uint16_t value_length, + struct gw_msg *msg, bool changed) +{ + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *event = cJSON_CreateObject(); + cJSON *device = cJSON_CreateObject(); + cJSON *address = cJSON_CreateObject(); + cJSON *desc = cJSON_CreateObject(); + cJSON *value_arr = cJSON_CreateArray(); + char str[64]; + + if ((root_obj == NULL) || (event == NULL) || (device == NULL) || + (address == NULL) || (desc == NULL) || (value_arr == NULL)) { + LOG_ERR("Invalid parameters"); + goto cleanup; + } + + CJADDSTRCS(root_obj, "type", "event"); + CJADDSTRCS(root_obj, "gatewayId", gateway_id); + CJADDNULLCS(root_obj, "requestId"); + + CJADDSTRCS(event, "type", changed ? + "device_descriptor_value_changed" : + "device_descriptor_value_read_result"); + CJADDSTRCS(event, "timestamp", get_time_str(str, sizeof(str))); + + CJADDSTRCS(device, "id", ble_address); + CJADDSTRCS(address, "address", ble_address); + /* TODO: Get Type; */ + CJADDSTRCS(address, "type", "random"); + + CJADDSTRCS(desc, "uuid", uuid); + CJADDSTRCS(desc, "path", path); + + for (int i = 0; i < value_length; i++) { + CJADDARRNUM(value_arr, value[i]); + } + + CJADDREFCS(device, "address", address); + CJADDREFCS(desc, "value", value_arr); + CJADDREFCS(event, "device", device); + CJADDREFCS(event, "descriptor", desc); + CJADDREFCS(root_obj, "event", event); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + ret = 0; + +cleanup: + cJSON_Delete(value_arr); + cJSON_Delete(desc); + cJSON_Delete(address); + cJSON_Delete(device); + cJSON_Delete(event); + cJSON_Delete(root_obj); + return ret; +} + +int device_chrc_read_encode(char *ble_address, char *uuid, char *path, + char *value, uint16_t value_length, + struct gw_msg *msg) +{ + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *event = cJSON_CreateObject(); + cJSON *device = cJSON_CreateObject(); + cJSON *address = cJSON_CreateObject(); + cJSON *chrc = cJSON_CreateObject(); + cJSON *value_arr = cJSON_CreateArray(); + char str[128]; + bool is_string = true; + + if ((root_obj == NULL) || (event == NULL) || (device == NULL) || + (address == NULL) || (chrc == NULL) || (value_arr == NULL)) { + goto cleanup; + } + + CJADDSTRCS(root_obj, "type", "event"); + CJADDSTRCS(root_obj, "gatewayId", gateway_id); + CJADDNULLCS(root_obj, "requestId"); + + CJADDSTRCS(event, "type", "device_characteristic_value_changed"); + /** This used to use "device_characteristic_value_read_result" + * but that no longer works in the cloud backend. + */ + CJADDSTRCS(event, "timestamp", get_time_str(str, sizeof(str))); + + CJADDSTRCS(device, "id", ble_address); + CJADDSTRCS(address, "address", ble_address); + CJADDSTRCS(address, "type", "random"); /* Not accurate: we do not know the type. */ + CJADDSTRCS(chrc, "uuid", uuid); + CJADDSTRCS(chrc, "path", path); + CJADDREFCS(device, "address", address); + + for (int i = 0; i < value_length; i++) { + if ((value[i] == '\0') && (i == (value_length - 1))) { + break; + } + if (!isprint(value[i])) { + is_string = false; + LOG_DBG("char at %d not printable: 0x%02X", i, value[i]); + break; + } + } + + if (is_string && (value_length > 1)) { + strncpy(str, value, sizeof(str) - 1); + str[MIN(sizeof(str) - 1, value_length)] = '\0'; + CJADDSTRCS(chrc, "value", str); + LOG_DBG("Value: %s", value); + } else { + LOG_HEXDUMP_DBG(value, value_length, "Value"); + } + + for (int i = 0; i < value_length; i++) { + CJADDARRNUM(value_arr, value[i]); + } + CJADDREFCS(chrc, "value", value_arr); + + CJADDREFCS(event, "device", device); + CJADDREFCS(event, "characteristic", chrc); + CJADDREFCS(root_obj, "event", event); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + ret = 0; + +cleanup: + cJSON_Delete(value_arr); + cJSON_Delete(chrc); + cJSON_Delete(address); + cJSON_Delete(device); + cJSON_Delete(event); + cJSON_Delete(root_obj); + return ret; +} + +static int create_device_wrapper(char *ble_address, bool conn_status, + struct gw_msg *msg) +{ + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *event = cJSON_CreateObject(); + cJSON *device = cJSON_CreateObject(); + cJSON *address = cJSON_CreateObject(); + cJSON *status = cJSON_CreateObject(); + cJSON *services = cJSON_CreateObject(); + char str[64]; + + if ((root_obj == NULL) || (event == NULL) || (device == NULL) || + (address == NULL) || (status == NULL) || (services == NULL)) { + goto cleanup; + } + + CJADDSTRCS(root_obj, "type", "event"); + CJADDSTRCS(root_obj, "gatewayId", gateway_id); + CJADDNULLCS(root_obj, "requestId"); + + CJADDSTRCS(event, "type", "device_discover_result"); + CJADDSTRCS(event, "timestamp", get_time_str(str, sizeof(str))); + + CJADDSTRCS(device, "id", ble_address); + CJADDSTRCS(address, "address", ble_address); + CJADDBOOLCS(status, "connected", conn_status); + + CJADDREFCS(device, "status", status); + CJADDREFCS(device, "address", address); + CJADDREFCS(event, "device", device); + CJADDREFCS(event, "services", services); + CJADDREFCS(root_obj, "event", event); + + char *ptr = (char *)msg->data.ptr; + + CJPRINT(root_obj, ptr, msg->data_max_len, 0); + ptr[strlen(ptr) - 3] = 0; + msg->data.len = strlen(ptr); + ret = 0; + +cleanup: + cJSON_Delete(services); + cJSON_Delete(status); + cJSON_Delete(address); + cJSON_Delete(device); + cJSON_Delete(event); + cJSON_Delete(root_obj); + return ret; +} + +int device_shadow_data_encode(const char *ble_address, bool connecting, + bool connected, struct gw_msg *msg) +{ + __ASSERT_NO_MSG(msg != NULL); + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *state_obj = cJSON_CreateObject(); + cJSON *reported_obj = cJSON_CreateObject(); + cJSON *status_connections = cJSON_CreateObject(); + cJSON *device = cJSON_CreateObject(); + cJSON *status = cJSON_CreateObject(); + + if ((root_obj == NULL) || (state_obj == NULL) || + (reported_obj == NULL) || (status_connections == NULL) || + (device == NULL) || (status == NULL)) { + LOG_ERR("Error creating shadow data"); + goto cleanup; + } + + CJADDSTRCS(device, "id", ble_address); + CJADDBOOLCS(status, "connected", connected); + CJADDBOOLCS(status, "connecting", connecting); + + CJADDREFCS(device, "status", status); + CJADDREF(status_connections, ble_address, device); + CJADDREFCS(reported_obj, "statusConnections", status_connections); + CJADDREFCS(state_obj, "reported", reported_obj); + CJADDREFCS(root_obj, "state", state_obj); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + ret = 0; + +cleanup: + cJSON_Delete(status); + cJSON_Delete(device); + cJSON_Delete(status_connections); + cJSON_Delete(reported_obj); + cJSON_Delete(state_obj); + cJSON_Delete(root_obj); + return ret; +} + +int gateway_reported_encode(struct gw_msg *msg) +{ + __ASSERT_NO_MSG(msg != NULL); + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *state_obj = cJSON_CreateObject(); + cJSON *reported_obj = cJSON_CreateObject(); + cJSON *connections_obj = cJSON_CreateNull(); + cJSON *status_connections = cJSON_CreateNull(); + + if ((root_obj == NULL) || (state_obj == NULL) || + (reported_obj == NULL) || (connections_obj == NULL) || (status_connections == NULL)) { + goto cleanup; + } + CJADDCHK(reported_obj, "desiredConnections", connections_obj); + CJADDCHK(reported_obj, "statusConnections", status_connections); + CJADDCHK(state_obj, "reported", reported_obj); + CJADDCHK(root_obj, "state", state_obj); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + cJSON_Delete(root_obj); + return 0; + +cleanup: + cJSON_Delete(status_connections); + cJSON_Delete(connections_obj); + cJSON_Delete(reported_obj); + cJSON_Delete(state_obj); + cJSON_Delete(root_obj); + + return ret; +} + +int gateway_desired_list_encode(const struct desired_conn *desired, int num_desired, + struct gw_msg *msg) +{ + __ASSERT_NO_MSG(msg != NULL); + int ret = -ENOMEM; + cJSON *root_obj = cJSON_CreateObject(); + cJSON *state_obj = cJSON_CreateObject(); + cJSON *desired_obj = cJSON_CreateObject(); + cJSON *connections_obj = cJSON_CreateArray(); + + if ((root_obj == NULL) || (state_obj == NULL) || + (desired_obj == NULL) || (connections_obj == NULL)) { + goto cleanup; + } + + /* create array of desired BLE addresses */ + for (int i = 0; i < num_desired; i++) { + if (!desired[i].active) { + continue; + } + if (desired_conns_strings) { + /* nrfcloud stage wants the new, simpler form of + * desiredConnections where its just an array of strings + */ + CJADDARRSTR(connections_obj, desired[i].ble_mac_addr_str); + } else { + /* nrfcloud stage still uses older array of objects */ + cJSON *item; + + CJADDARROBJ(connections_obj, item); + CJADDSTRCS(item, "id", desired[i].ble_mac_addr_str); + } + } + + if (!num_desired) { + connections_obj = cJSON_CreateNull(); + } + + CJADDCHK(desired_obj, "desiredConnections", connections_obj); + CJADDCHK(state_obj, "desired", desired_obj); + CJADDCHK(root_obj, "state", state_obj); + + CJPRINT(root_obj, (char *)msg->data.ptr, msg->data_max_len, 0); + msg->data.len = strlen((char *)msg->data.ptr); + + ret = 0; + +cleanup: + cJSON_Delete(connections_obj); + cJSON_Delete(desired_obj); + cJSON_Delete(state_obj); + cJSON_Delete(root_obj); + + return ret; +} + +/* Start of functions to assemble large JSON string. No room to create these + * all at once in cJSON + */ +static int device_discover_add_service(char *discovered_json, + struct gw_msg *msg) +{ + char *ptr = (char *)msg->data.ptr; + + if (first_service == false) { + strcat(ptr, "}},"); + } + + strcat(ptr, discovered_json + 1); + + if (strlen(ptr) >= 3) { + ptr[strlen(ptr) - 3] = 0; + } + msg->data.len = strlen(ptr); + + memset(discovered_json, 0, MAX_SERVICE_BUF_SIZE); + first_service = false; + first_chrc = true; + return 0; +} + +static int device_discover_add_chrc(char *discovered_json, + struct gw_msg *msg) +{ + char *ptr = (char *)msg->data.ptr; + + if (first_chrc == false) { + strcat(ptr, ","); + } + + strcat(ptr, discovered_json + 1); + + if (strlen(ptr) >= 1) { + ptr[strlen(ptr) - 1] = 0; + } + msg->data.len = strlen(ptr); + + memset(discovered_json, 0, MAX_SERVICE_BUF_SIZE); + + first_chrc = false; + return 0; +} + +static int device_discover_add_ccc(char *discovered_json, + struct gw_msg *msg) +{ + char *ptr = (char *)msg->data.ptr; + + if (strlen(ptr) >= 2) { + ptr[strlen(ptr) - 2] = 0; + } + + strcat(ptr, discovered_json + 1); + strcat(ptr, "}"); + msg->data.len = strlen(ptr); + + memset(discovered_json, 0, MAX_SERVICE_BUF_SIZE); + + first_chrc = false; + return 0; +} + +static int svc_attr_encode(char *uuid, char *path, + struct gw_msg *msg) +{ + int ret = -ENOMEM; + cJSON *services = cJSON_CreateObject(); + cJSON *service = cJSON_CreateObject(); + cJSON *chrcs = cJSON_CreateObject(); + + if ((services == NULL) || (service == NULL) || (chrcs == NULL)) { + goto cleanup; + } + + CJADDSTRCS(service, "uuid", uuid); + + CJADDREFCS(service, "characteristics", chrcs); + CJADDREF(services, uuid, service); + + /* Print services and add to wrapper */ + CJPRINT(services, service_buffer, MAX_SERVICE_BUF_SIZE, 0); + ret = device_discover_add_service(service_buffer, msg); + +cleanup: + cJSON_Delete(chrcs); + cJSON_Delete(service); + cJSON_Delete(services); + return ret; +} + +static int chrc_attr_encode(char *uuid, char *path, struct uuid_handle_pair *h, + struct gw_msg *msg) +{ + int ret = -ENOMEM; + cJSON *chrc = cJSON_CreateObject(); + cJSON *descriptors = cJSON_CreateObject(); + cJSON *props = cJSON_CreateObject(); + cJSON *value_arr = cJSON_CreateArray(); + cJSON *parent_chrc = cJSON_CreateObject(); + uint8_t properties = h->properties; + + if ((chrc == NULL) || (descriptors == NULL) || (props == NULL) || + (value_arr == NULL) || (parent_chrc == NULL)) { + goto cleanup; + } + + CJADDSTRCS(chrc, "uuid", uuid); + CJADDSTRCS(chrc, "path", path); + + if (!h->value_len) { + CJADDARRNUM(value_arr, 0); + } else { + for (int i = 0; i < h->value_len; i++) { + CJADDARRNUM(value_arr, h->value[i]); + } + } + + /* Check and add properties */ + if (properties & BT_GATT_CHRC_READ) { + CJADDBOOLCS(props, "read", true); + } + if (properties & BT_GATT_CHRC_WRITE) { + CJADDBOOLCS(props, "write", true); + } + if (properties & BT_GATT_CHRC_INDICATE) { + CJADDBOOLCS(props, "indicate", true); + } + if (properties & BT_GATT_CHRC_NOTIFY) { + CJADDBOOLCS(props, "notify", true); + } + if (properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) { + CJADDBOOLCS(props, "writeWithoutResponse", true); + } + if (properties & BT_GATT_CHRC_AUTH) { + CJADDBOOLCS(props, "authorizedSignedWrite", true); + } + if ((properties == 0) || ((properties & + ~(BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | + BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY | + BT_GATT_CHRC_WRITE_WITHOUT_RESP | + BT_GATT_CHRC_AUTH)) != 0)) { + LOG_ERR("Unknown CHRC property: %d\n", properties); + cJSON_Delete(parent_chrc); + return -EINVAL; + } + + CJADDREFCS(chrc, "properties", props); + CJADDREFCS(chrc, "value", value_arr); + CJADDREFCS(chrc, "descriptors", descriptors); + CJADDREF(parent_chrc, uuid, chrc); + + /* Print parent_chrhc and add to service */ + CJPRINT(parent_chrc, service_buffer, MAX_SERVICE_BUF_SIZE, 0); + ret = device_discover_add_chrc(service_buffer, msg); + +cleanup: + cJSON_Delete(parent_chrc); + cJSON_Delete(value_arr); + cJSON_Delete(props); + cJSON_Delete(descriptors); + cJSON_Delete(chrc); + return ret; +} + +static int ccc_attr_encode(char *uuid, char *path, + struct gw_msg *msg, bool sub_enabled) +{ + int ret = -ENOMEM; + cJSON *descriptor = cJSON_CreateObject(); + cJSON *value_arr = cJSON_CreateArray(); + cJSON *parent_ccc = cJSON_CreateObject(); + + if ((descriptor == NULL) || (value_arr == NULL) || + (parent_ccc == NULL)) { + goto cleanup; + } + uint8_t val = sub_enabled ? BT_GATT_CCC_NOTIFY : 0; + + CJADDSTRCS(descriptor, "uuid", uuid); + CJADDARRNUM(value_arr, val); + CJADDARRNUM(value_arr, 0); + CJADDSTRCS(descriptor, "path", path); + + CJADDREFCS(descriptor, "value", value_arr); + CJADDREF(parent_ccc, uuid, descriptor); + + /* Print parent_ccc and add to service */ + CJPRINT(parent_ccc, service_buffer, MAX_SERVICE_BUF_SIZE, 0); + ret = device_discover_add_ccc(service_buffer, msg); + +cleanup: + cJSON_Delete(parent_ccc); + cJSON_Delete(value_arr); + cJSON_Delete(descriptor); + return ret; +} + +static int attr_encode(struct uuid_handle_pair *uuid_handle, + struct uuid_handle_pair *other_handle, + char *uuid_str, char *path, + struct gw_msg *msg) +{ + int ret = 0; + + bt_to_upper(uuid_str, strlen(uuid_str)); + bt_to_upper(path, strlen(path)); + + if (uuid_handle->attr_type == BT_ATTR_SERVICE) { + LOG_INF("Encoding Service : UUID: %s", uuid_str); + ret = svc_attr_encode(uuid_str, path, msg); + + } else if (uuid_handle->attr_type == BT_ATTR_CHRC) { + ret = chrc_attr_encode(uuid_str, path, uuid_handle, msg); + + } else if (uuid_handle->attr_type == BT_ATTR_CCC) { + ret = ccc_attr_encode(uuid_str, path, msg, + uuid_handle->sub_enabled || + (other_handle ? other_handle->sub_enabled : 0)); + + } else { + LOG_ERR("Unknown Attr Type"); + ret = -EINVAL; + } + memset(service_buffer, 0, MAX_SERVICE_BUF_SIZE); + return ret; +} + +void get_uuid_str(struct uuid_handle_pair *uuid_handle, char *str, size_t len) +{ + struct bt_uuid *uuid; + + if (uuid_handle->uuid_type == BT_UUID_TYPE_16) { + uuid = &uuid_handle->uuid_16.uuid; + } else if (uuid_handle->uuid_type == BT_UUID_TYPE_128) { + uuid = &uuid_handle->uuid_128.uuid; + } else { + str[0] = '\0'; + return; + } + bt_uuid_get_str(uuid, str, len); +} + +int device_discovery_encode(struct ble_device_conn *conn_ptr, + struct gw_msg *msg) +{ + char uuid_str[BT_UUID_STR_LEN]; + char service_attr_str[BT_UUID_STR_LEN]; + char path_dep_two_str[BT_UUID_STR_LEN]; + char path_str[BT_MAX_PATH_LEN]; + int ret = 0; + uint8_t num_encoded = 0; + + first_service = true; + + LOG_INF("Num Pairs: %d", conn_ptr->num_pairs); + + ret = create_device_wrapper(conn_ptr->ble_mac_addr_str, true, msg); + if (ret) { + return ret; + } + + for (int i = 0; i < conn_ptr->num_pairs; i++) { + struct uuid_handle_pair *uuid_handle; + struct uuid_handle_pair *uh = NULL; + + uuid_handle = conn_ptr->uuid_handle_pairs[i]; + if (uuid_handle == NULL) { + continue; + } + + if ((uuid_handle->uuid_type != BT_UUID_TYPE_16) && + (uuid_handle->uuid_type != BT_UUID_TYPE_128)) { + LOG_ERR("ERROR: unknown UUID type"); + ret = -EINVAL; + continue; + } + get_uuid_str(uuid_handle, uuid_str, BT_UUID_STR_LEN); + + switch (uuid_handle->path_depth) { + case 1: + for (int j = i; j >= 0; j--) { + uh = conn_ptr->uuid_handle_pairs[j]; + if (uh == NULL) { + continue; + } + if (uh->is_service == true) { + get_uuid_str(uh, service_attr_str, + BT_UUID_STR_LEN); + break; + } + } + snprintk(path_str, BT_MAX_PATH_LEN, "%s/%s", + service_attr_str, uuid_str); + break; + case 2: + uh = conn_ptr->uuid_handle_pairs[i - 1]; + if (uh != NULL) { + get_uuid_str(uh, path_dep_two_str, + BT_UUID_STR_LEN); + snprintk(path_str, BT_MAX_PATH_LEN, "%s/%s/%s", + service_attr_str, path_dep_two_str, + uuid_str); + } + break; + } + + ret = attr_encode(uuid_handle, uh, uuid_str, path_str, msg); + if (!ret) { + num_encoded++; + } + } + + /* make sure we output at least one attribute, or + * device_discovery_send() will send malformed JSON + */ + if (num_encoded) { + /* Add the remaing brackets to the JSON string */ + strcat((char *)msg->data.ptr, "}}}}}"); + msg->data.len = strlen((char *)msg->data.ptr); + + /* ignore invalid UUID type since others were ok */ + ret = 0; + + } else if (!ret) { + ret = -EINVAL; /* no data to send */ + } + return ret; +} + +static char *get_addr_from_des_conn_array(cJSON *array, int index) +{ + cJSON *item; + cJSON *address_obj; + cJSON *ble_address; + + item = cJSON_GetArrayItem(array, index); + if (item == NULL) { + return NULL; + } + + if (item->type != cJSON_String) { + address_obj = cJSON_GetObjectItem(item, "address"); + if (address_obj == NULL) { + ble_address = cJSON_GetObjectItem(item, "id"); + } else { + ble_address = cJSON_GetObjectItem(address_obj, "address"); + } + /* the nrfcloud stage we are on uses an array of objects + * named 'address' or 'id', so remember this later if + * the shell needs to manipulate the desiredConnections + * array + */ + desired_conns_strings = false; + } else { + /* the nrfcloud stage uses a simple array of strings */ + desired_conns_strings = true; + return item->valuestring; + } + + if (ble_address != NULL) { + return ble_address->valuestring; + } + return NULL; +} + +int desired_conns_handler(cJSON *desired_connections_obj) +{ + bool changed = false; + int num_cons; + char *ble_mac_addr_str; + + if (!desired_connections_obj) { + return 0; + } + + char *ptr = cJSON_Print(desired_connections_obj); + + LOG_INF("Desired conns: %s", ptr); + nrf_cloud_free(ptr); + + if (!cJSON_GetArraySize(desired_connections_obj)) { + /* there are none, so we can't tell what format the nrfcloud + * stage wants to use... pick something here, but it may be + * wrong, which means later, if the shell user creates the first + * connection, it could be in the wrong format; but, this + * is a temporary development issue and will go away soon + * (Q1 2021) + */ + desired_conns_strings = false; + } + + struct desired_conn *cons = get_desired_array(&num_cons); + + /* check whether anything has actually changed */ + /* first, search for additions to the array */ + for (int i = 0; i < cJSON_GetArraySize(desired_connections_obj); i++) { + ble_mac_addr_str = get_addr_from_des_conn_array(desired_connections_obj, i); + + if (ble_mac_addr_str != NULL) { + bool found = false; + + for (int j = 0; j < num_cons; j++) { + if ((strcmp(ble_mac_addr_str, cons[j].ble_mac_addr_str) == 0) && + cons[j].active) { + found = true; + break; + } + } + /* new device found in cloud's array */ + if (!found) { + LOG_INF("New device added by cloud: %s", + ble_mac_addr_str); + changed = true; + break; + } + } + } + + /* second, search for removals from the array */ + for (int i = 0; i < num_cons; i++) { + bool found = false; + + if (!cons[i].active) { + continue; + } + + for (int j = 0; j < cJSON_GetArraySize(desired_connections_obj); + j++) { + ble_mac_addr_str = get_addr_from_des_conn_array( + desired_connections_obj, j); + if (ble_mac_addr_str != NULL) { + if ((strcmp(ble_mac_addr_str, cons[i].ble_mac_addr_str) == 0) && + cons[i].active) { + found = true; + break; + } + } + } + + /* device removed from cloud's array, and it wasn't manually added */ + if (!found && !cons[i].manual) { + LOG_INF("Device removed by cloud: %s", + cons[i].ble_mac_addr_str); + changed = true; + break; + } + } + + if (!changed) { +#if defined(ACK_DELTA) + int ret; + struct nrf_cloud_tx_data tx_data = { + .topic_type = NRF_CLOUD_TOPIC_STATE, + .qos = MQTT_QOS_1_AT_LEAST_ONCE + }; + + ret = nrf_cloud_shadow_delta_response_encode(state_obj, true, &tx_data.data); + + if (ret == 0) { + ret = nrf_cloud_send(&tx_data); + nrf_cloud_free((void *)tx_data.data.ptr); + if (ret) { + LOG_ERR("nct_cc_send failed %d", ret); + } + } else { + LOG_ERR("Error acking shadow delta: %d", ret); + } +#else + LOG_INF("Desired connections did not change. Ignoring delta."); +#endif + } + + LOG_DBG("Gateway state change detected"); + + ble_conn_mgr_clear_desired(false); + + for (int i = 0; i < cJSON_GetArraySize(desired_connections_obj); i++) { + + ble_mac_addr_str = get_addr_from_des_conn_array(desired_connections_obj, i); + + if (ble_mac_addr_str != NULL) { + LOG_DBG("Desired BLE address: %s", ble_mac_addr_str); + + if (!ble_conn_mgr_enabled(ble_mac_addr_str)) { + LOG_INF("Skipping disabled device: %s", + ble_mac_addr_str); + continue; + } + if (ble_conn_mgr_add_conn(ble_mac_addr_str)) { + LOG_DBG("Conn already added"); + } + ble_conn_mgr_update_desired(ble_mac_addr_str, i); + } else { + LOG_ERR("Invalid desired connection"); + return -EINVAL; + } + } + ble_conn_mgr_update_connections(); + return 0; +} + +void ble_codec_init(void) +{ + /* Nothing to do. */ +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_codec.h b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_codec.h new file mode 100644 index 000000000000..2c63391218e4 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_codec.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef BLE_CODEC_H_ +#define BLE_CODEC_H_ + +#include "cJSON.h" +#include "ble_conn_mgr.h" + +struct gw_msg { + struct k_mutex lock; + int data_max_len; + struct nrf_cloud_data data; +}; + +int device_found_encode(uint8_t num_devices_found, struct gw_msg *msg); +int device_connect_result_encode(char *ble_address, bool conn_status, + struct gw_msg *msg); +int device_value_changed_encode(char *ble_address, char *uuid, char *path, + char *value, uint16_t value_length, + struct gw_msg *msg); +int device_chrc_read_encode(char *ble_address, char *uuid, char *path, + char *value, uint16_t value_length, + struct gw_msg *msg); +int device_discovery_add_attr(char *discovered_json, bool last_attr, + struct gw_msg *msg); +int device_discovery_encode(struct ble_device_conn *conn_ptr, + struct gw_msg *msg); +int device_value_write_result_encode(const char *ble_address, const char *uuid, const char *path, + const char *value, uint16_t value_length, + struct gw_msg *msg); +int device_descriptor_value_encode(const char *ble_address, char *uuid, + const char *path, char *value, + uint16_t value_length, + struct gw_msg *msg, bool changed); +int device_error_encode(const char *ble_address, const char *error_msg, + struct gw_msg *msg); +int device_disconnect_result_encode(char *ble_address, bool conn_status, + struct gw_msg *msg); +int gateway_shadow_data_encode(void *modem_ptr, struct gw_msg *msg); +int device_shadow_data_encode(const char *ble_address, bool connecting, + bool connected, struct gw_msg *msg); +int gateway_desired_list_encode(const struct desired_conn *desired, int num_desired, + struct gw_msg *msg); +int gateway_reported_encode(struct gw_msg *msg); +void get_uuid_str(struct uuid_handle_pair *uuid_handle, char *str, size_t len); +char *get_time_str(char *dst, size_t len); +void ble_codec_init(void); +int desired_conns_handler(cJSON *desired_connections_obj); + +#endif diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_conn_mgr.c b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_conn_mgr.c new file mode 100644 index 000000000000..9ffb4d0e55d6 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_conn_mgr.c @@ -0,0 +1,877 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "gateway.h" +#include "ble_conn_mgr.h" +#include "ble_codec.h" +#include "cJSON.h" +#include "ble.h" +#include "cloud_connection.h" + +#include +LOG_MODULE_REGISTER(ble_conn_mgr, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +static int num_connected; +static struct ble_device_conn connected_ble_devices[CONFIG_BT_MAX_CONN]; + +static struct desired_conn desired_connections[CONFIG_BT_MAX_CONN]; + +#define CONN_MGR_STACK_SIZE 4096 +#define CONN_MGR_PRIORITY 1 + +static struct uuid_handle_pair *find_pair_by_handle(uint16_t handle, + const struct ble_device_conn *conn_ptr, + int *index); +static int ble_conn_mgr_get_free_conn(struct ble_device_conn **conn_ptr); + +static void process_connection(int i) +{ + int err; + struct ble_device_conn *dev = &connected_ble_devices[i]; + + if (dev->free) { + return; + } + + if (!await_cloud_ready(K_MSEC(50))) { + return; + } + + /* Add devices to allowlist */ + if (!dev->added_to_allowlist && !dev->shadow_updated && !dev->connected) { + if (!dev->hidden) { + err = set_shadow_ble_conn(dev->ble_mac_addr_str, true, false); + if (!err) { + dev->shadow_updated = true; + LOG_INF("Shadow updated."); + } + } + if (!ble_add_to_allowlist(dev->ble_mac_addr_str, true)) { + dev->added_to_allowlist = true; + LOG_INF("Device added to allowlist."); + } + } + + /* Connected. Do discovering if not discovered or currently + * discovering. + */ + if (dev->connected && !dev->discovered && !dev->discovering) { + + err = ble_discover(dev); + + if (!err) { + LOG_DBG("ble_discover(%s) failed: %d", + dev->ble_mac_addr_str, err); + } + } + + /* Discovering done. Encode and send. */ + if (dev->connected && dev->encode_discovered) { + dev->encode_discovered = false; + device_discovery_send(&connected_ble_devices[i]); + + bt_addr_t ble_id; + + err = bt_addr_from_str(connected_ble_devices[i].ble_mac_addr_str, + &ble_id); + if (!err) { +#if defined(CONFIG_GATEWAY_BLE_FOTA) + LOG_INF("Checking for BLE update..."); + nrf_cloud_fota_ble_update_check(&ble_id); +#endif + } + } +} + +static void connection_manager(int unused1, int unused2, int unused3) +{ + int i; + bool printed = false; + + ble_conn_mgr_init(); + while (1) { + + for (i = 0; i < CONFIG_BT_MAX_CONN; i++) { + /* Manager is busy. Do nothing. */ + if (connected_ble_devices[i].discovering) { + if (!printed) { + LOG_DBG("Connection work busy."); + printed = true; + } + goto end; + } + } + printed = false; + + for (i = 0; i < CONFIG_BT_MAX_CONN; i++) { + process_connection(i); + } + +end: + /* give up the CPU for a while; otherwise we spin much faster + * than the cloud side could reasonably change things, + * or that devices might connect and disconnect, and so + * would needlessly consume CPU + */ + k_sleep(K_MSEC(100)); + } +} + +K_THREAD_DEFINE(conn_mgr_thread, CONN_MGR_STACK_SIZE, + connection_manager, NULL, NULL, NULL, + CONN_MGR_PRIORITY, 0, 0); + +static void init_conn(struct ble_device_conn *dev) +{ + memset(dev, 0, sizeof(struct ble_device_conn)); + dev->free = true; +} + +static void ble_conn_mgr_conn_reset(struct ble_device_conn + *dev) +{ + struct uuid_handle_pair *uuid_handle; + + LOG_INF("Connection removed to %s", dev->ble_mac_addr_str); + + if (!dev->free) { + if (num_connected) { + num_connected--; + } + } + + /* free in backwards order to try to reduce fragmentation */ + while (dev->num_pairs) { + uuid_handle = dev->uuid_handle_pairs[dev->num_pairs - 1]; + if (uuid_handle != NULL) { + dev->uuid_handle_pairs[dev->num_pairs - 1] = NULL; + k_free(uuid_handle); + } + dev->num_pairs--; + } + init_conn(dev); +} + +void ble_conn_mgr_update_connections(void) +{ + int i; + + for (i = 0; i < CONFIG_BT_MAX_CONN; i++) { + struct ble_device_conn *dev = &connected_ble_devices[i]; + + if (dev->connected || dev->added_to_allowlist) { + dev->disconnect = true; + + for (int j = 0; j < CONFIG_BT_MAX_CONN; j++) { + if (desired_connections[j].active) { + if (!strcmp(desired_connections[j].ble_mac_addr_str, + dev->ble_mac_addr_str)) { + /* If in the list then don't + * disconnect. + */ + dev->disconnect = false; + break; + } + } + } + + if (dev->disconnect) { + int err; + + LOG_INF("Cloud: disconnect device %s", + dev->ble_mac_addr_str); + if (dev->added_to_allowlist) { + if (!ble_add_to_allowlist(dev->ble_mac_addr_str, false)) { + dev->added_to_allowlist = false; + } + } + err = disconnect_device_by_addr(dev->ble_mac_addr_str); + if (err) { + LOG_ERR("Device might still be connected: %d", + err); + } + ble_conn_mgr_conn_reset(dev); + if (IS_ENABLED(CONFIG_SETTINGS)) { + LOG_INF("Saving settings"); + settings_save(); + } + } + } + } +} + +static void ble_conn_mgr_change_desired(const char *ble_mac_addr_str, uint8_t index, + bool active, bool manual) +{ + if (index <= CONFIG_BT_MAX_CONN) { + strncpy(desired_connections[index].ble_mac_addr_str, ble_mac_addr_str, + DEVICE_ADDR_LEN); + desired_connections[index].active = active; + desired_connections[index].manual = manual; + + LOG_INF("Desired Connection %s: %s %s", + active ? "Added" : "Removed", + ble_mac_addr_str, + manual ? "(manual)" : ""); + } +} + +struct desired_conn *get_desired_array(int *array_size) +{ + if (array_size == NULL) { + return NULL; + } + *array_size = ARRAY_SIZE(desired_connections); + return desired_connections; +} + +static int find_desired_connection(const char *ble_mac_addr_str, + struct desired_conn **pcon) +{ + struct desired_conn *con; + + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + con = &desired_connections[i]; + if ((strncmp(ble_mac_addr_str, con->ble_mac_addr_str, + sizeof(con->ble_mac_addr_str)) == 0) && + (ble_mac_addr_str[0] != '\0')) { + if (pcon) { + *pcon = con; + } + return i; + } + } + if (pcon) { + *pcon = NULL; + } + return -EINVAL; +} + +void ble_conn_mgr_update_desired(const char *ble_mac_addr_str, uint8_t index) +{ + ble_conn_mgr_change_desired(ble_mac_addr_str, index, true, false); +} + +int ble_conn_mgr_add_desired(const char *ble_mac_addr_str, bool manual) +{ + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (!desired_connections[i].active) { + ble_conn_mgr_change_desired(ble_mac_addr_str, i, true, manual); + return 0; + } + } + return -EINVAL; +} + +int ble_conn_mgr_rem_desired(const char *ble_mac_addr_str, bool manual) +{ + if (ble_mac_addr_str == NULL) { + return -EINVAL; + } + + int i = find_desired_connection(ble_mac_addr_str, NULL); + + if (i >= 0) { + ble_conn_mgr_change_desired(ble_mac_addr_str, i, false, manual); + return 0; + } + return -EINVAL; +} + +void ble_conn_mgr_clear_desired(bool all) +{ + LOG_INF("Desired Connections Cleared."); + + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (!desired_connections[i].manual || all) { + desired_connections[i].active = false; + } + } +} + +bool ble_conn_mgr_enabled(const char *ble_mac_addr_str) +{ + struct desired_conn *con; + + if (ble_mac_addr_str == NULL) { + return false; + } + + find_desired_connection(ble_mac_addr_str, &con); + + if (con != NULL) { + return !con->manual; + } + + return true; +} + +bool ble_conn_mgr_is_desired(const char *ble_mac_addr_str) +{ + struct desired_conn *con; + + if (ble_mac_addr_str == NULL) { + return false; + } + + find_desired_connection(ble_mac_addr_str, &con); + + return (con != NULL) && (con->active); +} + +int ble_conn_mgr_generate_path(const struct ble_device_conn *conn_ptr, + uint16_t handle, char *path, bool ccc) +{ + char path_str[BT_MAX_PATH_LEN]; + char service_uuid[BT_UUID_STR_LEN] = {0}; + char ccc_uuid[BT_UUID_STR_LEN]; + char chrc_uuid[BT_UUID_STR_LEN]; + uint8_t path_depth = 0; + struct uuid_handle_pair *uuid_handle; + int i; + + path_str[0] = '\0'; + path[0] = '\0'; + + + uuid_handle = find_pair_by_handle(handle, conn_ptr, &i); + if (uuid_handle == NULL) { + LOG_ERR("Path not generated; handle %u not found for ble_mac_addr_str %s", + handle, conn_ptr->ble_mac_addr_str); + return -ENXIO; + } + + path_depth = uuid_handle->path_depth; + + get_uuid_str(uuid_handle, chrc_uuid, BT_UUID_STR_LEN); + + if (ccc && ((i + 1) < conn_ptr->num_pairs)) { + uuid_handle = conn_ptr->uuid_handle_pairs[i + 1]; + if (uuid_handle == NULL) { + LOG_ERR("Path not generated; handle after %u " + "not found for ble_addr %s", + handle, conn_ptr->ble_mac_addr_str); + return -ENXIO; + } + get_uuid_str(uuid_handle, ccc_uuid, BT_UUID_STR_LEN); + } else { + ccc_uuid[0] = '\0'; + } + + for (int j = i; j >= 0; j--) { + uuid_handle = conn_ptr->uuid_handle_pairs[j]; + if (uuid_handle == NULL) { + continue; + } + if (uuid_handle->is_service) { + get_uuid_str(uuid_handle, service_uuid, + BT_UUID_STR_LEN); + break; + } + } + + if (!ccc) { + snprintk(path_str, BT_MAX_PATH_LEN, "%s/%s", + service_uuid, chrc_uuid); + } else { + snprintk(path_str, BT_MAX_PATH_LEN, "%s/%s/%s", + service_uuid, chrc_uuid, ccc_uuid); + } + + bt_to_upper(path_str, strlen(path_str)); + memset(path, 0, BT_MAX_PATH_LEN); + memcpy(path, path_str, strlen(path_str)); + LOG_DBG("Num pairs: %d, CCC: %d, depth: %d, generated path: %s", + conn_ptr->num_pairs, ccc, path_depth, path_str); + return 0; +} + +int ble_conn_mgr_add_conn(const char *ble_mac_addr_str) +{ + int err = 0; + struct ble_device_conn *connected_ble_ptr; + + /* Check if already added */ + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (connected_ble_devices[i].free == false) { + if (!strcmp(ble_mac_addr_str, connected_ble_devices[i].ble_mac_addr_str)) { + LOG_DBG("Connection already exsists"); + return 1; + } + } + } + + err = ble_conn_mgr_get_free_conn(&connected_ble_ptr); + + if (err) { + LOG_ERR("No free connections"); + return err; + } + + memcpy(connected_ble_ptr->ble_mac_addr_str, ble_mac_addr_str, DEVICE_ADDR_LEN); + connected_ble_ptr->free = false; + err = bt_addr_le_from_str(ble_mac_addr_str, "random", &connected_ble_ptr->bt_addr); + if (err) { + LOG_ERR("Address from string failed (err %d)", err); + } + num_connected++; + LOG_INF("BLE conn to %s added to manager", ble_mac_addr_str); + return err; +} + +int ble_conn_set_connected(struct ble_device_conn *connected_ble_ptr, bool connected) +{ + int err = 0; + + if (connected) { + connected_ble_ptr->connected = true; + } else { + connected_ble_ptr->connected = false; + connected_ble_ptr->shadow_updated = false; + } + LOG_INF("Conn updated: connected=%u", connected); + return err; +} + +int ble_conn_mgr_rediscover(const char *ble_mac_addr_str) +{ + int err = 0; + struct ble_device_conn *connected_ble_ptr; + + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ble_ptr); + if (err) { + LOG_ERR("Connection not found for ble_mac_addr_str %s", ble_mac_addr_str); + return err; + } + + if (!connected_ble_ptr->discovering) { + if (connected_ble_ptr->discovered) { + /* cloud wants data again; just send it */ + LOG_INF("Skipping device discovery on %s", + connected_ble_ptr->ble_mac_addr_str); + if (connected_ble_ptr->connected) { + connected_ble_ptr->encode_discovered = true; + } else { + err = device_discovery_send(connected_ble_ptr); + } + } else { + connected_ble_ptr->num_pairs = 0; + } + } + + return err; +} + +/* an nRF5 SDK device's normal MAC address least significant byte is + * incremented for the DFU bootloader MAC address + */ +int ble_conn_mgr_calc_other_addr(const char *old_addr, char *new_addr, int len, bool normal) +{ + bt_addr_le_t btaddr; + int err; + + err = bt_addr_le_from_str(old_addr, "random", &btaddr); + if (err) { + LOG_ERR("Could not convert address"); + return err; + } + if (normal) { + btaddr.a.val[0]--; + } else { + btaddr.a.val[0]++; + } + + memset(new_addr, 0, len); + bt_addr_le_to_str(&btaddr, new_addr, len); + bt_to_upper(new_addr, len); + return 0; +} + +int ble_conn_mgr_force_dfu_rediscover(const char *ble_mac_addr_str) +{ + int err = 0; + struct ble_device_conn *connected_ble_ptr; + + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ble_ptr); + if (err) { + LOG_ERR("Connection not found for ble_mac_addr_str %s", ble_mac_addr_str); + return err; + } + + if (!connected_ble_ptr->discovering) { + LOG_INF("Marking device %s to be rediscovered", + ble_mac_addr_str); + connected_ble_ptr->discovered = false; + connected_ble_ptr->num_pairs = 0; + } + + return 0; +} + +int ble_conn_mgr_remove_conn(const char *ble_mac_addr_str) +{ + int err = 0; + struct ble_device_conn *connected_ble_ptr; + + err = ble_conn_mgr_get_conn_by_addr(ble_mac_addr_str, &connected_ble_ptr); + if (err) { + LOG_ERR("Connection not found for ble_mac_addr_str %s", ble_mac_addr_str); + return err; + } + + ble_conn_mgr_conn_reset(connected_ble_ptr); + return err; +} + + +static int ble_conn_mgr_get_free_conn(struct ble_device_conn **conn_ptr) +{ + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (connected_ble_devices[i].free == true) { + *conn_ptr = &connected_ble_devices[i]; + LOG_DBG("Found Free connection: %d", i); + return 0; + } + } + return -ENOMEM; +} + +void ble_conn_mgr_check_pending(void) +{ + struct ble_device_conn *conn_ptr = connected_ble_devices; + + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++, conn_ptr++) { + if (conn_ptr->connected && conn_ptr->dfu_pending) { + if (conn_ptr->dfu_attempts) { + conn_ptr->dfu_attempts--; + } else { + conn_ptr->dfu_pending = false; + continue; + } + LOG_INF("Requesting pending DFU job for %s", + conn_ptr->ble_mac_addr_str); + nrf_cloud_fota_ble_update_check(&conn_ptr->bt_addr.a); + break; + } + } +} + +int ble_conn_mgr_get_conn_by_addr(const char *ble_mac_addr_str, struct ble_device_conn **conn_ptr) +{ + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (!strcmp(ble_mac_addr_str, connected_ble_devices[i].ble_mac_addr_str)) { + *conn_ptr = &connected_ble_devices[i]; + LOG_DBG("Conn Found"); + return 0; + } + } + return -ENOENT; + +} + +bool ble_conn_mgr_is_addr_connected(const char *ble_mac_addr_str) +{ + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (!strcmp(ble_mac_addr_str, connected_ble_devices[i].ble_mac_addr_str)) { + return connected_ble_devices[i].connected; + } + } + return false; +} + +static struct uuid_handle_pair *find_pair_by_handle(uint16_t handle, + const struct ble_device_conn *conn_ptr, + int *index) +{ + struct uuid_handle_pair *uuid_handle; + + for (int i = 0; i < conn_ptr->num_pairs; i++) { + uuid_handle = conn_ptr->uuid_handle_pairs[i]; + if (uuid_handle == NULL) { + continue; + } + if (handle == uuid_handle->handle) { + if (index != NULL) { + *index = i; + } + return uuid_handle; + } + } + return NULL; +} + +static void update_ccc_value(uint16_t handle, const struct ble_device_conn *conn_ptr, uint8_t value) +{ + struct uuid_handle_pair *ccc_handle; + + ccc_handle = find_pair_by_handle(handle + 1, conn_ptr, NULL); + if (ccc_handle && (ccc_handle->attr_type == BT_ATTR_CCC)) { + ccc_handle->value[0] = value; + ccc_handle->value_len = 2; + LOG_DBG("Updated CCC value"); + } else { + LOG_DBG("CCC not found for handle: %d", handle); + } +} + +int ble_conn_mgr_set_subscribed(uint16_t handle, uint8_t sub_index, + const struct ble_device_conn *conn_ptr) +{ + struct uuid_handle_pair *uuid_handle; + + uuid_handle = find_pair_by_handle(handle, conn_ptr, NULL); + if (uuid_handle) { + uuid_handle->sub_enabled = true; + uuid_handle->sub_index = sub_index; + update_ccc_value(handle, conn_ptr, BT_GATT_CCC_NOTIFY); + return 0; + } + + return 1; +} + + +int ble_conn_mgr_remove_subscribed(uint16_t handle, + const struct ble_device_conn *conn_ptr) +{ + struct uuid_handle_pair *uuid_handle; + + uuid_handle = find_pair_by_handle(handle, conn_ptr, NULL); + if (uuid_handle) { + uuid_handle->sub_enabled = false; + update_ccc_value(handle, conn_ptr, 0); + return 0; + } + return 1; +} + + +int ble_conn_mgr_get_subscribed(uint16_t handle, + const struct ble_device_conn *conn_ptr, + bool *status, uint8_t *sub_index) +{ + struct uuid_handle_pair *uuid_handle; + + uuid_handle = find_pair_by_handle(handle, conn_ptr, NULL); + if (uuid_handle) { + if (status) { + *status = uuid_handle->sub_enabled; + } + if (sub_index) { + *sub_index = uuid_handle->sub_index; + } + return 0; + } + + return 1; +} + +int ble_conn_mgr_get_uuid_by_handle(uint16_t handle, char *uuid, + const struct ble_device_conn *conn_ptr) +{ + char uuid_str[BT_UUID_STR_LEN]; + struct uuid_handle_pair *uuid_handle; + + memset(uuid, 0, BT_UUID_STR_LEN); + + uuid_handle = find_pair_by_handle(handle, conn_ptr, NULL); + if (uuid_handle) { + get_uuid_str(uuid_handle, uuid_str, BT_UUID_STR_LEN); + bt_to_upper(uuid_str, strlen(uuid_str)); + memcpy(uuid, uuid_str, strlen(uuid_str)); + LOG_DBG("Found UUID: %s for handle: %d", + uuid_str, + handle); + return 0; + } + + LOG_ERR("Handle %u on ble_addr %s not found; num pairs: %d", handle, + conn_ptr->ble_mac_addr_str, + (int)conn_ptr->num_pairs); + return 1; +} + +struct uuid_handle_pair *ble_conn_mgr_get_uhp_by_uuid(const char *uuid, + const struct ble_device_conn *conn_ptr) +{ + char str[BT_UUID_STR_LEN]; + + for (int i = 0; i < conn_ptr->num_pairs; i++) { + struct uuid_handle_pair *uuid_handle = conn_ptr->uuid_handle_pairs[i]; + + if (uuid_handle == NULL) { + continue; + } + + get_uuid_str(uuid_handle, str, sizeof(str)); + bt_to_upper(str, strlen(str)); + if (!strcmp(uuid, str)) { + LOG_DBG("Handle: %d, value_len: %d found for UUID: %s", + uuid_handle->handle, uuid_handle->value_len, uuid); + return uuid_handle; + } + } + + LOG_ERR("Handle not found for UUID: %s", uuid); + return NULL; +} + +int ble_conn_mgr_get_handle_by_uuid(uint16_t *handle, const char *uuid, + const struct ble_device_conn *conn_ptr) +{ + char str[BT_UUID_STR_LEN]; + + for (int i = 0; i < conn_ptr->num_pairs; i++) { + struct uuid_handle_pair *uuid_handle = + conn_ptr->uuid_handle_pairs[i]; + + if (uuid_handle == NULL) { + continue; + } + + get_uuid_str(uuid_handle, str, sizeof(str)); + bt_to_upper(str, strlen(str)); + if (!strcmp(uuid, str)) { + *handle = uuid_handle->handle; + LOG_DBG("Handle: %d found for UUID: %s", *handle, uuid); + return 0; + } + } + + LOG_ERR("Handle not found for UUID: %s", uuid); + return 1; +} + +int ble_conn_mgr_add_uuid_pair(const struct bt_uuid *uuid, uint16_t handle, + uint8_t path_depth, uint8_t properties, + uint8_t attr_type, + struct ble_device_conn *conn_ptr, + bool is_service) +{ + char str[BT_UUID_STR_LEN]; + + if (!conn_ptr) { + LOG_ERR("No connection ptr!"); + return -EINVAL; + } + + if (conn_ptr->num_pairs >= MAX_UUID_PAIRS) { + LOG_ERR("Max uuid pair limit reached on %s", + conn_ptr->ble_mac_addr_str); + return -E2BIG; + } + + LOG_DBG("Handle: %d", handle); + + if (!uuid) { + return 0; + } + + struct uuid_handle_pair *uuid_handle; + int err = 0; + + uuid_handle = conn_ptr->uuid_handle_pairs[conn_ptr->num_pairs]; + if (uuid_handle != NULL) { + /* we already discovered this device */ + if (uuid_handle->uuid_type != uuid->type) { + if (uuid_handle->uuid_type == BT_UUID_TYPE_16) { + /* likely got larger, so free and reallocate */ + k_free(uuid_handle); + uuid_handle = NULL; + } + } + } + + switch (uuid->type) { + case BT_UUID_TYPE_16: + if (uuid_handle == NULL) { + uuid_handle = (struct uuid_handle_pair *) + k_calloc(1, SMALL_UUID_HANDLE_PAIR_SIZE); + if (uuid_handle == NULL) { + LOG_ERR("Out of memory error allocating " + "for handle %u", handle); + err = -ENOMEM; + break; + } + } + memcpy(&uuid_handle->uuid_16, BT_UUID_16(uuid), + sizeof(struct bt_uuid_16)); + uuid_handle->uuid_type = uuid->type; + bt_uuid_get_str(&uuid_handle->uuid_16.uuid, str, sizeof(str)); + + LOG_DBG("\tChar: 0x%s", str); + break; + case BT_UUID_TYPE_128: + if (uuid_handle == NULL) { + uuid_handle = (struct uuid_handle_pair *) + k_calloc(1, LARGE_UUID_HANDLE_PAIR_SIZE); + if (uuid_handle == NULL) { + LOG_ERR("Out of memory error allocating " + "for handle %u", handle); + err = -ENOMEM; + break; + } + } + memcpy(&uuid_handle->uuid_128, BT_UUID_128(uuid), + sizeof(struct bt_uuid_128)); + uuid_handle->uuid_type = uuid->type; + bt_uuid_get_str(&uuid_handle->uuid_128.uuid, str, sizeof(str)); + + LOG_DBG("\tChar: 0x%s", str); + break; + default: + err = -EINVAL; + } + + if (err) { + conn_ptr->uuid_handle_pairs[conn_ptr->num_pairs] = NULL; + return err; + } + + uuid_handle->properties = properties; + uuid_handle->attr_type = attr_type; + uuid_handle->path_depth = path_depth; + uuid_handle->is_service = is_service; + uuid_handle->handle = handle; + LOG_DBG("%d. Handle: %d", conn_ptr->num_pairs, handle); + + /* finally, store it in the array of pointers to uuid_handle_pairs */ + conn_ptr->uuid_handle_pairs[conn_ptr->num_pairs] = uuid_handle; + + conn_ptr->num_pairs++; + + return 0; +} + +struct ble_device_conn *get_connected_device(unsigned int i) +{ + if (i < CONFIG_BT_MAX_CONN) { + if (!connected_ble_devices[i].free) { + return &connected_ble_devices[i]; + } + } + return NULL; +} + +int get_num_connected(void) +{ + return num_connected; +} + +void ble_conn_mgr_init(void) +{ + num_connected = 0; + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + init_conn(&connected_ble_devices[i]); + } +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_conn_mgr.h b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_conn_mgr.h new file mode 100644 index 000000000000..f2018d8a4b75 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/ble_conn_mgr.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _BLE_CONN_MGR_H_ +#define _BLE_CONN_MGR_H_ + +#include "ble.h" +#include + +#define MAX_UUID_PAIRS 68 +#define DEVICE_ADDR_LEN 18 + +#define BT_MAX_VALUE_LEN 32 +#define BT_MAX_UUID_LEN 37 +#define BT_MAX_PATH_LEN 111 + +#define MAX_DFU_ATTEMPTS 3 + +#define SMALL_UUID_HANDLE_PAIR_SIZE (sizeof(struct uuid_handle_pair) - \ + sizeof(struct bt_uuid_128) + \ + sizeof(struct bt_uuid_16)) +#define LARGE_UUID_HANDLE_PAIR_SIZE (sizeof(struct uuid_handle_pair)) + +struct uuid_handle_pair { + uint16_t handle; + uint8_t uuid_type; + uint8_t attr_type; + uint8_t path_depth; + uint8_t properties; + bool is_service; + bool sub_enabled; + uint8_t sub_index; + union { + struct bt_uuid_16 uuid_16; + struct bt_uuid_128 uuid_128; + }; + uint16_t value_len; + uint8_t value[BT_MAX_VALUE_LEN]; +}; + +struct ble_device_conn { + char ble_mac_addr_str[DEVICE_ADDR_LEN]; + bt_addr_le_t bt_addr; + struct uuid_handle_pair *uuid_handle_pairs[MAX_UUID_PAIRS]; + uint8_t num_pairs; + uint8_t dfu_attempts; + bool connected : 1; + bool discovering : 1; + bool free : 1; + bool discovered : 1; + bool encode_discovered : 1; + bool added_to_allowlist : 1; + bool shadow_updated : 1; + bool disconnect : 1; + bool dfu_pending : 1; + bool hidden : 1; +}; + +struct desired_conn { + char ble_mac_addr_str[DEVICE_ADDR_LEN]; + bool active; + bool manual; +}; + +int ble_conn_mgr_add_conn(const char *ble_mac_addr_str); +int ble_conn_mgr_calc_other_addr(const char *old_addr, char *new_addr, + int len, bool normal); +int ble_conn_mgr_generate_path(const struct ble_device_conn *conn_ptr, + uint16_t handle, + char *path, bool ccc); +int ble_conn_mgr_remove_conn(const char *ble_mac_addr_str); +int ble_conn_mgr_get_conn_by_addr(const char *ble_mac_addr_str, + struct ble_device_conn **conn_ptr); +int ble_conn_mgr_add_uuid_pair(const struct bt_uuid *uuid, uint16_t handle, + uint8_t path_depth, uint8_t properties, + uint8_t attr_type, + struct ble_device_conn *conn_ptr, + bool is_service); +int ble_conn_mgr_get_uuid_by_handle(uint16_t handle, char *uuid, + const struct ble_device_conn *conn_ptr); +int ble_conn_mgr_get_handle_by_uuid(uint16_t *handle, const char *uuid, + const struct ble_device_conn *conn_ptr); +struct uuid_handle_pair *ble_conn_mgr_get_uhp_by_uuid(const char *uuid, + const struct ble_device_conn *conn_ptr); +void ble_conn_mgr_init(void); +int ble_conn_set_connected(struct ble_device_conn *conn_ptr, bool connected); +int ble_conn_mgr_set_subscribed(uint16_t handle, uint8_t sub_index, + const struct ble_device_conn *conn_ptr); +int ble_conn_mgr_remove_subscribed(uint16_t handle, + const struct ble_device_conn *conn_ptr); +int ble_conn_mgr_get_subscribed(uint16_t handle, + const struct ble_device_conn *conn_ptr, + bool *status, uint8_t *sub_index); +void ble_conn_mgr_update_desired(const char *ble_mac_addr_str, uint8_t index); +int ble_conn_mgr_add_desired(const char *ble_mac_addr_str, bool manual); +int ble_conn_mgr_rem_desired(const char *ble_mac_addr_str, bool manual); +bool ble_conn_mgr_is_desired(const char *ble_mac_addr_str); +void ble_conn_mgr_clear_desired(bool all); +bool ble_conn_mgr_enabled(const char *ble_mac_addr_str); +void ble_conn_mgr_update_connections(void); +int ble_conn_mgr_rediscover(const char *ble_mac_addr_str); +struct ble_device_conn *get_connected_device(unsigned int i); +int get_num_connected(void); +struct desired_conn *get_desired_array(int *array_size); +bool ble_conn_mgr_is_addr_connected(const char *ble_mac_addr_str); +void ble_conn_mgr_print_mem(void); +int ble_conn_mgr_find_related_addr(const char *old_addr, char *new_addr, int len); +int ble_conn_mgr_force_dfu_rediscover(const char *ble_mac_addr_str); +void ble_conn_mgr_check_pending(void); + +#endif diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/cli.c b/samples/cellular/nrf_cloud_ble_gateway/src/ble/cli.c new file mode 100644 index 000000000000..126758cb22ca --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/cli.c @@ -0,0 +1,1679 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#undef __XSI_VISIBLE +#define __XSI_VISIBLE 1 +#undef _XOPEN_SOURCE +#define _XOPEN_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_NRF_CLOUD_FOTA) +#include +#endif +#include +#include +#include +#include +#include +#include +#include "ncs_version.h" +#include "nrf_cloud_transport.h" +#include "ble.h" +#include "ble_codec.h" +#include "ble_conn_mgr.h" +#include "peripheral_dfu.h" +#include "gateway.h" +#include "flash_test.h" + +LOG_MODULE_REGISTER(cli, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +#define DEFAULT_PASSWORD CONFIG_SHELL_DEFAULT_PASSWORD + +/* disable for now -- breaks BLE HCI after used */ +#define PRINT_CTLR_INFO_ENABLED 0 + +/* set to 1 to enable showing cert list */ +#define DISPLAY_CERT_LIST 0 + +#ifndef CONFIG_LOG_DOMAIN_ID +#define CONFIG_LOG_DOMAIN_ID 0 +#endif + +enum ble_cmd_type { + BLE_CMD_ALL, + BLE_CMD_MAC, + BLE_CMD_NAME +}; + +struct modem_param_info modem_param; + +/* was: CONFIG_AT_CMD_RESPONSE_MAX_LEN]; */ +static char response_buf[2048]; + +static void set_at_prompt(const struct shell *shell, bool at_mode); +void peripheral_dfu_set_test_mode(bool test); + +char *rem_eol(const char *s, char *d, int len) +{ + if (!s) { + return ""; + } + + char *p = d; + + strncpy(d, s, len); + + while (*p) { + if ((*p == '\r') || (*p == '\n')) { + *p = '\0'; + break; + } + p++; + } + return d; +} + +void cmd_testflash(const struct shell *shell) +{ + int ret; + + if (IS_ENABLED(CONFIG_FLASH_TEST)) { + shell_print(shell, "Testing flash..."); + ret = flash_test(); + } else { + shell_print(shell, "Test not enabled"); + ret = -ENOTSUP; + } + + if (ret) { + shell_error(shell, "Error testing flash: %d", ret); + } else { + shell_print(shell, "Test passed."); + } +} + +void print_fw_info(const struct shell *shell, bool verbose) +{ + extern struct fw_info m_firmware_info; + struct fw_info *info = &m_firmware_info; + + if (info) { + shell_print(shell, "Board name: \t%s", CONFIG_BOARD); + shell_print(shell, "App name: \t%s", STRINGIFY(PROJECT_NAME)); + shell_print(shell, "App rev: \t%s", CONFIG_APP_VERSION); + shell_print(shell, "nRF Connect SDK rev:\t%s", STRINGIFY(NCS_BUILD_VERSION)); + shell_print(shell, "Zephyr rev: \t%s", STRINGIFY(BUILD_VERSION)); + shell_print(shell, "Built date and time:\t%s %s", __DATE__, __TIME__); + if (!verbose) { + return; + } + if (!fw_info_check((uint32_t)info)) { + shell_error(shell, "fw_info struct is invalid"); + return; + } + shell_print(shell, "Ver: \t%u", info->version); + shell_print(shell, "Size: \t%u", info->size); + shell_print(shell, "Start: \t0x%08x", info->address); + shell_print(shell, "Boot: \t0x%08x", info->boot_address); + } +} + +#if defined(CONFIG_SYS_HEAP_RUNTIME_STATS) +extern struct sys_heap _system_heap; +#endif + +int heap_shell(const struct shell *shell, size_t argc, char **argv) +{ +#if defined(CONFIG_SYS_HEAP_RUNTIME_STATS) + int err; + struct sys_memory_stats kernel_stats; + + err = sys_heap_runtime_stats_get(&_system_heap, &kernel_stats); + if (err) { + shell_error(shell, "heap: failed to read kernel heap statistics, error: %d", err); + } else { + shell_print(shell, "kernel heap statistics:"); + shell_print(shell, "free: %6d", kernel_stats.free_bytes); + shell_print(shell, "allocated: %6d", kernel_stats.allocated_bytes); + shell_print(shell, "max. allocated: %6d\n", kernel_stats.max_allocated_bytes); + } +#endif /* CONFIG_SYS_HEAP_RUNTIME_STATS */ + +/* Calculate the system heap maximum size. */ +#define USED_RAM_END_ADDR POINTER_TO_UINT(&_end) +#define HEAP_BASE USED_RAM_END_ADDR +#define MAX_HEAP_SIZE (KB(CONFIG_SRAM_SIZE) - (HEAP_BASE - CONFIG_SRAM_BASE_ADDRESS)) + + shell_print(shell, "system heap statistics:"); + shell_print(shell, "max. size: %6ld", MAX_HEAP_SIZE); + + return 0; +} + +void print_modem_info(const struct shell *shell, bool creds) +{ +#ifdef CONFIG_MODEM_INFO + modem_info_init(); + modem_info_params_init(&modem_param); + + int ret = modem_info_params_get(&modem_param); + + if (ret) { + shell_error(shell, "Error getting modem info: %d", ret); + } else { + struct device_param *dev = &modem_param.device; + struct network_param *net = &modem_param.network; + struct sim_param *sim = &modem_param.sim; + char buf[128]; + + if (dev->modem_fw.type == MODEM_INFO_FW_VERSION) { + shell_print(shell, "Modem fw: \t%s", + rem_eol(dev->modem_fw.value_string, buf, sizeof(buf))); + } else { + shell_print(shell, "Modem fw type: \t%u val %u", + dev->modem_fw.type, + dev->modem_fw.value); + } + if (dev->battery.type == MODEM_INFO_BATTERY) { + shell_print(shell, "Battery: \t%u mV", + dev->battery.value); + } + if (dev->imei.type == MODEM_INFO_IMEI) { + shell_print(shell, "IMEI: \t\t%s", + rem_eol(dev->imei.value_string, buf, sizeof(buf))); + } + shell_print(shell, "Board: \t\t%s", + rem_eol(dev->board, buf, sizeof(buf))); + shell_print(shell, "App Name: \t%s", + rem_eol(dev->app_name, buf, sizeof(buf))); + shell_print(shell, "App Ver: \t%s", + rem_eol(dev->app_version, buf, sizeof(buf))); + + if (sim->uicc.type == MODEM_INFO_UICC) { + shell_print(shell, "UICC: \t\t%u", + sim->uicc.value); + } + if (sim->iccid.type == MODEM_INFO_ICCID) { + shell_print(shell, "ICCID: \t\t%s", + rem_eol(sim->iccid.value_string, buf, sizeof(buf))); + } + if (sim->imsi.type == MODEM_INFO_IMSI) { + shell_print(shell, "IMSI: \t\t%s", + rem_eol(sim->imsi.value_string, buf, sizeof(buf))); + } + + if (net->current_band.type == + MODEM_INFO_CUR_BAND) { + shell_print(shell, "Cur band: \t%u", + net->current_band.value); + } + if (net->sup_band.type == + MODEM_INFO_SUP_BAND) { + shell_print(shell, "Sup band: \t%s", + rem_eol(net->sup_band.value_string, buf, sizeof(buf))); + } + if (net->area_code.type == + MODEM_INFO_AREA_CODE) { + shell_print(shell, "Area code: \t%s", + rem_eol(net->area_code.value_string, buf, sizeof(buf))); + } + if (net->current_operator.type == + MODEM_INFO_OPERATOR) { + shell_print(shell, "Operator: \t%s", + rem_eol(net->current_operator.value_string, buf, sizeof(buf))); + } + if (net->mcc.type == + MODEM_INFO_MCC) { + shell_print(shell, "MCC: \t\t%u", + net->mcc.value); + } + if (net->mnc.type == + MODEM_INFO_MNC) { + shell_print(shell, "MNC: \t\t%u", + net->mnc.value); + } + if (net->ip_address.type == + MODEM_INFO_IP_ADDRESS) { + shell_print(shell, "IP address: \t%s", + rem_eol(net->ip_address.value_string, buf, sizeof(buf))); + } + if (net->ue_mode.type == + MODEM_INFO_UE_MODE) { + shell_print(shell, "UE mode: \t%u", + net->ue_mode.value); + } + if (net->apn.type == + MODEM_INFO_APN) { + shell_print(shell, "Access point: \t%s", + rem_eol(net->apn.value_string, buf, sizeof(buf))); + } + if (net->lte_mode.value == 1) { + shell_print(shell, "Mode: \t\tLTE-M"); + } else if (net->nbiot_mode.value == 1) { + shell_print(shell, "Mode: \t\tNB-IoT"); + } + shell_print(shell, "Cell ID: \t%ld", + (long)net->cellid_dec); + +#if defined(CONFIG_MODEM_INFO_ADD_DATE_TIME) || defined(CONFIG_DATE_TIME_MODEM) + char *str = net->date_time.value_string; + struct timespec now; + struct tm *tm; + char *atime; + + shell_print(shell, "Net date/time: \t%s " + "DST %d TZ %ld", + str, _daylight, _timezone); + clock_gettime(CLOCK_REALTIME, &now); + tm = localtime(&now.tv_sec); + if (tm == NULL) { + shell_error(shell, "could not get local time"); + return; + } + atime = asctime(tm); + if (atime == NULL) { + shell_error(shell, "could not get asctime"); + return; + } + shell_print(shell, "Time now: \t%s", asctime(tm)); +#else + char time_str[30] = {0}; + + if (get_time_str(time_str, sizeof(time_str))) { + shell_print(shell, "Current UTC: \t%s", time_str); + } +#endif + } +#endif + + if (creds) { + response_buf[0] = '\0'; + int err; + + err = nrf_modem_at_cmd(response_buf, sizeof(response_buf), "%s", "AT%CMNG=1"); + if (err < 0) { + shell_error(shell, "ERROR: %d", err); + } else if (err > 0) { + shell_error(shell, "ERROR: 0x%X", nrf_modem_at_err(err)); + } + + shell_print(shell, "%s", response_buf); + } +} + +void print_connection_status(const struct shell *shell) +{ + int err; + enum lte_lc_nw_reg_status status; + enum lte_lc_system_mode smode; + enum lte_lc_system_mode_preference smode_pref; + enum lte_lc_func_mode fmode; + + err = lte_lc_nw_reg_status_get(&status); + if (err) { + shell_error(shell, "lte_lc_nw_reg_status_get: %d", err); + } else { + shell_print(shell, "LTE reg status: \t%d", (int)status); + } + err = lte_lc_system_mode_get(&smode, &smode_pref); + if (err) { + shell_error(shell, "lte_lc_system_mode_get: %d", err); + } else { + shell_print(shell, "LTE system mode: \t%d", (int)smode); + shell_print(shell, "system mode pref: \t%d", (int)smode_pref); + } + err = lte_lc_func_mode_get(&fmode); + if (err) { + shell_error(shell, "lte_lc_func_mode_get: %d", err); + } else { + shell_print(shell, "LTE functional mode: \t%d", (int)fmode); + } +} + +static void print_cloud_info(const struct shell *shell) +{ + char stage[NRF_CLOUD_STAGE_ID_MAX_LEN] = "unknown"; + char tenant_id[NRF_CLOUD_TENANT_ID_MAX_LEN] = "unknown"; + static char id[NRF_CLOUD_CLIENT_ID_MAX_LEN] = "unknown"; + + nct_stage_get(stage, sizeof(stage)); + nrf_cloud_tenant_id_get(tenant_id, sizeof(tenant_id)); + nrf_cloud_client_id_get(id, sizeof(id)); + print_connection_status(shell); + + shell_print(shell, "nrfcloud stage: \t%s", stage); + shell_print(shell, "nrfcloud endpoint: \t%s", CONFIG_NRF_CLOUD_HOST_NAME); + shell_print(shell, "nrfcloud tenant id: \t%s", tenant_id); + shell_print(shell, "nrfcloud dev id: \t%s", id); + shell_print(shell, "security sectag: \t%d", CONFIG_NRF_CLOUD_SEC_TAG); +} + +static void print_ctlr_info(const struct shell *shell) +{ +#if PRINT_CTLR_INFO_ENABLED + struct net_buf *rsp; + int ret; + + ret = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_VERSION_INFO, NULL, &rsp); + if (ret) { + shell_error(shell, "Could not read version info: %d", ret); + } else { + struct bt_hci_rp_vs_read_version_info *info; + + info = (void *)rsp->data; + shell_print(shell, "HW platform: \t0x%04x", info->hw_platform); + shell_print(shell, "HW variant: \t0x%04x", info->hw_variant); + shell_print(shell, "FW variant: \t0x%02x", info->fw_variant); + shell_print(shell, "FW version: \t%u.%u", + info->fw_version, + sys_le16_to_cpu(info->fw_revision)); + shell_print(shell, "Build: \t0x%08X", info->fw_build); + } + + if (!IS_ENABLED(CONFIG_BT_LL_SOFTDEVICE)) { + ret = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_BUILD_INFO, + NULL, &rsp); + if (ret) { + LOG_ERR("Could not read build info: %d", ret); + } else { + struct bt_hci_rp_vs_read_build_info *build; + + build = (void *)rsp->data; + shell_print(shell, "OS build: \t%s", + (const char *)build->info); + } + } + + if (IS_ENABLED(CONFIG_BT_LL_SOFTDEVICE)) { + ret = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_STATIC_ADDRS, + NULL, &rsp); + if (ret || (rsp == NULL)) { + LOG_ERR("Could not read static address: %d", ret); + } else { + struct bt_hci_rp_vs_read_static_addrs *addrs; + uint8_t *a; + + addrs = (void *)rsp->data; + a = &addrs->a[0].bdaddr.val[0]; + shell_print(shell, "Static addr: \t" + "%02X:%02X:%02X:%02X:%02X:%02X", + a[0], a[1], a[2], a[3], a[4], a[5]); + } + } +#else + shell_print(shell, "unavailable"); +#endif +} + +static void print_scan_info(const struct shell *shell) +{ + unsigned int i; + struct ble_scanned_dev *dev; + + shell_print(shell, " MAC, type, RSSI, name"); + for (i = 0; i < MAX_SCAN_RESULTS; i++) { + dev = get_scanned_device(i); + if (dev == NULL) { + shell_print(shell, ""); + break; + } + shell_print(shell, "%u. %s, %d, %s", + i + 1, dev->ble_mac_addr_str, dev->rssi, dev->name); + } +} + +static void print_conn_info(const struct shell *shell, bool show_path, + bool notify) +{ + unsigned int i; + unsigned int j; + unsigned int k; + int count = 0; + struct ble_device_conn *dev; + struct uuid_handle_pair *up; + char uuid_str[BT_UUID_STR_LEN]; + char path[BT_MAX_PATH_LEN]; + char props[64]; + static const char * const types[] = {"svc", "chr", "---", "ccc"}; + static const char * const properties[] = {"brdcst", "read", "wrnorsp", "write", + "notif", "indi", "auth", "extprop"}; + + if (!notify) { + shell_print(shell, " MAC, connected, discovered, shadow" + " updated, denylist status, ctrld by," + " visible, num UUIDs"); + } + for (i = 0; i < CONFIG_BT_MAX_CONN; i++) { + dev = get_connected_device(i); + if (dev == NULL) { + continue; + } + count++; + shell_print(shell, "%u. %s, %s, %s, %s, %s, %s, %s, UUIDs:%u", + count, + dev->ble_mac_addr_str, + dev->connected ? "CONNNECTED" : "disconnected", + dev->discovered ? "DISCOVERED" : + (dev->discovering ? "DISCOVERING" : "not dscvred"), + dev->shadow_updated ? "SHADOW UPDATED" : + "shadow not set", + dev->added_to_allowlist ? "CONN ALLOWED" : + "conn denied", + ble_conn_mgr_enabled(dev->ble_mac_addr_str) ? "CLOUD" : "local", + !dev->hidden ? "VISIBLE" : "hidden", + (unsigned int)dev->num_pairs + ); + if (!notify) { + shell_print(shell, " is service, UUID, UUID type, " + "handle, type, path depth, " + "properties, sub index, sub " + "enabled, val_len"); + } + for (j = 0; j < dev->num_pairs; j++) { + up = dev->uuid_handle_pairs[j]; + if (up == NULL) { + continue; + } + if (notify && !up->sub_enabled) { + continue; + } + get_uuid_str(up, uuid_str, BT_UUID_STR_LEN); + props[0] = '\0'; + for (k = 0; k < 8; k++) { + if (up->properties & (1 << k)) { + if (props[0]) { + strncat(props, ", ", sizeof(props) - 1); + } + strncat(props, properties[k], sizeof(props) - 1); + } + } + shell_print(shell, + " %u, %s, %s, %u, %u, %s, %u, 0x%02X (%s), %u, %s, %u", + j + 1, + up->is_service ? "serv" : "char", + uuid_str, + up->uuid_type, + up->handle, + up->attr_type <= BT_ATTR_CCC ? types[up->attr_type] : "unk", + (unsigned int)up->path_depth, + (unsigned int)up->properties, + props, + (unsigned int)up->sub_index, + up->sub_enabled ? "NOTIFY ON" : "notify off", + up->value_len + ); + if (up->value_len) { + shell_hexdump(shell, up->value, up->value_len); + } + if (show_path) { + ble_conn_mgr_generate_path(dev, up->handle, path, + up->attr_type == BT_ATTR_CCC); + shell_print(shell, " %u, %s", + up->handle, path); + } + } + } +} + +static void print_irq_info(const struct shell *shell) +{ + int i; + extern char _vector_start[]; + void **vectors = (void *)_vector_start; + + shell_print(shell, "IRQn, entry, prio, en, pend, active, arg"); + for (i = -15; i < IRQ_TABLE_SIZE; i++) { + void *entry; + const void *arg; + + if (i < 0) { + entry = vectors[i + 16]; + arg = 0; + } else { + entry = _sw_isr_table[i].isr; + arg = _sw_isr_table[i].arg; + } + if (entry == z_irq_spurious) { + continue; + } + shell_print(shell, "% 3d. %10p, %d, %d, %d, %d, %10p", + i, entry, + NVIC_GetPriority(i), NVIC_GetEnableIRQ(i), + NVIC_GetPendingIRQ(i), NVIC_GetActive(i), + arg); + } +} + +static int ble_conn_save(const struct shell *shell) +{ + int num; + struct desired_conn *desired = get_desired_array(&num); + + return set_shadow_desired_conn(desired, num); +} + +static int ble_conn_mac(const struct shell *shell, char *ble_mac_addr_str) +{ + int err; + + shell_print(shell, " Adding connection to %s...", ble_mac_addr_str); + err = ble_conn_mgr_add_conn(ble_mac_addr_str); + if (!err) { + shell_print(shell, " Adding to desired list..."); + err = ble_conn_mgr_add_desired(ble_mac_addr_str, true); + } + if (err) { + shell_error(shell, " Failed: error %d", err); + } else { + shell_print(shell, " Done."); + } + + return err; +} + +static int ble_conn_all(const struct shell *shell) +{ + unsigned int i; + struct ble_scanned_dev *dev; + int err = 0; + int count = 0; + + for (i = 0; i < MAX_SCAN_RESULTS; i++) { + dev = get_scanned_device(i); + if (dev == NULL) { + break; + } + err = ble_conn_mac(shell, dev->ble_mac_addr_str); + if (!err) { + count++; + } + } + + shell_print(shell, "Connected to %d devices", count); + return err; +} + +static int ble_conn_name(const struct shell *shell, char *name) +{ + unsigned int i; + struct ble_scanned_dev *dev; + + for (i = 0; i < MAX_SCAN_RESULTS; i++) { + dev = get_scanned_device(i); + if (dev == NULL) { + break; + } + if (strcmp(name, dev->name) == 0) { + shell_print(shell, "Connecting to %s", name); + return ble_conn_mac(shell, dev->ble_mac_addr_str); + } + } + + shell_error(shell, "BLE device %s not found", name); + return -EINVAL; +} + +static int ble_disconn_mac(const struct shell *shell, char *ble_mac_addr_str) +{ + int err; + + shell_print(shell, " Disconnecting device..."); + if (!ble_conn_mgr_is_addr_connected(ble_mac_addr_str)) { + err = ble_add_to_allowlist(ble_mac_addr_str, false); + if (err) { + shell_error(shell, " Failed to remove from allowlist: %d", err); + } + } + err = disconnect_device_by_addr(ble_mac_addr_str); + if (err) { + shell_error(shell, " Error disconnecting device: %d", err); + } + shell_print(shell, " Removing connection to %s...", ble_mac_addr_str); + err = ble_conn_mgr_remove_conn(ble_mac_addr_str); + if (!err) { + shell_print(shell, " Removing from desired list..."); + err = ble_conn_mgr_rem_desired(ble_mac_addr_str, true); + if (err) { + shell_error(shell, " Failed to remove from desired list: %d", err); + } + } + if (err) { + shell_error(shell, " Failed: error %d", err); + } else { + shell_print(shell, " Done."); + } + + return err; +} + +static int ble_disconn_all(const struct shell *shell) +{ + unsigned int i; + struct ble_device_conn *con; + int err = 0; + int count = 0; + + for (i = 0; i < CONFIG_BT_MAX_CONN; i++) { + con = get_connected_device(i); + if (con == NULL) { + continue; + } + err = ble_disconn_mac(shell, con->ble_mac_addr_str); + if (!err) { + count++; + } + } + + shell_print(shell, "Disconnected %d devices", count); + return 0; +} + +static int ble_disconn_name(const struct shell *shell, char *name) +{ + unsigned int i; + struct ble_scanned_dev *dev; + + for (i = 0; i < MAX_SCAN_RESULTS; i++) { + dev = get_scanned_device(i); + if (dev == NULL) { + break; + } + if (strcmp(name, dev->name) == 0) { + shell_print(shell, "Disconnecting %s", name); + return ble_disconn_mac(shell, dev->ble_mac_addr_str); + } + } + + shell_print(shell, "BLE device %s not found", name); + return -EINVAL; +} + +static enum ble_cmd_type get_cmd_type(char *arg) +{ + enum ble_cmd_type ret; + + if (strcmp(arg, "all") == 0) { + ret = BLE_CMD_ALL; + } else if (strchr(arg, ':')) { + ret = BLE_CMD_MAC; + } else { + ret = BLE_CMD_NAME; + } + return ret; +} + +/* This dynamic command helper is called repeatedly by the shell when + * the user wants command completion which lists possible MAC addresses. + * It calls this until it returns NULL. + * + * The first for loop returns all the devices we are already supposed to + * connect to, because they're in the shadow's desiredConnections array. + * The second nested for loop effectively appends to that list any scan + * results which are not also devices we should connect with (so those + * devices are not listed twice). + * + * This would happen if the user used the CLI to scan for advertising + * devices with 'ble scan', then used 'ble conn' to connect to one + * (which temporarily adds this new device to the desiredConnections + * array), and then used a dynamic command completion which hits this + * function. + * + * Often, the scan results are empty, and there is just a short list + * of desired connections, so the second loop does nothing. + */ +static const char *get_mac_addr(size_t idx, bool all) +{ + unsigned int i; + unsigned int j; + struct ble_scanned_dev *dev; + struct ble_device_conn *con; + int count = 0; + + /* combine our list of desired connections and scan results */ + for (i = 0; i < CONFIG_BT_MAX_CONN; i++) { + con = get_connected_device(i); + if (con == NULL) { + continue; + } + if (count == idx) { + return con->ble_mac_addr_str; + } + count++; + } + + if (!all) { + return NULL; + } + + for (i = 0; i < MAX_SCAN_RESULTS; i++) { + dev = get_scanned_device(i); + if (dev == NULL) { + break; /* end of list of scanned devices */ + } + for (j = 0; j < CONFIG_BT_MAX_CONN; j++) { + con = get_connected_device(i); + if (con == NULL) { + continue; + } + if (strcmp(con->ble_mac_addr_str, dev->ble_mac_addr_str) == 0) { + dev = NULL; + break; + } + } + if (dev == NULL) { + continue; /* scan matches connected device, so skip */ + } + if (count == idx) { + return dev->ble_mac_addr_str; + } + count++; + } + + return NULL; +} + +static const char *get_device_name(size_t idx) +{ + unsigned int i; + struct ble_scanned_dev *dev; + int count = 0; + + for (i = 0; i < MAX_SCAN_RESULTS; i++) { + dev = get_scanned_device(i); + if (dev == NULL) { + break; /* end of list of scanned devices */ + } + if (strlen(dev->name) == 0) { + continue; + } + if (count == idx) { + return dev->name; + } + count++; + } + + return NULL; +} + +static const char *get_cmd_param(size_t idx) +{ + /* all is a valid parameter */ + if (idx == 0) { + return "all"; + } + /* return any known device names */ + if (idx <= get_num_scan_names()) { + return get_device_name(idx - 1); + } + /* then any MAC addresses */ + return get_mac_addr(idx - 1 - get_num_scan_names(), true); +} + +uint32_t get_log_module_level(const struct shell *shell, const char *name) +{ + uint32_t modules_cnt = log_src_cnt_get(CONFIG_LOG_DOMAIN_ID); + const char *tmp_name; + uint32_t i; + uint32_t level = LOG_LEVEL_NONE; + + /* if current log level of ble module >= INF, then no print needed */ + for (i = 0U; i < modules_cnt; i++) { + tmp_name = log_source_name_get(CONFIG_LOG_DOMAIN_ID, i); + if (tmp_name == NULL) { + continue; + } + if (strcmp(tmp_name, name) == 0) { + level = log_filter_get(shell->log_backend->backend, + CONFIG_LOG_DOMAIN_ID, i, true); + break; + } + } + return level; +} + +/* COMMAND HANDLERS */ + +static int cmd_info_list(const struct shell *shell, size_t argc, char **argv) +{ + /* Get MAC from argv */ + shell_print(shell, "MAC selected: %s", argv[0]); + + size_t idx = 0; + const char *ble_mac_addr_str; + + for (idx = 0;; idx++) { + ble_mac_addr_str = get_mac_addr(idx, true); + if (ble_mac_addr_str) { + shell_print(shell, "%zd. %s", idx, ble_mac_addr_str); + } else { + shell_print(shell, "end of list"); + break; + } + } + return 0; +} + +#define DYNAMIC_ADDR_HELP " Valid BLE MAC address" + +static void get_dynamic_addr(size_t idx, struct shell_static_entry *entry) +{ + entry->syntax = get_mac_addr(idx, true); + + if (entry->syntax == NULL) { + return; + } + + entry->handler = cmd_info_list; + entry->subcmd = NULL; + entry->help = DYNAMIC_ADDR_HELP; + entry->args.mandatory = 1; + entry->args.optional = 6; +} + +SHELL_DYNAMIC_CMD_CREATE(dynamic_addr, get_dynamic_addr); + +static int cmd_param_list(const struct shell *shell, size_t argc, char **argv) +{ + /* Get MAC from argv */ + shell_print(shell, "param selected: %s", argv[0]); + + size_t idx = 0; + const char *param; + + shell_print(shell, "num scan names: %d", get_num_scan_names()); + shell_print(shell, "num scan results: %d", get_num_scan_results()); + shell_print(shell, "num connected: %d", get_num_connected()); + + for (idx = 0;; idx++) { + param = get_cmd_param(idx); + if (param) { + shell_print(shell, "%zd. %s", idx, param); + } else { + shell_print(shell, "end of list"); + break; + } + } + return 0; +} + +#define DYNAMIC_PARAM_HELP " all | name | MAC" + +static void get_dynamic_param(size_t idx, struct shell_static_entry *entry) +{ + entry->syntax = get_cmd_param(idx); + + if (entry->syntax == NULL) { + return; + } + + entry->handler = cmd_param_list; + entry->subcmd = NULL; + entry->help = DYNAMIC_PARAM_HELP; + entry->args.mandatory = 1; + entry->args.optional = 6; +} + +SHELL_DYNAMIC_CMD_CREATE(dynamic_param, get_dynamic_param); + +static int cmd_info_gateway(const struct shell *shell, size_t argc, char **argv) +{ + bool detailed = false; + + if (argc > 1) { + if (strcmp(argv[1], "verbose") == 0) { + detailed = true; + } + } + + print_fw_info(shell, true); + return 0; +} + +static int cmd_info_modem(const struct shell *shell, size_t argc, char **argv) +{ + bool creds = false; + + if (argc > 1) { + if (strcmp(argv[1], "verbose") == 0) { + creds = true; + } + } + print_modem_info(shell, creds); + return 0; +} + +static int cmd_info_cloud(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + print_cloud_info(shell); + return 0; +} + +static int cmd_info_ctlr(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + print_ctlr_info(shell); + return 0; +} + +static int cmd_info_scan(const struct shell *shell, size_t argc, + char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + print_scan_info(shell); + return 0; +} + +static int cmd_info_conn(const struct shell *shell, size_t argc, + char **argv) +{ + bool path = false; + bool notify = false; + int i; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "path") == 0) { + path = true; + } else if (strcmp(argv[i], "notify") == 0) { + notify = true; + } else { + shell_error(shell, "unknown option: %s", argv[i]); + return 0; + } + } + print_conn_info(shell, path, notify); + return 0; +} + +static int cmd_info_irq(const struct shell *shell, size_t argc, + char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + print_irq_info(shell); + return 0; +} + +#if CONFIG_GATEWAY_BLE_FOTA +static int cmd_ble_test(const struct shell *shell, size_t argc, char **argv) +{ + static bool test_mode = true; + + shell_print(shell, "Setting BLE FOTA test mode to %u", test_mode); + peripheral_dfu_set_test_mode(test_mode); + test_mode = !test_mode; + return 0; +} +#endif + +static int cmd_ble_scan(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + /* if current log level of ble module < INF, then print needed */ + bool print_scan = (get_log_module_level(shell, "ble") < LOG_LEVEL_INF); + + scan_start(print_scan); + return 0; +} + +static int cmd_ble_save(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "save connection list to cloud..."); + return ble_conn_save(shell); +} + +static int cmd_ble_conn(const struct shell *shell, size_t argc, char **argv) +{ + char *arg = argv[0]; + + if (argc < 1) { + return -EINVAL; + } + + switch (get_cmd_type(arg)) { + case BLE_CMD_ALL: + shell_print(shell, "connecting all BLE devices..."); + return ble_conn_all(shell); + case BLE_CMD_MAC: + shell_print(shell, "connecting to MAC %s", arg); + return ble_conn_mac(shell, arg); + case BLE_CMD_NAME: + shell_print(shell, "connecting to name %s", arg); + return ble_conn_name(shell, arg); + default: + return -EINVAL; + } +} + +static void get_dynamic_ble_conn(size_t idx, struct shell_static_entry *entry) +{ + entry->syntax = get_cmd_param(idx); + + if (entry->syntax == NULL) { + return; + } + + entry->handler = cmd_ble_conn; + entry->subcmd = NULL; + entry->help = DYNAMIC_PARAM_HELP; + entry->args.mandatory = 1; + entry->args.optional = 0; +} + +SHELL_DYNAMIC_CMD_CREATE(dynamic_ble_conn, get_dynamic_ble_conn); + +static int cmd_ble_disc(const struct shell *shell, size_t argc, char **argv) +{ + char *arg = argv[0]; + + if (argc < 1) { + return -EINVAL; + } + + switch (get_cmd_type(arg)) { + case BLE_CMD_ALL: + shell_print(shell, "disconnecting all BLE devices..."); + return ble_disconn_all(shell); + case BLE_CMD_MAC: + shell_print(shell, "disconnecting MAC %s", arg); + return ble_disconn_mac(shell, arg); + case BLE_CMD_NAME: + shell_print(shell, "disconnecting name %s", arg); + return ble_disconn_name(shell, arg); + default: + return -EINVAL; + } +} + +static void get_dynamic_ble_disc(size_t idx, struct shell_static_entry *entry) +{ + entry->syntax = get_cmd_param(idx); + + if (entry->syntax == NULL) { + return; + } + + entry->handler = cmd_ble_disc; + entry->subcmd = NULL; + entry->help = DYNAMIC_PARAM_HELP; + entry->args.mandatory = 1; + entry->args.optional = 0; +} + +SHELL_DYNAMIC_CMD_CREATE(dynamic_ble_disc, get_dynamic_ble_disc); + +static int cmd_ble_notif_en(const struct shell *shell, size_t argc, char **argv) +{ + char *arg = argv[0]; + + if (argc < 1) { + return -EINVAL; + } + + switch (get_cmd_type(arg)) { + case BLE_CMD_ALL: + shell_print(shell, "enable notifications on all devices"); + struct ble_device_conn *dev; + int i; + int count = 0; + + for (i = 0; i < CONFIG_BT_MAX_CONN; i++) { + dev = get_connected_device(i); + if (dev == NULL) { + continue; + } + shell_print(shell, "enabling on MAC %s...", dev->ble_mac_addr_str); + ble_subscribe_all(dev->ble_mac_addr_str, BT_GATT_CCC_NOTIFY); + count++; + } + if (!count) { + shell_warn(shell, "Connect a device first."); + } + break; + case BLE_CMD_MAC: + if ((argc < 2) || (strcmp(argv[1], "all") == 0)) { + shell_print(shell, "enable all notifications on MAC %s", + arg); + ble_subscribe_all(arg, BT_GATT_CCC_NOTIFY); + return 0; + } + uint16_t handle = atoi(argv[1]); + + shell_print(shell, "enable notification on MAC %s handle %u", + arg, handle); + ble_subscribe_handle(arg, handle, BT_GATT_CCC_NOTIFY); + break; + default: + return -EINVAL; + } + return 0; +} + +static void get_dynamic_ble_en(size_t idx, struct shell_static_entry *entry) +{ + entry->syntax = get_cmd_param(idx); + + if (entry->syntax == NULL) { + return; + } + + entry->handler = cmd_ble_notif_en; + entry->subcmd = NULL; + entry->help = DYNAMIC_PARAM_HELP; + entry->args.mandatory = 1; + entry->args.optional = 2; +} + +SHELL_DYNAMIC_CMD_CREATE(dynamic_ble_en, get_dynamic_ble_en); + +static int cmd_ble_notif_dis(const struct shell *shell, size_t argc, char **argv) +{ + char *arg = argv[0]; + + if (argc < 1) { + return -EINVAL; + } + + switch (get_cmd_type(arg)) { + case BLE_CMD_ALL: + shell_print(shell, "disable notifications on all devices"); + struct ble_device_conn *dev; + int i; + + for (i = 0; i < CONFIG_BT_MAX_CONN; i++) { + dev = get_connected_device(i); + if (dev == NULL) { + continue; + } + shell_print(shell, "disabling on MAC %s...", + dev->ble_mac_addr_str); + ble_subscribe_all(dev->ble_mac_addr_str, 0); + } + break; + case BLE_CMD_MAC: + if ((argc < 2) || (strcmp(argv[1], "all") == 0)) { + shell_print(shell, "disable all notifications on MAC %s", + arg); + ble_subscribe_all(arg, 0); + return 0; + } + uint16_t handle = atoi(argv[1]); + + shell_print(shell, "disable notification on MAC %s handle %u", + arg, handle); + ble_subscribe_handle(arg, handle, 0); + break; + default: + return -EINVAL; + } + return 0; +} + +static void get_dynamic_ble_dis(size_t idx, struct shell_static_entry *entry) +{ + entry->syntax = get_cmd_param(idx); + + if (entry->syntax == NULL) { + return; + } + + entry->handler = cmd_ble_notif_dis; + entry->subcmd = NULL; + entry->help = DYNAMIC_PARAM_HELP; + entry->args.mandatory = 1; + entry->args.optional = 1; +} + +SHELL_DYNAMIC_CMD_CREATE(dynamic_ble_dis, get_dynamic_ble_dis); + +static void set_at_prompt(const struct shell *shell, bool at_mode) +{ + static bool normal_prompt = true; + + if (at_mode && normal_prompt) { + normal_prompt = false; + + shell_print(shell, "Type 'exit' to exit AT mode"); + shell_prompt_change(shell, ""); + + shell_use_colors_set(shell, false); + + } else if (!at_mode && !normal_prompt) { + normal_prompt = true; + + shell_prompt_change(shell, CONFIG_SHELL_PROMPT_SECURE); + + /* hack: manually turn off select mode as is done in + * zephyr/subsys/shell/shell.c:alt_metakeys_handle() + * when it receives SHELL_VT100_ASCII_ALT_R -- there is + * no API for doing so + */ + shell_set_root_cmd(NULL); + + shell_use_colors_set(shell, true); + } +} + +static int app_cmd_at(const struct shell *shell, size_t argc, char **argv) +{ + if (argv[1] == NULL) { + shell_help(shell); + return -EINVAL; + } + if (strcmp(argv[1], "enable") == 0) { + shell_set_root_cmd("at"); + set_at_prompt(shell, true); + return 0; + } else if (strcmp(argv[1], "exit") == 0) { + set_at_prompt(shell, false); + return 0; + } + int err; + + if (strcmp(argv[1], "AT+CFUN=4") == 0) { + control_cloud_connection(false); /* disable reconnections */ + } else if (strcmp(argv[1], "AT+CFUN=1") == 0) { + control_cloud_connection(true); /* enable reconnections */ + } + + char *c = argv[1]; + int i = 0; + + while ((c = strstr(c, "\\n")) != NULL) { + c[0] = '\r'; + c[1] = '\n'; + i++; + } + + err = nrf_modem_at_cmd(response_buf, sizeof(response_buf), "%s", argv[1]); + if (err) { + shell_error(shell, "ERROR"); + return -EINVAL; + } + + shell_print(shell, "%s", response_buf); + + return 0; +} + +static int app_exit(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + set_at_prompt(shell, false); + return 0; +} + +static int cmd_reboot(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + device_shutdown(true); + return 0; +} + +static int cmd_shutdown(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + device_shutdown(false); + return 0; +} + +/* + * example: + * fota firmware.nrfcloud.com 3c7003c6-45a0-4a74-9023-1e006ceeb835/APP*30f6ce17*1.4.0/app_update.bin + */ +static int cmd_fota(const struct shell *shell, size_t argc, char **argv) +{ +#if defined(CONFIG_NRF_CLOUD_FOTA) + if (argc < 3) { + return -EINVAL; + } + char *host = argv[1]; + char *path = argv[2]; + int sec_tag = CONFIG_NRF_CLOUD_SEC_TAG; + size_t frag = CONFIG_NRF_CLOUD_FOTA_DOWNLOAD_FRAGMENT_SIZE; + + if (argc > 3) { + sec_tag = atoi(argv[3]); + } + if (argc > 4) { + frag = atoll(argv[4]); + } + shell_print(shell, "starting FOTA download from host:%s, path:%s, " + "sec_tag:%d, frag_size:%zd", + host, path, sec_tag, frag); + + char *space = strstr(path, " "); + + if (space) { + shell_print(shell, "mcuboot download detected"); + } + int err = fota_download_start(host, path, sec_tag, 0, frag); + + if (err) { + shell_error(shell, "Error %d starting download", err); + } + return 0; +#else + shell_error(shell, "FOTA support not enabled in this build."); + return 0; +#endif +} + +/* + * example: + * ble fota C2:6B:AC:6D:05:A3 firmware.beta.nrfcloud.com ba1752ef-0d36-4fcf-8748-3cad9f8801b0/ + * APP*f1078fc9*2.2.0/app_thingy_s132.bin 155272 1 2.2.0 + * ble fota C2:6B:AC:6D:05:A3 firmware.beta.nrfcloud.com ba1752ef-0d36-4fcf-8748-3cad9f8801b0/ + * APP*56693cf8*2.2.0/app_thingy_s132dat.bin 135 1 2.2.0 + * ble fota C2:6B:AC:6D:05:A3 firmware.beta.nrfcloud.com ba1752ef-0d36-4fcf-8748-3cad9f8801b0/ + * APP*f15b85a8*s132/sd_bl.bin 153344 0 s132 + * ble fota C2:6B:AC:6D:05:A3 firmware.beta.nrfcloud.com ba1752ef-0d36-4fcf-8748-3cad9f8801b0/ + * APP*3fb54637*s132/sd_bl_dat.bin 139 1 s132 + */ +#if CONFIG_GATEWAY_BLE_FOTA +static int cmd_ble_fota(const struct shell *shell, size_t argc, char **argv) +{ + if (argc < 3) { + return -EINVAL; + } + char *addr = argv[0]; + char *host = argv[1]; + char *path = argv[2]; + int size = 0; + bool init_packet = true; + char *ver = "1"; + uint32_t crc = 0; + int sec_tag = -1; + char *apn = NULL; + size_t frag = 0; + + if (argc > 3) { + size = atoi(argv[3]); + } + if (argc > 4) { + init_packet = atoi(argv[4]) != 0; + } + if (argc > 5) { + ver = argv[5]; + } + if (argc > 6) { + crc = atoi(argv[6]); + } + if (argc > 7) { + sec_tag = atoi(argv[7]); + } + if (argc > 8) { + frag = atoll(argv[8]); + } + if (argc > 9) { + apn = argv[9]; + } + + shell_print(shell, "starting BLE DFU to addr:%s, from host:%s, " + "path:%s, size:%d, final:%d, ver:%s, crc:%u, " + "sec_tag:%d, apn:%s, frag_size:%zd", + addr, host, path, size, init_packet, ver, crc, + sec_tag, apn ? apn : "", frag); + + /* set parameters for BLE update */ + int err; + + err = peripheral_dfu_config(addr, size, ver, crc, init_packet, true); + if (err) { + peripheral_dfu_cleanup(); + shell_error(shell, "Error %d starting peripheral DFU", err); + } else { + err = peripheral_dfu_start(host, path, sec_tag, apn, frag); + if (err) { + shell_error(shell, "Error %d starting download", err); + } + } + return 0; +} + +static void get_dynamic_ble_fota(size_t idx, struct shell_static_entry *entry) +{ + /* build dynamic list of mac addresses of only devices we have + * connected with (not scan results too) + */ + entry->syntax = get_mac_addr(idx, false); + + if (entry->syntax == NULL) { + return; + } + + entry->handler = cmd_ble_fota; + entry->subcmd = NULL; + entry->help = DYNAMIC_PARAM_HELP; + entry->args.mandatory = 5; + entry->args.optional = 5; +} + +SHELL_DYNAMIC_CMD_CREATE(dynamic_ble_fota, get_dynamic_ble_fota); +#endif + + +static int cmd_session(const struct shell *shell, size_t argc, char **argv) +{ + if (argc < 2) { + shell_print(shell, "Persistent sessions = %d", + nct_get_session_state()); + } else { + int flag = atoi(argv[1]); + + if ((nct_get_session_state() == 0) && (flag == 1)) { + shell_warn(shell, "Setting persistent sessions true " + "when it is not, may result " + "in data loss; use at your own " + "risk."); + } + shell_print(shell, "Setting persistent sessions = %d", flag); + nct_save_session_state(flag); + } + return 0; +} + +int check_passwd(char *passwd) +{ + return strcmp(passwd, DEFAULT_PASSWORD); +} + +int is_valid_passwd(char *passwd) +{ + if (strlen(passwd) < 8) { + return -EINVAL; + } else { + return 0; + } +} + +int set_passwd(char *passwd) +{ + return -ENOTSUP; +} + +static int cmd_login(const struct shell *shell, size_t argc, char **argv) +{ + static uint32_t attempts; + + if (argc < 2) { + shell_print(shell, "Access requires: "); + return -EINVAL; + } + + if (check_passwd(argv[1]) == 0) { + attempts = 0; + shell_obscure_set(shell, false); + z_shell_log_backend_enable(shell->log_backend, (void *)shell, + CONFIG_STARTING_LOG_LEVEL); + z_shell_history_purge(shell->history); + shell_set_root_cmd(NULL); + shell_prompt_change(shell, CONFIG_SHELL_PROMPT_SECURE); + + shell_print(shell, "\nnRF Cloud Gateway\n"); + print_fw_info(shell, false); + + shell_print(shell, "\nHit tab for help.\n"); + return 0; + } + shell_error(shell, "Incorrect password!"); + attempts++; + if (attempts > 3) { + k_sleep(K_SECONDS(attempts)); + } + return -EINVAL; +} + +static int cmd_passwd(const struct shell *shell, size_t argc, char **argv) +{ + int err; + + err = check_passwd(argv[1]); + if (err) { + shell_error(shell, "Incorrect password!"); + return err; + } + + err = is_valid_passwd(argv[2]); + if (err) { + shell_error(shell, "Invalid password. Must be 8 characters or longer."); + return err; + } + + err = set_passwd(argv[2]); + if (!err) { + shell_print(shell, "Password changed."); + } else { + shell_error(shell, "Unable to store password."); + } + + return err; +} + +static int cmd_logout(const struct shell *shell, size_t argc, char **argv) +{ + shell_set_root_cmd("login"); + shell_obscure_set(shell, true); + shell_prompt_change(shell, CONFIG_SHELL_PROMPT_UART); + shell_print(shell, "\n"); + return 0; +} + +void cli_init(void) +{ + const struct shell *shell = shell_backend_uart_get_ptr(); + + if (shell) { + /* + * Zephyr commit: SHA-1: 2b5723d4558619a9283683499c095e0b344d32d7 + * shell: add init backend configuration + * broke the config system regarding obscure, so for now, + * set it manually on + */ + shell_obscure_set(shell, IS_ENABLED(CONFIG_SHELL_START_OBSCURED)); + } + + if (IS_ENABLED(CONFIG_SHELL_START_OBSCURED)) { + shell_set_root_cmd("login"); + } else { + shell_prompt_change(shell, CONFIG_SHELL_PROMPT_SECURE); + } + +#if defined(CONFIG_STARTING_LOG_OVERRIDE) + for (int i = 0; i < log_src_cnt_get(CONFIG_LOG_DOMAIN_ID); i++) { + if (IS_ENABLED(CONFIG_NRF_CLOUD_GATEWAY_LOG_LEVEL_DBG)) { + printk("%d. %s\n", i, + log_source_name_get(CONFIG_LOG_DOMAIN_ID, i)); + } + log_filter_set(NULL, CONFIG_LOG_DOMAIN_ID, i, + CONFIG_STARTING_LOG_LEVEL); + } +#endif +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_info, + SHELL_CMD(cloud, NULL, "Cloud information.", cmd_info_cloud), + SHELL_CMD(ctlr, NULL, "BLE controller information.", cmd_info_ctlr), + SHELL_CMD(conn, NULL, "[path] [notify] Connected Bluetooth devices information.", + cmd_info_conn), + SHELL_CMD(gateway, NULL, " Gateway information.", cmd_info_gateway), + SHELL_CMD(heap, NULL, "Print heap usage statistics.", heap_shell), + SHELL_COND_CMD(CONFIG_GATEWAY_DBG_CMDS, irq, NULL, "Dump IRQ table.", cmd_info_irq), + SHELL_COND_CMD(CONFIG_GATEWAY_DBG_CMDS, list, &dynamic_addr, + "List known BLE MAC addresses.", NULL), + SHELL_CMD(modem, NULL, " Modem information.", cmd_info_modem), + SHELL_COND_CMD(CONFIG_GATEWAY_DBG_CMDS, param, &dynamic_param, "List parameters.", NULL), + SHELL_CMD(scan, NULL, "Bluetooth scan results.", cmd_info_scan), + SHELL_SUBCMD_SET_END /* Array terminated. */ +); +SHELL_CMD_REGISTER(info, &sub_info, "Informational commands", NULL); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_ble, + SHELL_CMD(scan, NULL, "Scan for BLE devices.", cmd_ble_scan), + SHELL_CMD(save, NULL, "Save desired connections to shadow.", cmd_ble_save), + SHELL_CMD(conn, &dynamic_ble_conn, " Connect to BLE device(s).", NULL), + SHELL_CMD(disc, &dynamic_ble_disc, " Disconnect BLE device(s).", NULL), + SHELL_CMD(en, &dynamic_ble_en, " Enable " + "notifications on BLE device(s).", NULL), + SHELL_CMD(dis, &dynamic_ble_dis, " Disable " + "notifications on BLE device(s).", NULL), +#if CONFIG_GATEWAY_BLE_FOTA + SHELL_CMD(fota, &dynamic_ble_fota, + " [ver] [crc] [sec_tag] [frag_size] [apn] " + "BLE firmware over-the-air update.", NULL), + SHELL_CMD(test, NULL, "Set BLE FOTA download test mode.", cmd_ble_test), +#endif + SHELL_SUBCMD_SET_END /* Array terminated. */ +); +SHELL_CMD_ARG_REGISTER(ble, &sub_ble, "Bluetooth commands", NULL, 0, 3); + +SHELL_CMD_ARG_REGISTER(fota, NULL, " [sec_tag] [frag_size] [apn] " + "firmware over-the-air update.", cmd_fota, 2, 2); +SHELL_CMD_ARG_REGISTER(at, NULL, " | exit> Execute an AT " + "command. Use first to remain " + "in AT command mode until 'exit'.", + app_cmd_at, 2, SHELL_OPT_ARG_RAW); +SHELL_CMD_ARG_REGISTER(session, NULL, "<0 | 1> Get or change persistent sessions flag.", + cmd_session, 0, 1); +SHELL_CMD_REGISTER(testflash, NULL, "Test the external flash.", cmd_testflash); +SHELL_CMD_REGISTER(reboot, NULL, "Reboot the gateway.", cmd_reboot); +SHELL_CMD_REGISTER(shutdown, NULL, "Shutdown the gateway.", cmd_shutdown); +SHELL_CMD_REGISTER(exit, NULL, "Exit 'select at' mode.", app_exit); +SHELL_CMD_ARG_REGISTER(login, NULL, "", cmd_login, 2, 0); +SHELL_CMD_ARG_REGISTER(passwd, NULL, " ", cmd_passwd, 3, 0); +SHELL_CMD_REGISTER(logout, NULL, "Log out.", cmd_logout); diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/dfu/peripheral_dfu.c b/samples/cellular/nrf_cloud_ble_gateway/src/ble/dfu/peripheral_dfu.c new file mode 100644 index 000000000000..fe3048cf87bc --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/dfu/peripheral_dfu.c @@ -0,0 +1,1362 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nrf_cloud_fota.h" +#include "ble.h" +#include "gateway.h" +#include "ble_conn_mgr.h" +#include "peripheral_dfu.h" + +LOG_MODULE_REGISTER(peripheral_dfu, CONFIG_NRF_CLOUD_FOTA_LOG_LEVEL); + +#define DFU_CONTROL_POINT_UUID "8EC90001F3154F609FB8838830DAEA50" +#define DFU_PACKET_UUID "8EC90002F3154F609FB8838830DAEA50" +#define DFU_BUTTONLESS_UUID "8EC90003F3154F609FB8838830DAEA50" +#define DFU_BUTTONLESS_BONDED_UUID "8EC90004F3154F609FB8838830DAEA50" +#define MAX_CHUNK_SIZE 20 +#define PROGRESS_UPDATE_INTERVAL 5 +#define MAX_FOTA_FILES 2 + +#define CALL_TO_PRINTK(fmt, ...) do { \ + printk(fmt "\n", ##__VA_ARGS__); \ + } while (false) + +#define LOGPKINF(...) do { \ + if (use_printk) { \ + CALL_TO_PRINTK(__VA_ARGS__); \ + } else { \ + LOG_INF(__VA_ARGS__); \ + } \ + } while (false) + +enum nrf_dfu_op_t { + NRF_DFU_OP_PROTOCOL_VERSION = 0x00, + NRF_DFU_OP_OBJECT_CREATE = 0x01, + NRF_DFU_OP_RECEIPT_NOTIF_SET = 0x02, + NRF_DFU_OP_CRC_GET = 0x03, + NRF_DFU_OP_OBJECT_EXECUTE = 0x04, + NRF_DFU_OP_OBJECT_SELECT = 0x06, + NRF_DFU_OP_MTU_GET = 0x07, + NRF_DFU_OP_OBJECT_WRITE = 0x08, + NRF_DFU_OP_PING = 0x09, + NRF_DFU_OP_HARDWARE_VERSION = 0x0A, + NRF_DFU_OP_FIRMWARE_VERSION = 0x0B, + NRF_DFU_OP_ABORT = 0x0C, + NRF_DFU_OP_RESPONSE = 0x60, + NRF_DFU_OP_INVALID = 0xFF +}; + +enum nrf_dfu_result_t { + NRF_DFU_RES_CODE_INVALID = 0x00, + NRF_DFU_RES_CODE_SUCCESS = 0x01, + NRF_DFU_RES_CODE_OP_CODE_NOT_SUPPORTED = 0x02, + NRF_DFU_RES_CODE_INVALID_PARAMETER = 0x03, + NRF_DFU_RES_CODE_INSUFFICIENT_RESOURCES = 0x04, + NRF_DFU_RES_CODE_INVALID_OBJECT = 0x05, + NRF_DFU_RES_CODE_UNSUPPORTED_TYPE = 0x07, + NRF_DFU_RES_CODE_OPERATION_NOT_PERMITTED = 0x08, + NRF_DFU_RES_CODE_OPERATION_FAILED = 0x0A, + NRF_DFU_RES_CODE_EXT_ERROR = 0x0B +}; + +typedef enum { + NRF_DFU_EXT_ERROR_NO_ERROR = 0x00, + NRF_DFU_EXT_ERROR_INVALID_ERROR_CODE = 0x01, + NRF_DFU_EXT_ERROR_WRONG_COMMAND_FORMAT = 0x02, + NRF_DFU_EXT_ERROR_UNKNOWN_COMMAND = 0x03, + NRF_DFU_EXT_ERROR_INIT_COMMAND_INVALID = 0x04, + NRF_DFU_EXT_ERROR_FW_VERSION_FAILURE = 0x05, + NRF_DFU_EXT_ERROR_HW_VERSION_FAILURE = 0x06, + NRF_DFU_EXT_ERROR_SD_VERSION_FAILURE = 0x07, + NRF_DFU_EXT_ERROR_SIGNATURE_MISSING = 0x08, + NRF_DFU_EXT_ERROR_WRONG_HASH_TYPE = 0x09, + NRF_DFU_EXT_ERROR_HASH_FAILED = 0x0A, + NRF_DFU_EXT_ERROR_WRONG_SIGNATURE_TYPE = 0x0B, + NRF_DFU_EXT_ERROR_VERIFICATION_FAILED = 0x0C, + NRF_DFU_EXT_ERROR_INSUFFICIENT_SPACE = 0x0D, +} nrf_dfu_ext_error_code_t; + +enum nrf_dfu_firmware_type_t { + NRF_DFU_FIRMWARE_TYPE_SOFTDEVICE = 0x00, + NRF_DFU_FIRMWARE_TYPE_APPLICATION = 0x01, + NRF_DFU_FIRMWARE_TYPE_BOOTLOADER = 0x02, + NRF_DFU_FIRMWARE_TYPE_UNKNOWN = 0xFF +}; + +struct dfu_notify_packet { + uint8_t magic; + uint8_t op; + uint8_t result; + union { + struct select { + uint32_t max_size; + uint32_t offset; + uint32_t crc; + } select; + struct crc { + uint32_t offset; + uint32_t crc; + } crc; + struct hw_version { + uint32_t part; + uint32_t variant; + uint32_t rom_size; + uint32_t ram_size; + uint32_t rom_page_size; + } hw; + struct fw_version { + uint8_t type; + uint32_t version; + uint32_t addr; + uint32_t len; + } fw; + uint8_t ext_err_code; + }; +} __packed; + +enum ble_dfu_buttonless_op_code_t { + DFU_OP_RESERVED = 0x00, + DFU_OP_ENTER_BOOTLOADER = 0x01, + DFU_OP_SET_ADV_NAME = 0x02, + DFU_OP_RESPONSE_CODE = 0x20 +}; + +enum ble_dfu_buttonless_rsp_code_t { + DFU_RSP_INVALID = 0x00, + DFU_RSP_SUCCESS = 0x01, + DFU_RSP_OP_CODE_NOT_SUPPORTED = 0x02, + DFU_RSP_OPERATION_FAILED = 0x04, + DFU_RSP_ADV_NAME_INVALID = 0x05, + DFU_RSP_BUSY = 0x06, + DFU_RSP_NOT_BONDED = 0x07 +}; + +struct secure_dfu_ind_packet { + uint8_t resp_code; + uint8_t op_code; + uint8_t rsp_code; +} __packed; + +K_SEM_DEFINE(peripheral_dfu_active, 1, 1); +static size_t download_size; +static size_t completed_size; +static bool finish_page; +static bool test_mode; +static bool verbose; +static uint32_t max_size; +static uint32_t flash_page_size; +static uint32_t page_remaining; + +/* responses from peripheral device */ +static bool notify_received; +static bool normal_mode; +static uint16_t notify_length; +static uint8_t dfu_notify_data[MAX_CHUNK_SIZE]; + +/* normal mode BLE device address */ +static char ble_norm_addr[BT_ADDR_STR_LEN]; +/* normal mode secure DFU responses */ +struct secure_dfu_ind_packet *secure_dfu_ind_packet_data = + (struct secure_dfu_ind_packet *) dfu_notify_data; + +/* DFU mode BLE device address */ +static char ble_dfu_addr[BT_ADDR_STR_LEN]; +struct ble_device_conn *dfu_conn_ptr; +/* DFU mode protocol responses */ +struct dfu_notify_packet *dfu_notify_packet_data = + (struct dfu_notify_packet *)dfu_notify_data; + +static struct nrf_cloud_fota_ble_job fota_ble_job; +static struct nrf_cloud_fota_job_info fota_files[MAX_FOTA_FILES]; +static int total_completed_size; +static int active_num; +static int num_fota_files; +static struct k_work_delayable fota_job_work; + +static char app_version[16]; +static int image_size; +static uint16_t original_crc; +static int socket_retries_left; +static bool first_fragment; +static bool init_packet; +static bool use_printk; +static struct download_client dlc; + +static int send_select_command(char *ble_addr); +static int send_create_command(char *ble_addr, uint32_t size); +static int send_switch_to_dfu(char *ble_addr); +static int send_select_data(char *ble_addr); +static int send_create_data(char *ble_addr, uint32_t size); +static int send_prn(char *ble_addr, uint16_t receipt_rate); +static int send_data(char *ble_addr, const char *buf, size_t len); +static int send_request_crc(char *ble_addr); +static int send_execute(char *ble_addr); +static int send_abort(char *ble_addr); +static void on_sent(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params); +static int send_hw_version_get(char *ble_addr); +static int send_fw_version_get(char *ble_addr, uint8_t fw_type); +static uint8_t peripheral_dfu(const char *buf, size_t len); +static int download_client_callback(const struct download_client_evt *event); +static void cancel_dfu(enum nrf_cloud_fota_error error); +static void fota_ble_callback(const struct nrf_cloud_fota_ble_job * + const ble_job); +static void free_job(void); +static bool start_next_job(void); +static void fota_job_next(struct k_work *work); + +void peripheral_dfu_set_test_mode(bool test) +{ + test_mode = test; +} + +static int notification_callback(const char *addr, const char *chrc_uuid, + uint8_t *data, uint16_t len) +{ + if ((strcmp(addr, ble_norm_addr) != 0) && + (strcmp(addr, ble_dfu_addr) != 0)) { + LOG_WRN("Notification received for %s (not us)", addr); + return 0; /* not for us */ + } + /** @TODO: implement bonded mode also */ + if (strcmp(chrc_uuid, DFU_BUTTONLESS_UUID) == 0) { + normal_mode = true; + } else if (strcmp(chrc_uuid, DFU_CONTROL_POINT_UUID) == 0) { + normal_mode = false; + } else { + LOG_WRN("Notification received for wrong UUID:%s", chrc_uuid); + return 0; /* not for us */ + } + notify_length = MIN(len, sizeof(dfu_notify_data)); + memcpy(dfu_notify_data, data, notify_length); + notify_received = true; + LOG_DBG("%sdfu notified %u bytes", normal_mode ? "secure " : "", notify_length); + LOG_HEXDUMP_DBG(data, notify_length, "notify packet"); + return 1; +} + +static int wait_for_notification(void) +{ + int max_loops = 1000; + + while (!notify_received) { + k_sleep(K_MSEC(10)); + if (!max_loops--) { + return -ETIMEDOUT; + } + } + return 0; +} + +static int decode_secure_dfu(void) +{ + struct secure_dfu_ind_packet *p = secure_dfu_ind_packet_data; + int err; + + if (notify_length < 3) { + LOG_ERR("Indication too short: %u < 3", notify_length); + return -EINVAL; + } + if (p->resp_code != DFU_OP_RESPONSE_CODE) { + LOG_ERR("First byte not 0x%02X: 0x%02X", DFU_OP_RESPONSE_CODE, p->resp_code); + return -EINVAL; + } + switch (p->op_code) { + case DFU_OP_ENTER_BOOTLOADER: + if (p->rsp_code == DFU_RSP_SUCCESS) { + LOG_INF("Device switching to DFU mode!"); + err = 0; + } else { + LOG_ERR("Error %d switching to DFU mode", p->rsp_code); + err = -EIO; + } + break; + case DFU_OP_SET_ADV_NAME: + if (p->rsp_code == DFU_RSP_SUCCESS) { + LOG_INF("DFU adv name changed successfully"); + err = 0; + } else { + LOG_ERR("Error %d changing DFU adv name", p->rsp_code); + err = -EIO; + } + break; + default: + LOG_ERR("Unexpected op indicated: 0x%02X", p->op_code); + err = -EINVAL; + } + return err; +} + +static int decode_dfu(void) +{ + struct dfu_notify_packet *p = dfu_notify_packet_data; + char buf[256]; + + if (notify_length < 3) { + LOG_ERR("Notification too short: %u < 3", notify_length); + return -EINVAL; + } + if (p->magic != 0x60) { + LOG_ERR("First byte not 0x60: 0x%02X", p->magic); + return -EINVAL; + } + switch (p->op) { + case NRF_DFU_OP_OBJECT_CREATE: + snprintf(buf, sizeof(buf), "DFU Create response: 0x%02X", + p->result); + break; + case NRF_DFU_OP_RECEIPT_NOTIF_SET: + snprintf(buf, sizeof(buf), + "DFU Set Receipt Notification response: 0x%02X", + p->result); + break; + case NRF_DFU_OP_CRC_GET: + if (notify_length < 7) { + LOG_ERR("Notification too short: %u < 7", + notify_length); + return -EINVAL; + } + snprintf(buf, sizeof(buf), "DFU CRC response: 0x%02X, " + "offset: 0x%08X, crc: 0x%08X", + p->result, p->crc.offset, p->crc.crc); + break; + case NRF_DFU_OP_OBJECT_EXECUTE: + snprintf(buf, sizeof(buf), "DFU Execute response: 0x%02X", + p->result); + break; + case NRF_DFU_OP_OBJECT_SELECT: + snprintf(buf, sizeof(buf), + "DFU Select response: 0x%02X, offset: 0x%08X," + " crc: 0x%08X, max_size: %u", p->result, + p->select.offset, p->select.crc, p->select.max_size); + max_size = p->select.max_size; + break; + case NRF_DFU_OP_HARDWARE_VERSION: + snprintf(buf, sizeof(buf), + "DFU HW Version Get response: 0x%02X, part: 0x%08X," + " variant: 0x%08X, rom_size: 0x%08X, ram_size: 0x%08X," + " rom_page_size: 0x%08X", p->result, p->hw.part, + p->hw.variant, p->hw.rom_size, p->hw.ram_size, + p->hw.rom_page_size); + flash_page_size = p->hw.rom_page_size; + break; + case NRF_DFU_OP_FIRMWARE_VERSION: + snprintf(buf, sizeof(buf), + "DFU FW Version Get response: 0x%02X, type: 0x%02X," + " version: 0x%02X, addr: 0x%02X, len: 0x%02X", + p->result, p->fw.type, p->fw.version, p->fw.addr, + p->fw.len); + break; + default: + LOG_ERR("Unknown DFU notification: 0x%02X", p->op); + return -EINVAL; + } + if (p->result == NRF_DFU_RES_CODE_SUCCESS) { + if (verbose) { + LOG_INF("%s", buf); + } else { + LOG_DBG("%s", buf); + } + return 0; + } else if (p->result == NRF_DFU_RES_CODE_EXT_ERROR) { + if (p->ext_err_code == NRF_DFU_EXT_ERROR_FW_VERSION_FAILURE) { + LOG_ERR("Cannot downgrade target firmware version!"); + } else if (p->ext_err_code == NRF_DFU_EXT_ERROR_FW_VERSION_FAILURE) { + LOG_ERR("Incompatible target hardware version!"); + } else if (p->ext_err_code == NRF_DFU_EXT_ERROR_FW_VERSION_FAILURE) { + LOG_ERR("Incompatible target SoftDevice version!"); + } else { + LOG_ERR("Extended DFU error: 0x%02X", p->ext_err_code); + } + return -EFAULT; + } + LOG_WRN("DFU operation failed: %d", p->result); + return -EPROTO; +} + +int peripheral_dfu_init(void) +{ + int err; + + k_work_init_delayable(&fota_job_work, fota_job_next); + + err = download_client_init(&dlc, download_client_callback); + if (err) { + return err; + } + return nrf_cloud_fota_ble_set_handler(fota_ble_callback); +} + +int peripheral_dfu_cleanup(void) +{ + ble_subscribe(ble_norm_addr, DFU_BUTTONLESS_UUID, 0); + ble_conn_mgr_remove_conn(ble_dfu_addr); + ble_conn_mgr_rem_desired(ble_dfu_addr, true); + k_sem_give(&peripheral_dfu_active); + return 0; +} + +int peripheral_dfu_config(const char *addr, int size, const char *version, + uint32_t crc, bool init_pkt, bool use_prtk) +{ + struct ble_device_conn *conn; + uint16_t handle; + int err; + + err = ble_conn_mgr_get_conn_by_addr(addr, &conn); + if (err) { + LOG_ERR("Connection not found for addr %s", addr); + return err; + } + + err = k_sem_take(&peripheral_dfu_active, K_NO_WAIT); + if (err) { + LOG_ERR("Peripheral DFU already active."); + conn->dfu_attempts = MAX_DFU_ATTEMPTS; + conn->dfu_pending = true; + return -EAGAIN; + } + + dfu_conn_ptr = NULL; + + strncpy(ble_norm_addr, addr, sizeof(ble_norm_addr)); + + ble_register_notify_callback(notification_callback); + + err = ble_conn_mgr_get_handle_by_uuid(&handle, DFU_BUTTONLESS_UUID, conn); + if (err) { + err = ble_conn_mgr_get_handle_by_uuid(&handle, DFU_CONTROL_POINT_UUID, conn); + if (err) { + LOG_ERR("Device does not support buttonless DFU, and is not in DFU mode"); + k_sem_give(&peripheral_dfu_active); + return -EINVAL; + } + LOG_INF("Device already in DFU mode"); + strncpy(ble_dfu_addr, addr, sizeof(ble_dfu_addr)); + goto ready; + } + + /* need to switch device to DFU mode first */ + ble_conn_mgr_calc_other_addr(addr, ble_dfu_addr, DEVICE_ADDR_LEN, false); + + err = ble_conn_mgr_get_conn_by_addr(ble_dfu_addr, &conn); + if (err) { + LOG_INF("Adding temporary connection to %s for DFU", + ble_dfu_addr); + err = ble_conn_mgr_add_conn(ble_dfu_addr); + if (err) { + goto failed; + } + LOG_INF("Connection added to ble_conn_mgr"); + } else { + LOG_INF("Connection already exists in ble_conn_mgr"); + } + err = ble_conn_mgr_add_desired(ble_dfu_addr, true); + if (err) { + goto failed; + } + + LOG_INF("Enabling indication"); + err = ble_subscribe(ble_norm_addr, DFU_BUTTONLESS_UUID, + BT_GATT_CCC_INDICATE); + if (err) { + goto failed; + } + + LOG_INF("Switching to DFU mode"); + err = send_switch_to_dfu(ble_norm_addr); + if (err) { + goto failed; + } + int max_loops = 300; + + err = ble_conn_mgr_get_conn_by_addr(ble_dfu_addr, &conn); + if (err) { + goto failed; + } + + /* special dfu flag -- don't send info to cloud about this dfu device */ + conn->hidden = true; + + LOG_INF("Waiting for device discovery..."); + while (!conn->discovered && !conn->encode_discovered) { + k_sleep(K_MSEC(100)); + if (!max_loops--) { + LOG_ERR("Timeout: conn:%u, ding:%u, disc:%u, enc:%u, np:%u", + conn->connected, conn->discovering, + conn->discovered, conn->encode_discovered, + conn->num_pairs); + err = -ETIMEDOUT; + goto failed; + } + } + LOG_INF("Continuing BLE DFU"); + +ready: + strncpy(app_version, version, sizeof(app_version)); + image_size = size; + original_crc = crc; + init_packet = init_pkt; + use_printk = use_prtk; + dfu_conn_ptr = conn; + return 0; + +failed: + LOG_ERR("Error configuring update:%d", err); + return err; +} + +int peripheral_dfu_start(const char *host, const char *file, int sec_tag, + const char *apn, size_t fragment_size) +{ + int err = -1; + const int sec_tag_list[1] = { + sec_tag + }; + struct download_client_cfg config = { + .sec_tag_list = sec_tag_list, + .sec_tag_count = ARRAY_SIZE(sec_tag_list), + .pdn_id = 0, + .frag_size_override = fragment_size, + .set_tls_hostname = (sec_tag != -1), + }; + + if (host == NULL || file == NULL) { + peripheral_dfu_cleanup(); + return -EINVAL; + } + + socket_retries_left = CONFIG_FOTA_SOCKET_RETRIES; + + first_fragment = true; + + err = download_client_set_host(&dlc, host, &config); + if (err != 0) { + peripheral_dfu_cleanup(); + return err; + } + + err = download_client_start(&dlc, file, 0); + if (err != 0) { + download_client_disconnect(&dlc); + peripheral_dfu_cleanup(); + return err; + } + + return 0; +} + +static int download_client_callback(const struct download_client_evt *event) +{ + int err = 0; + + if (event == NULL) { + return -EINVAL; + } + + switch (event->id) { + case DOWNLOAD_CLIENT_EVT_FRAGMENT: + if (first_fragment) { + err = download_client_file_size_get(&dlc, &image_size); + if (err) { + LOG_ERR("Error determining file size: %d", err); + } else { + LOG_INF("Downloading %zd bytes", image_size); + } + } + err = peripheral_dfu(event->fragment.buf, + event->fragment.len); + if (err) { + LOG_ERR("Error from peripheral_dfu: %d", err); + } + break; + case DOWNLOAD_CLIENT_EVT_DONE: + LOG_INF("Download client done"); + err = download_client_disconnect(&dlc); + if (err) { + LOG_ERR("Error disconnecting from download client: %d", + err); + } + k_work_reschedule(&fota_job_work, K_SECONDS(1)); + break; + case DOWNLOAD_CLIENT_EVT_ERROR: { + /* In case of socket errors we can return 0 to retry/continue, + * or non-zero to stop + */ + if ((socket_retries_left) && ((event->error == -ENOTCONN) || + (event->error == -ECONNRESET))) { + LOG_WRN("Download socket error. %d retries left...", + socket_retries_left); + socket_retries_left--; + /* Fall through and return 0 below to tell + * download_client to retry + */ + } else { + err = download_client_disconnect(&dlc); + if (err) { + LOG_ERR("Error disconnecting from " + "download client: %d", err); + } + LOG_ERR("Download client error"); + cancel_dfu(NRF_CLOUD_FOTA_ERROR_DOWNLOAD); + err = -EIO; + } + break; + } + default: + break; + } + + return err; +} + +static int start_ble_job(struct nrf_cloud_fota_ble_job *const ble_job) +{ + __ASSERT_NO_MSG(ble_job != NULL); + int ret; + enum nrf_cloud_fota_status status; + + ret = peripheral_dfu_start(ble_job->info.host, ble_job->info.path, + CONFIG_NRF_CLOUD_SEC_TAG, NULL, + CONFIG_NRF_CLOUD_FOTA_DOWNLOAD_FRAGMENT_SIZE); + if (ret) { + LOG_ERR("Failed to start FOTA download: %d", ret); + status = NRF_CLOUD_FOTA_FAILED; + } else { + LOG_INF("Downloading update"); + status = NRF_CLOUD_FOTA_IN_PROGRESS; + if (total_completed_size) { + /* not the first file, so no progress needed here */ + return ret; + } + } + (void)nrf_cloud_fota_ble_job_update(ble_job, status); + + return ret; +} + +static void fota_ble_callback(const struct nrf_cloud_fota_ble_job * + const ble_job) +{ + char addr[BT_ADDR_LE_STR_LEN]; + bool init_pkt; + char *ver = "n/a"; + uint32_t crc = 0; + int sec_tag = CONFIG_NRF_CLOUD_SEC_TAG; + char *apn = NULL; + size_t frag = CONFIG_NRF_CLOUD_FOTA_DOWNLOAD_FRAGMENT_SIZE; + int err; + + bt_addr_to_str(&ble_job->ble_id, addr, sizeof(addr)); + + if (!ble_conn_mgr_is_addr_connected(addr)) { + /* TODO: add ability to queue? */ + LOG_WRN("Device not connected; ignoring job"); + return; + } + + init_pkt = strstr(ble_job->info.path, "dat") != NULL; + + err = peripheral_dfu_config(addr, ble_job->info.file_size, ver, crc, + init_pkt, false); + if (err == -EAGAIN) { + /* already busy; ask for the job when done with current */ + return; + } else if (err) { + /* could not configure, so don't start job */ + cancel_dfu(NRF_CLOUD_FOTA_ERROR_APPLY_FAIL); + return; + } + + memset(fota_files, 0, sizeof(fota_files)); + num_fota_files = 0; + + int i; + char *end; + char *path = ble_job->info.path; + bool done = false; + + /* separate out various file paths to download */ + for (i = 0; i < MAX_FOTA_FILES; i++) { + end = strchr(path, ' '); + if (!end) { + end = &path[strlen(path)]; + done = true; + } + fota_files[i].path = k_calloc(1 + end - path, 1); + if (!fota_files[i].path) { + LOG_ERR("Out of memory"); + return; + } + memcpy(fota_files[i].path, path, end - path); + path = end + 1; + num_fota_files++; + if (done) { + break; + } + } + + /* fix up order of files based on type */ + for (i = 0; i < MAX_FOTA_FILES; i++) { + if (strstr(fota_files[i].path, ".dat")) { + if (i) { /* .dat is not first; make it so */ + char *tmp = fota_files[0].path; + + fota_files[0].path = fota_files[i].path; + fota_files[i].path = tmp; + } + break; + } + } + + active_num = 0; + total_completed_size = 0; + fota_ble_job.info.path = fota_files[active_num].path; + + /* job structure will disappear after this callback, so make a copy */ + fota_ble_job.ble_id = ble_job->ble_id; + fota_ble_job.info.type = ble_job->info.type; + fota_ble_job.info.id = strdup(ble_job->info.id); + fota_ble_job.info.host = strdup(ble_job->info.host); + /* total size of all files */ + fota_ble_job.info.file_size = ble_job->info.file_size; + fota_ble_job.error = NRF_CLOUD_FOTA_ERROR_NONE; + + LOG_INF("Starting BLE DFU to addr:%s, from host:%s, size:%d, " + "init:%d, ver:%s, crc:%u, sec_tag:%d, apn:%s, frag_size:%zd", + addr, fota_ble_job.info.host, + fota_ble_job.info.file_size, + init_packet, ver, crc, sec_tag, + apn ? apn : "", frag); + LOG_INF("Num files:%d", num_fota_files); + for (i = 0; i < MAX_FOTA_FILES; i++) { + if (!fota_files[i].path) { + break; + } + LOG_INF("File:%d path:%s", i + 1, fota_files[i].path); + } + + (void)start_ble_job(&fota_ble_job); +} + +static void fota_job_next(struct k_work *work) +{ + if (!start_next_job()) { + ble_conn_mgr_force_dfu_rediscover(ble_norm_addr); + if (strcmp(ble_norm_addr, ble_dfu_addr) != 0) { + ble_conn_mgr_force_dfu_rediscover(ble_dfu_addr); + } + ble_register_notify_callback(NULL); + LOGPKINF("DFU complete"); + free_job(); + peripheral_dfu_cleanup(); + ble_conn_mgr_check_pending(); + } +} + +static bool start_next_job(void) +{ + /* select next job */ + active_num++; + if (active_num >= num_fota_files) { + LOG_INF("No more files. Done."); + return false; + } + fota_ble_job.info.path = fota_files[active_num].path; + fota_ble_job.error = NRF_CLOUD_FOTA_ERROR_NONE; + + if (!ble_conn_mgr_is_addr_connected(ble_dfu_addr)) { + /* TODO: add ability to queue? */ + LOG_ERR("Device not connected; cancelling remainder of job"); + cancel_dfu(NRF_CLOUD_FOTA_ERROR_APPLY_FAIL); + return true; + } + + /* update globals */ + init_packet = strstr(fota_ble_job.info.path, ".dat") != NULL; + + LOG_INF("Starting second BLE DFU to addr:%s, from host:%s, " + "path:%s, size:%d, init:%d", + ble_dfu_addr, fota_ble_job.info.host, + fota_ble_job.info.path, fota_ble_job.info.file_size, + init_packet); + + int err = start_ble_job(&fota_ble_job); + + return (err == 0); +} + +static void free_job(void) +{ + if (fota_ble_job.info.id) { + free(fota_ble_job.info.id); + fota_ble_job.info.id = NULL; + if (fota_ble_job.info.host) { + free(fota_ble_job.info.host); + fota_ble_job.info.host = NULL; + } + memset(&fota_ble_job, 0, sizeof(fota_ble_job)); + } + + for (int i = 0; i < MAX_FOTA_FILES; i++) { + if (!fota_files[i].path) { + break; + } + k_free(fota_files[i].path); + fota_files[i].path = NULL; + fota_files[i].file_size = 0; + } +} + +static void cancel_dfu(enum nrf_cloud_fota_error error) +{ + ble_register_notify_callback(NULL); + if (fota_ble_job.info.id) { + enum nrf_cloud_fota_status status; + int err; + + if (ble_conn_mgr_is_addr_connected(ble_dfu_addr)) { + LOG_INF("Sending DFU Abort..."); + send_abort(ble_dfu_addr); + } + + /* TODO: adjust error according to actual failure */ + fota_ble_job.error = error; + status = NRF_CLOUD_FOTA_FAILED; + + err = nrf_cloud_fota_ble_job_update(&fota_ble_job, status); + if (err) { + LOG_ERR("Error updating job: %d", err); + } + free_job(); + LOGPKINF("Update cancelled."); + } + peripheral_dfu_cleanup(); + ble_conn_mgr_check_pending(); +} + +static uint8_t peripheral_dfu(const char *buf, size_t len) +{ + static size_t prev_percent; + static size_t prev_update_percent; + static uint32_t prev_crc; + size_t percent = 0; + int err = 0; + + if (first_fragment) { + LOG_INF("Len:%zd, first:%u, size:%d, init:%u", len, + first_fragment, image_size, init_packet); + } + download_size = image_size; + + if (test_mode) { + size_t percent = 0; + + if (first_fragment) { + completed_size = 0; + first_fragment = false; + } + completed_size += len; + + if (download_size) { + percent = (100 * completed_size) / download_size; + } + + LOG_INF("Progress: %zd%%; %zd of %zd", + percent, completed_size, download_size); + + if (percent >= 100) { + LOG_INF("DFU complete"); + } + return 0; + } + + if (first_fragment) { + first_fragment = false; + LOGPKINF("BLE DFU starting to %s...", ble_dfu_addr); + + if (fota_ble_job.info.id) { + fota_ble_job.dl_progress = (100 * total_completed_size) / + fota_ble_job.info.file_size; + err = nrf_cloud_fota_ble_job_update(&fota_ble_job, + NRF_CLOUD_FOTA_DOWNLOADING); + if (err) { + LOG_ERR("Error updating job: %d", err); + free_job(); + peripheral_dfu_cleanup(); + return err; + } + } + prev_percent = 0; + prev_update_percent = 0; + completed_size = 0; + prev_crc = 0; + page_remaining = 0; + finish_page = false; + + if (init_packet) { + verbose = true; + flash_page_size = 0; + + LOG_INF("Loading Init Packet and " + "turning on notifications..."); + err = ble_subscribe(ble_dfu_addr, DFU_CONTROL_POINT_UUID, + BT_GATT_CCC_NOTIFY); + if (err) { + goto cleanup; + } + + LOG_INF("Setting DFU PRN to 0..."); + err = send_prn(ble_dfu_addr, 0); + if (err) { + goto cleanup; + } + + LOG_INF("Querying hardware version..."); + (void)send_hw_version_get(ble_dfu_addr); + + LOG_INF("Querying firmware version (APP)..."); + (void)send_fw_version_get(ble_dfu_addr, + NRF_DFU_FIRMWARE_TYPE_APPLICATION); + + LOG_INF("Sending DFU select command..."); + err = send_select_command(ble_dfu_addr); + if (err) { + goto cleanup; + } + /* TODO: check offset and CRC; do no transfer + * and skip execute if offset != init length or + * CRC mismatch -- however, to do this, we need the + * cloud to send us the expected CRC for the file + */ + if (dfu_notify_packet_data->select.offset != image_size) { + LOG_INF("Image size mismatched, so continue; " + "offset:%u, size:%u", + dfu_notify_packet_data->select.offset, + image_size); + } else { + LOG_INF("Image size matches device!"); + if (dfu_notify_packet_data->select.crc != original_crc) { + LOG_INF("CRC mismatched, so continue; " + "dev crc:0x%08X, this crc:" + "0x%08X", + dfu_notify_packet_data->select.crc, + original_crc); + } else { + LOG_INF("CRC matches device!"); + } + } + + LOG_INF("Sending DFU create command..."); + err = send_create_command(ble_dfu_addr, image_size); + /* This will need to be the size of the entire init + * file. If http chunks it smaller this won't work + */ + if (err) { + goto cleanup; + } + } else { + verbose = false; + finish_page = false; + + LOG_INF("Loading Firmware"); + LOG_INF("Setting DFU PRN to 0..."); + err = send_prn(ble_dfu_addr, 0); + if (err) { + goto cleanup; + } + + LOG_INF("Sending DFU select data..."); + err = send_select_data(ble_dfu_addr); + if (err) { + goto cleanup; + } + if (dfu_notify_packet_data->select.offset != image_size) { + LOG_INF("Image size mismatched, so continue; " + "offset:%u, size:%u", + dfu_notify_packet_data->select.offset, + image_size); + } else { + LOG_INF("Image size matches device!"); + if (dfu_notify_packet_data->select.crc != original_crc) { + LOG_INF("CRC mismatched, so continue; " + "dev crc:0x%08X, this crc:" + "0x%08X", + dfu_notify_packet_data->select.crc, + original_crc); + } else { + LOG_INF("CRC matches device!"); + /* TODO: we could skip the entire file, + * and just send an indication to the + * cloud that the job is complete + */ + } + } + } + } + + /* output data while handing page boundaries */ + while (len) { + if (!finish_page) { + uint32_t file_remaining = download_size - completed_size; + + page_remaining = MIN(max_size, file_remaining); + + LOG_DBG("Page remaining %u, max_size %u, len %u", + page_remaining, max_size, len); + if (!init_packet) { + err = send_create_data(ble_dfu_addr, page_remaining); + if (err) { + goto cleanup; + } + } + if (len < page_remaining) { + LOG_DBG("Need to transfer %u in page", + page_remaining); + finish_page = true; + } else { + if (!completed_size) { + LOG_DBG("No need to finish first page " + "next time"); + } + } + } + + size_t page_len = MIN(len, page_remaining); + + LOG_DBG("Sending DFU data len %u...", page_len); + err = send_data(ble_dfu_addr, buf, page_len); + if (err) { + goto cleanup; + } + + prev_crc = crc32_ieee_update(prev_crc, buf, page_len); + + completed_size += page_len; + total_completed_size += page_len; + len -= page_len; + buf += page_len; + LOG_DBG("Completed size: %u", completed_size); + + page_remaining -= page_len; + if ((page_remaining == 0) || + (completed_size == download_size)) { + finish_page = false; + LOG_DBG("Page is complete"); + } else { + LOG_DBG("Page remaining: %u bytes", page_remaining); + } + + if (!finish_page) { + /* give hardware time to finish; omitting this step + * results in an error on the execute request and/or + * CRC mismatch; sometimes we are too fast so we + * need to retry, because the transfer was still + * going on + */ + for (int i = 1; i <= 5; i++) { + k_sleep(K_MSEC(100)); + + LOG_DBG("Sending DFU request CRC %d...", i); + err = send_request_crc(ble_dfu_addr); + if (err) { + break; + } + if (dfu_notify_packet_data->crc.offset != + completed_size) { + err = -EIO; + } else if (dfu_notify_packet_data->crc.crc != + prev_crc) { + err = -EBADMSG; + } else { + LOG_INF("CRC and length match."); + err = 0; + break; + } + } + if (err == -EIO) { + LOG_ERR("Transfer offset wrong; received: %u," + " expected: %u", + dfu_notify_packet_data->crc.offset, + completed_size); + } + if (err == -EBADMSG) { + LOG_ERR("CRC wrong; received: 0x%08X, " + "expected: 0x%08X", + dfu_notify_packet_data->crc.crc, + prev_crc); + } + if (err) { + goto cleanup; + } + + LOG_DBG("Sending DFU execute..."); + err = send_execute(ble_dfu_addr); + if (err) { + goto cleanup; + } + } + } + + if (download_size) { + percent = (100 * total_completed_size) / fota_ble_job.info.file_size; + } + + if (percent != prev_percent) { + LOGPKINF("Progress: %zd%%, %zd bytes of %zd", percent, + completed_size, download_size); + prev_percent = percent; + + if (fota_ble_job.info.id && + (((percent - prev_update_percent) >= + PROGRESS_UPDATE_INTERVAL) || + (percent >= 100))) { + enum nrf_cloud_fota_status status; + + LOG_INF("Sending job update at %zd%% percent", + percent); + fota_ble_job.error = NRF_CLOUD_FOTA_ERROR_NONE; + if (percent < 100) { + status = NRF_CLOUD_FOTA_DOWNLOADING; + fota_ble_job.dl_progress = percent; + } else { + status = NRF_CLOUD_FOTA_SUCCEEDED; + fota_ble_job.dl_progress = 100; + if (dfu_conn_ptr) { + dfu_conn_ptr->dfu_pending = false; + } + } + err = nrf_cloud_fota_ble_job_update(&fota_ble_job, + status); + if (err) { + LOG_ERR("Error updating job: %d", err); + goto cleanup; + } + prev_update_percent = percent; + } + } + + return err; + +cleanup: + cancel_dfu(NRF_CLOUD_FOTA_ERROR_APPLY_FAIL); + return err; +} + +static int send_data(char *ble_addr, const char *buf, size_t len) +{ + /* "chunk" The data */ + int idx = 0; + int err = 0; + + while (idx < len) { + uint8_t size = MIN(MAX_CHUNK_SIZE, (len - idx)); + + LOG_DBG("Sending write without response: %d, %d", size, idx); + err = gatt_write_without_response(ble_addr, DFU_PACKET_UUID, + (uint8_t *)&buf[idx], size); + if (err) { + LOG_ERR("Error writing chunk at %d size %u: %d", + idx, size, err); + break; + } + idx += size; + } + return err; +} + +/* name should be a string constant so log_strdup not needed */ +static int do_cmd(char *ble_addr, bool normal_mode, uint8_t *buf, uint16_t len, + char *name, bool verbose) +{ + int err; + const char *uuid; + + if (normal_mode) { + uuid = DFU_BUTTONLESS_UUID; + } else { + uuid = DFU_CONTROL_POINT_UUID; + } + notify_received = false; + + err = gatt_write(ble_addr, uuid, buf, len, on_sent); + if (err) { + LOG_ERR("Error writing %s: %d", name, err); + return err; + } + err = wait_for_notification(); + if (err) { + LOG_ERR("Timeout waiting for notification from %s: %d", + name, err); + return err; + } + err = normal_mode ? decode_secure_dfu() : decode_dfu(); + if (err && verbose) { + LOG_ERR("Notification decode error from %s: %d", + name, err); + } + return err; +} + +static int send_switch_to_dfu(char *ble_addr) +{ + char smol_buf[1]; + + smol_buf[0] = DFU_OP_ENTER_BOOTLOADER; + + return do_cmd(ble_addr, true, smol_buf, sizeof(smol_buf), "DFU", true); +} + + +/* set number of writes between CRC reports; 0 disables */ +static int send_prn(char *ble_addr, uint16_t receipt_rate) +{ + char smol_buf[3]; + + smol_buf[0] = NRF_DFU_OP_RECEIPT_NOTIF_SET; + smol_buf[1] = receipt_rate & 0xff; + smol_buf[2] = (receipt_rate >> 8) & 0xff; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "PRN", true); +} + +static int send_select_command(char *ble_addr) +{ + char smol_buf[2]; + + smol_buf[0] = NRF_DFU_OP_OBJECT_SELECT; + smol_buf[1] = 0x01; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "Select Cmd", true); +} + +static int send_select_data(char *ble_addr) +{ + char smol_buf[2]; + + smol_buf[0] = NRF_DFU_OP_OBJECT_SELECT; + smol_buf[1] = 0x02; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "Select Data", true); +} + +static int send_create_command(char *ble_addr, uint32_t size) +{ + uint8_t smol_buf[6]; + + smol_buf[0] = NRF_DFU_OP_OBJECT_CREATE; + smol_buf[1] = 0x01; + smol_buf[2] = size & 0xFF; + smol_buf[3] = (size >> 8) & 0xFF; + smol_buf[4] = (size >> 16) & 0xFF; + smol_buf[5] = (size >> 24) & 0xFF; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "Create Cmd", true); +} + +static int send_create_data(char *ble_addr, uint32_t size) +{ + uint8_t smol_buf[6]; + + LOG_DBG("Size is %d", size); + + smol_buf[0] = NRF_DFU_OP_OBJECT_CREATE; + smol_buf[1] = 0x02; + smol_buf[2] = size & 0xFF; + smol_buf[3] = (size >> 8) & 0xFF; + smol_buf[4] = (size >> 16) & 0xFF; + smol_buf[5] = (size >> 24) & 0xFF; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "Create Data", true); +} + +static void on_sent(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + const void *data; + uint16_t length; + + /* TODO: Make a copy of volatile data that is passed to the + * callback. Check err value at least in the wait function. + */ + data = params->data; + length = params->length; + + LOG_DBG("Resp err:0x%02X, from write:", err); + LOG_HEXDUMP_DBG(data, length, "sent"); +} + +static int send_request_crc(char *ble_addr) +{ + char smol_buf[1]; + + /* Requesting 32 bit offset followed by 32 bit CRC */ + smol_buf[0] = NRF_DFU_OP_CRC_GET; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "CRC Request", true); +} + +static int send_execute(char *ble_addr) +{ + char smol_buf[1]; + + LOG_DBG("Sending Execute"); + + smol_buf[0] = NRF_DFU_OP_OBJECT_EXECUTE; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "Execute", true); +} + +static int send_hw_version_get(char *ble_addr) +{ + char smol_buf[1]; + + LOG_INF("Sending HW Version Get"); + + smol_buf[0] = NRF_DFU_OP_HARDWARE_VERSION; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "Get HW Ver", false); +} + +static int send_fw_version_get(char *ble_addr, uint8_t fw_type) +{ + char smol_buf[2]; + + LOG_INF("Sending FW Version Get"); + + smol_buf[0] = NRF_DFU_OP_FIRMWARE_VERSION; + smol_buf[1] = fw_type; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "Get FW Ver", false); +} + +static int send_abort(char *ble_addr) +{ + char smol_buf[1]; + + smol_buf[0] = 0x0C; + + return do_cmd(ble_addr, false, smol_buf, + sizeof(smol_buf), "Abort", true); +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/dfu/peripheral_dfu.h b/samples/cellular/nrf_cloud_ble_gateway/src/ble/dfu/peripheral_dfu.h new file mode 100644 index 000000000000..76b982ec13a4 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/dfu/peripheral_dfu.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef PERIPHERAL_DFU_H_ +#define PERIPHERAL_DFU_H_ + +int peripheral_dfu_init(void); +int peripheral_dfu_config(const char *addr, int size, const char *version, + uint32_t crc, bool init_pkt, bool use_printk); +int peripheral_dfu_start(const char *host, const char *file, int sec_tag, + const char *apn, size_t fragment_size); +int peripheral_dfu_cleanup(void); + +#endif diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/flash/CMakeLists.txt b/samples/cellular/nrf_cloud_ble_gateway/src/ble/flash/CMakeLists.txt new file mode 100644 index 000000000000..4f724759867d --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/flash/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_include_directories(include) +target_sources_ifdef( + CONFIG_FLASH_TEST + app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/flash_test.c + ) diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/flash/flash_test.c b/samples/cellular/nrf_cloud_ble_gateway/src/ble/flash/flash_test.c new file mode 100644 index 000000000000..d26575e275fa --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/flash/flash_test.c @@ -0,0 +1,139 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#if CONFIG_SPI_NOR || DT_NODE_HAS_STATUS(DT_INST(0, jedec_spi_nor), okay) +#define FLASH_NAME "JEDEC SPI-NOR" +#elif CONFIG_NORDIC_QSPI_NOR || DT_NODE_HAS_STATUS(DT_INST(0, nordic_qspi_nor), okay) +#define FLASH_NAME "JEDEC QSPI-NOR" +#else +#error Unsupported flash driver +#endif + +#define FLASH_TEST_REGION_OFFSET 0xff000 +#define FLASH_SECTOR_SIZE 4096 + +#define MEMCTRL_NODE DT_NODELABEL(ext_mem_ctrl) + +#define MEMCTRL_GPIO_CTRL DT_GPIO_CTLR(MEMCTRL_NODE, gpios) +#define MEMCTRL_GPIO_PIN DT_GPIO_PIN(MEMCTRL_NODE, gpios) + +/** + * Set the external mem control pin to high to + * enable access to the external memory chip. + */ +static int flash_test_init(void) +{ + const struct device *port; + int err; + + printk("%s\n", __func__); + + port = DEVICE_DT_GET(MEMCTRL_GPIO_CTRL); + if (!port) { + printk("memctrl port not found: %d\n", errno); + return -EIO; + } + + err = gpio_pin_configure(port, MEMCTRL_GPIO_PIN, GPIO_OUTPUT_HIGH); + if (err) { + printk("error on gpio_pin_configure(): %d\n", err); + return err; + } + + printk("init complete\n"); + return err; +} + +SYS_INIT(flash_test_init, POST_KERNEL, CONFIG_SPI_NOR_INIT_PRIORITY); + +int flash_test(void) +{ + const uint8_t expected[] = { + 0x55, 0xaa, 0x66, 0x99, 0xa5, 0x5a, 0x69, 0x96, + 0xa5, 0x5a, 0x96, 0x69, 0x5a, 0xa5, 0x4b, 0xb4 + }; + const size_t len = sizeof(expected); + uint8_t buf[sizeof(expected)]; + const struct device *flash_dev; + int rc; + int ret = 0; + + printk("\n" FLASH_NAME " SPI flash testing\n"); + printk("==========================\n"); + +#if CONFIG_SPI_NOR || DT_NODE_HAS_STATUS(DT_INST(0, jedec_spi_nor), okay) + flash_dev = DEVICE_DT_GET_ONE(jedec_spi_nor); +#elif CONFIG_NORDIC_QSPI_NOR || DT_NODE_HAS_STATUS(DT_INST(0, nordic_qspi_nor), okay) + flash_dev = DEVICE_DT_GET_ONE(jedec_qspi_nor); +#endif + + if (!flash_dev) { + printk("SPI flash driver %s was not found!\n", FLASH_NAME); + ret = -EIO; + goto error; + } + + printk("\nTest 1: Flash erase: "); + + rc = flash_erase(flash_dev, FLASH_TEST_REGION_OFFSET, + FLASH_SECTOR_SIZE); + if (rc != 0) { + printk("FAIL - %d\n", rc); + } else { + printk("PASS\n"); + } + + printk("\nTest 2: Flash write %u bytes: ", len); + + rc = flash_write(flash_dev, FLASH_TEST_REGION_OFFSET, expected, len); + if (rc != 0) { + printk("FAIL - %d\n", rc); + ret = -EIO; + goto error; + } else { + printk("PASS\n"); + } + + printk("\nTest 3: Flash read %u bytes: ", len); + memset(buf, 0, len); + rc = flash_read(flash_dev, FLASH_TEST_REGION_OFFSET, buf, len); + if (rc != 0) { + printk("FAIL - %d\n", rc); + ret = -EIO; + goto error; + } else { + printk("PASS\n"); + } + + printk("\nTest 4: Compare %u bytes: ", len); + if (memcmp(expected, buf, len) != 0) { + const uint8_t *wp = expected; + const uint8_t *rp = buf; + const uint8_t *rpe = rp + len; + + ret = -EFAULT; + printk("FAIL - %d\n", ret); + while (rp < rpe) { + printk("%08x wrote %02x read %02x %s\n", + (uint32_t)(FLASH_TEST_REGION_OFFSET + (rp - buf)), + *wp, *rp, (*rp == *wp) ? "match" : "MISMATCH"); + ++rp; + ++wp; + } + goto error; + } else { + printk("PASS\n"); + } + return 0; + +error: + return ret; +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/flash/include/flash_test.h b/samples/cellular/nrf_cloud_ble_gateway/src/ble/flash/include/flash_test.h new file mode 100644 index 000000000000..bdfb13ad5376 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/flash/include/flash_test.h @@ -0,0 +1,10 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _FLASH_TEST_H_ + +int flash_test(void); + +#endif /* _FLASH_TEST_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/gateway.c b/samples/cellular/nrf_cloud_ble_gateway/src/ble/gateway.c new file mode 100644 index 000000000000..c945f63ef05f --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/gateway.c @@ -0,0 +1,694 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_NRF_MODEM_LIB) +#include +#endif +#include +#include + +#include "gateway.h" +#include "nrf_cloud_mem.h" + +#include "cJSON.h" +#include "cJSON_os.h" +#include "ble.h" +#include "ble_codec.h" +#include "ble_conn_mgr.h" + +LOG_MODULE_REGISTER(gateway, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +#define AT_CMNG_READ_LEN 97 + +#define CLOUD_PROC_STACK_SIZE 2048 +#define CLOUD_PROC_PRIORITY 5 + +#define GET_PSK_ID "AT%CMNG=2,16842753,4" +#define GET_PSK_ID_LEN (sizeof(GET_PSK_ID)-1) +#define GET_PSK_ID_ERR "ERROR" + +/* Uncomment below to enable writing characteristics from cloud_data_process() + * rather than from gateway_handler(). However, writing added too much data + * to the fifo. There was no memory to unsubscribe from memory intensive + * subscribes. + */ +#define QUEUE_CHAR_WRITES +#define QUEUE_CHAR_READS + +#define VALUE_BUF_SIZE 256 +static uint8_t value_buf[VALUE_BUF_SIZE]; + +char gateway_id[NRF_CLOUD_CLIENT_ID_LEN+1]; + +struct cloud_data_t { + void *fifo_reserved; + char addr[BT_ADDR_STR_LEN]; + char uuid[UUID_STR_LEN]; + uint8_t client_char_config; + bool read; + bool ccc; + bool sub; +#if defined(QUEUE_CHAR_WRITES) + bool write; + size_t data_len; + uint8_t data[VALUE_BUF_SIZE]; +#endif +}; + +static atomic_t queued_cloud_data; +K_FIFO_DEFINE(cloud_data_fifo); + +static int gateway_handler(cJSON *json); + +int gateway_shadow_delta_handler(const struct nrf_cloud_obj_shadow_delta *delta) +{ + cJSON *root_obj = delta->state.json; + cJSON *desired_connections_obj = cJSON_GetObjectItem(root_obj, "desiredConnections"); + + if (!desired_connections_obj) { + LOG_DBG("No desired connections provided"); + return 0; + } + + return desired_conns_handler(desired_connections_obj); +} + +int gateway_shadow_accepted_handler(const struct nrf_cloud_obj *desired) +{ + ARG_UNUSED(desired); + + int err = nrf_cloud_shadow_transform_request("state.desired.desiredConnections", 1024); + + if (err) { + LOG_ERR("Error requesting shadow tf: %d", err); + } + return err; +} + +int gateway_shadow_transform_handler(const struct nrf_cloud_obj *desired_conns) +{ + if (!desired_conns || (desired_conns->type != NRF_CLOUD_OBJ_TYPE_JSON) || + !(desired_conns->json)) { + return -EINVAL; + } + cJSON *desired_tf = desired_conns->json; + cJSON *desired = cJSON_GetObjectItem(desired_tf, "tf"); + + return desired_conns_handler(desired); +} + +static void cloud_data_process(int unused1, int unused2, int unused3) +{ + ARG_UNUSED(unused1); + ARG_UNUSED(unused2); + ARG_UNUSED(unused3); + struct k_mutex lock; + int ret; + + k_mutex_init(&lock); + + while (1) { + LOG_DBG("Waiting for cloud_data_fifo"); + struct cloud_data_t *cloud_data = k_fifo_get(&cloud_data_fifo, K_FOREVER); + + atomic_dec(&queued_cloud_data); + LOG_DBG("Dequeued cloud_data_fifo element; still queued: %ld", + atomic_get(&queued_cloud_data)); + + if (cloud_data != NULL) { + k_mutex_lock(&lock, K_FOREVER); + + if (cloud_data->sub) { + LOG_DBG("Dequeued sub msg"); + ble_subscribe(cloud_data->addr, + cloud_data->uuid, + cloud_data->client_char_config); + } +#if defined(QUEUE_CHAR_READS) + else if (cloud_data->read) { + LOG_DBG("Dequeued gatt_read request %s, %s, %u", + cloud_data->addr, + cloud_data->uuid, + cloud_data->ccc); + ret = gatt_read(cloud_data->addr, + cloud_data->uuid, + cloud_data->ccc); + if (ret) { + LOG_ERR("Error on gatt_read(%s, %s, %u): %d", + cloud_data->addr, + cloud_data->uuid, + cloud_data->ccc, + ret); + } + } +#endif +#if defined(QUEUE_CHAR_WRITES) + else if (cloud_data->write) { + LOG_DBG("Dequeued gatt write request"); + ret = gatt_write(cloud_data->addr, + cloud_data->uuid, + cloud_data->data, + cloud_data->data_len, NULL); + if (ret) { + LOG_ERR("Error on gatt_write(%s, %s): %d", + cloud_data->addr, + cloud_data->uuid, + ret); + } + } +#endif + k_free(cloud_data); + k_mutex_unlock(&lock); + } + } +} + +K_THREAD_DEFINE(cloud_proc_thread, CLOUD_PROC_STACK_SIZE, + cloud_data_process, NULL, NULL, NULL, + K_LOWEST_APPLICATION_THREAD_PRIO, 0, 0); + +static cJSON *json_object_decode(cJSON *obj, const char *str) +{ + return obj ? cJSON_GetObjectItem(obj, str) : NULL; +} + +static bool compare(const char *s1, const char *s2) +{ + return !strncmp(s1, s2, strlen(s2)); +} + +int gateway_data_handler(const struct nrf_cloud_data *const dev_msg) +{ + struct nrf_cloud_obj msg_obj; + int err = nrf_cloud_obj_input_decode(&msg_obj, dev_msg); + + if (err) { + /* The message isn't JSON or otherwise couldn't be parsed. */ + LOG_DBG("A general topic device message of length %d could not be parsed.", + dev_msg->len); + return err; + } + + if (msg_obj.type != NRF_CLOUD_OBJ_TYPE_JSON) { + LOG_DBG("Wrong message type for gateway: %d", msg_obj.type); + err = -EINVAL; + } else { + err = gateway_handler(msg_obj.json); + } + + (void)nrf_cloud_obj_free(&msg_obj); + return err; +} + +static int gateway_handler(cJSON *root_obj) +{ + int ret = 0; + cJSON *desired_obj; + + cJSON *type_obj; + cJSON *operation_obj; + + cJSON *ble_address; + + cJSON *chrc_uuid; + cJSON *service_uuid; + cJSON *desc_arr; + uint8_t desc_buf[2] = {0}; + uint8_t desc_len = 0; + + cJSON *value_arr; + uint8_t value_len = 0; + + if (root_obj == NULL) { + return -ENOENT; + } + + type_obj = json_object_decode(root_obj, "type"); + + if (type_obj == NULL) { + ret = -ENOENT; + goto exit_handler; + } + + const char *type_str = type_obj->valuestring; + + if (!compare(type_str, "operation")) { + ret = -EINVAL; + goto exit_handler; + } + + operation_obj = json_object_decode(root_obj, "operation"); + desired_obj = json_object_decode(operation_obj, "type"); + + if (compare(desired_obj->valuestring, "scan")) { + desired_obj = json_object_decode(operation_obj, "scanType"); + switch (desired_obj->valueint) { + case 0: + /* submit k_work to respond */ + LOG_INF("Cloud requested BLE scan"); + scan_start(false); + break; + case 1: + /* submit k_work to respond */ + /* TODO: Add beacon support */ + break; + default: + break; + } + + } else if (compare(desired_obj->valuestring, + "device_characteristic_value_read")) { + ble_address = json_object_decode(operation_obj, + "deviceAddress"); + chrc_uuid = json_object_decode(operation_obj, + "characteristicUUID"); + + LOG_INF("Got device_characteristic_value_read: %s", + ble_address->valuestring); + if ((ble_address != NULL) && (chrc_uuid != NULL)) { +#if defined(QUEUE_CHAR_READS) + struct cloud_data_t cloud_data = { + .read = true, + .ccc = false, + .sub = false + }; + + memcpy(&cloud_data.addr, + ble_address->valuestring, + strlen(ble_address->valuestring)); + memcpy(&cloud_data.uuid, + chrc_uuid->valuestring, + strlen(chrc_uuid->valuestring)); + + size_t size = sizeof(struct cloud_data_t); + char *mem_ptr = k_malloc(size); + + if (mem_ptr == NULL) { + LOG_ERR("Out of memory!"); + ret = -ENOMEM; + goto exit_handler; + } + + memcpy(mem_ptr, &cloud_data, size); + atomic_inc(&queued_cloud_data); + k_fifo_put(&cloud_data_fifo, mem_ptr); + LOG_DBG("Queued device_characteristic_value_read addr:%s, uuid:%s; " + "queued: %ld", + cloud_data.addr, cloud_data.uuid, atomic_get(&queued_cloud_data)); +#else + ret = gatt_read(ble_address->valuestring, chrc_uuid->valuestring, false); + if (ret) { + LOG_ERR("Error on gatt_read(%s, %s, 0): %d", + ble_address->valuestring, + chrc_uuid->valuestring, + ret); + } +#endif + } + + } else if (compare(desired_obj->valuestring, + "device_descriptor_value_read")) { + + ble_address = json_object_decode(operation_obj, + "deviceAddress"); + chrc_uuid = json_object_decode(operation_obj, + "characteristicUUID"); + + LOG_INF("Got device_descriptor_value_read: %s", + ble_address->valuestring); + if ((ble_address != NULL) && (chrc_uuid != NULL)) { +#if defined(QUEUE_CHAR_READS) + struct cloud_data_t cloud_data = { + .read = true, + .ccc = true, + .sub = false + }; + + memcpy(&cloud_data.addr, + ble_address->valuestring, + strlen(ble_address->valuestring)); + memcpy(&cloud_data.uuid, + chrc_uuid->valuestring, + strlen(chrc_uuid->valuestring)); + + size_t size = sizeof(struct cloud_data_t); + char *mem_ptr = k_malloc(size); + + if (mem_ptr == NULL) { + LOG_ERR("Out of memory!"); + ret = -ENOMEM; + goto exit_handler; + } + + memcpy(mem_ptr, &cloud_data, size); + atomic_inc(&queued_cloud_data); + k_fifo_put(&cloud_data_fifo, mem_ptr); + LOG_DBG("Queued device_descriptor_value_read addr:%s, uuid:%s, queued: %ld", + cloud_data.addr, cloud_data.uuid, atomic_get(&queued_cloud_data)); +#else + ret = gatt_read(ble_address->valuestring, chrc_uuid->valuestring, true); + if (ret) { + LOG_ERR("Error on gatt_read(%s, %s, 1): %d", + ble_address->valuestring, + chrc_uuid->valuestring, + ret); + } +#endif + } + + } else if (compare(desired_obj->valuestring, + "device_descriptor_value_write")) { + ble_address = json_object_decode(operation_obj, + "deviceAddress"); + chrc_uuid = json_object_decode(operation_obj, + "characteristicUUID"); + desc_arr = json_object_decode(operation_obj, + "descriptorValue"); + + desc_len = cJSON_GetArraySize(desc_arr); + for (int i = 0; i < desc_len; i++) { + cJSON *item = cJSON_GetArrayItem(desc_arr, i); + + desc_buf[i] = item->valueint; + } + + LOG_DBG("Got device_descriptor_value_write " + "addr: %s, uuid: %s, value: (0x%02X, 0x%02X)", + ble_address->valuestring, chrc_uuid->valuestring, + desc_buf[0], desc_buf[1]); + + if ((ble_address != NULL) && (chrc_uuid != NULL)) { +#if defined(QUEUE_CHAR_WRITES) + struct cloud_data_t cloud_data = { + .sub = true, + .read = false + }; + + memcpy(&cloud_data.addr, + ble_address->valuestring, + strlen(ble_address->valuestring)); + memcpy(&cloud_data.uuid, + chrc_uuid->valuestring, + strlen(chrc_uuid->valuestring)); + + cloud_data.client_char_config = desc_buf[0]; + + size_t size = sizeof(struct cloud_data_t); + char *mem_ptr = k_malloc(size); + + if (mem_ptr == NULL) { + LOG_ERR("Out of memory!"); + ret = -ENOMEM; + goto exit_handler; + } + + memcpy(mem_ptr, &cloud_data, size); + atomic_inc(&queued_cloud_data); + k_fifo_put(&cloud_data_fifo, mem_ptr); + LOG_DBG("Queued device_descriptor_value_write addr:%s, uuid:%s, " + "ccc:%d, sub:%d, conf:%d, queued:%ld", + cloud_data.addr, + cloud_data.uuid, + cloud_data.ccc, cloud_data.sub, + cloud_data.client_char_config, + atomic_get(&queued_cloud_data)); +#else + ret = gatt_write(ble_address->valuestring, + chrc_uuid->valuestring, + desc_buf, desc_len, NULL); + if (ret) { + LOG_ERR("Error on gatt_write(%s, %s): %d", + ble_address->valuestring, + chrc_uuid->valuestring, + ret); + } +#endif + } + + } else if (compare(desired_obj->valuestring, + "device_characteristic_value_write")) { + ble_address = json_object_decode(operation_obj, + "deviceAddress"); + chrc_uuid = json_object_decode(operation_obj, + "characteristicUUID"); + service_uuid = json_object_decode(operation_obj, + "serviceUUID"); + value_arr = json_object_decode(operation_obj, + "characteristicValue"); + + if (cJSON_IsString(value_arr)) { + char *str = cJSON_GetStringValue(value_arr); + + strncpy(value_buf, str, sizeof(value_buf) - 1); + value_len = strlen(value_buf); + } else { + value_len = MIN(cJSON_GetArraySize(value_arr), VALUE_BUF_SIZE); + + for (int i = 0; i < value_len; i++) { + cJSON *item = cJSON_GetArrayItem(value_arr, i); + + value_buf[i] = item->valueint; + } + } + + LOG_DBG("Got device_characteristic_value_write " + "addr: %s, svc uuid:%s, chrc uuid:%s", + ble_address->valuestring, + service_uuid ? service_uuid->valuestring : "n/a", + chrc_uuid ? chrc_uuid->valuestring : "n/a"); + LOG_HEXDUMP_DBG(value_buf, value_len, "value"); + + if ((ble_address != NULL) && (chrc_uuid != NULL)) { +#if defined(QUEUE_CHAR_WRITES) + struct cloud_data_t cloud_data = { + .write = true, + .data_len = value_len + }; + + memcpy(&cloud_data.addr, + ble_address->valuestring, + strlen(ble_address->valuestring)); + memcpy(&cloud_data.uuid, + chrc_uuid->valuestring, + strlen(chrc_uuid->valuestring)); + memcpy(&cloud_data.data, + value_buf, + value_len); + + size_t size = sizeof(struct cloud_data_t); + char *mem_ptr = k_malloc(size); + + if (mem_ptr == NULL) { + LOG_ERR("Out of memory!"); + ret = -ENOMEM; + goto exit_handler; + } + + memcpy(mem_ptr, &cloud_data, size); + atomic_inc(&queued_cloud_data); + k_fifo_put(&cloud_data_fifo, mem_ptr); + LOG_DBG("Queued device_characteristic_value_write " + "addr:%s, uuid:%s, queued:%ld", + cloud_data.addr, cloud_data.uuid, atomic_get(&queued_cloud_data)); +#else + ret = gatt_write(ble_address->valuestring, + chrc_uuid->valuestring, + value_buf, value_len, NULL); + if (ret) { + LOG_ERR("Error on gatt_write(%s, %s): %d", + ble_address->valuestring, + chrc_uuid->valuestring, + ret); + } +#endif + } + + } else if (compare(desired_obj->valuestring, "device_discover")) { + ble_address = json_object_decode(operation_obj, + "deviceAddress"); + if (ble_address != NULL) { + LOG_INF("Cloud requested device_discover: %s", + ble_address->valuestring); + ble_conn_mgr_rediscover(ble_address->valuestring); + } + } + +exit_handler: + return ret; +} + +void set_log_panic(void) +{ + LOG_PANIC(); +} + +static void starting_button_handler(void) +{ +#if defined(CONFIG_ENTER_52840_MCUBOOT_VIA_BUTTON) + /* the active board file need to include the functions: + * nrf52840_reset_to_mcuboot() and nrf52840_wait_boot_low() + */ + if (ui_button_is_active(1)) { + printk("BOOT BUTTON HELD\n"); + /* ui_led_set_pattern(UI_BLE_BUTTON, PWM_DEV_1); */ + while (ui_button_is_active(1)) { + k_sleep(K_MSEC(500)); + } + printk("BOOT BUTTON RELEASED\n"); + if (!is_boot_selected()) { + printk("Boot held after reset but not long enough" + " to select the nRF52840 bootloader!\n"); + /* ui_led_set_pattern(UI_BLE_OFF, PWM_DEV_1); */ + } else { + /* User wants to update the 52840 */ + nrf52840_reset_to_mcuboot(); + + /* wait forever for signal from other side so we can + * continue + */ + int err; + + err = nrf52840_wait_boot_complete(WAIT_BOOT_TIMEOUT_MS); + if (err == 0) { + /* ui_led_set_pattern(UI_BLE_OFF, PWM_DEV_1); */ + k_sleep(K_SECONDS(1)); + printk("nRF52840 update complete\n"); + } else { + /* unable to monitor; just halt */ + printk("Error waiting for nrf52840 reboot: %d\n", + err); + for (;;) { + k_sleep(K_MSEC(500)); + } + } + } + } +#endif +} + +void init_gateway(void) +{ + int err; + + starting_button_handler(); + err = ble_init(); + if (err) { + LOG_ERR("Error initializing BLE: %d", err); + } else { + /* Set BLE is disconnected LED pattern */ + } + + ble_codec_init(); + +#if CONFIG_GATEWAY_BLE_FOTA + ret = peripheral_dfu_init(); + if (ret) { + LOG_ERR("Error initializing BLE DFU: %d", ret); + } +#endif +} + +void reset_gateway(void) +{ + ble_conn_mgr_init(); + ble_conn_mgr_clear_desired(true); + ble_stop_activity(); +} + +int g2c_send(const struct nrf_cloud_data *output) +{ + struct nrf_cloud_tx_data msg; + + msg.data.ptr = output->ptr; + msg.data.len = output->len; + msg.topic_type = NRF_CLOUD_TOPIC_MESSAGE; + msg.qos = MQTT_QOS_1_AT_LEAST_ONCE; + msg.id = 0; + msg.obj = NULL; + + return nrf_cloud_send(&msg); +} + +int gw_shadow_publish(const struct nrf_cloud_data *output) +{ + struct nrf_cloud_tx_data msg; + + msg.data.ptr = output->ptr; + msg.data.len = output->len; + msg.topic_type = NRF_CLOUD_TOPIC_STATE; + msg.qos = MQTT_QOS_1_AT_LEAST_ONCE; + msg.id = 0; + msg.obj = NULL; + + return nrf_cloud_send(&msg); +} + +void device_shutdown(bool reboot) +{ + int err; + + LOG_PANIC(); + if (!reboot) { + LOG_INF("Shutting down..."); +#if defined(CONFIG_MODEM_WAKEUP_PIN) + nrf_gpio_cfg_input(CONFIG_MODEM_WAKEUP_PIN, + NRF_GPIO_PIN_PULLUP); + nrf_gpio_cfg_sense_set(CONFIG_MODEM_WAKEUP_PIN, + NRF_GPIO_PIN_SENSE_LOW); +#endif + } + + /* Set BLE shutdown pattern */ + + LOG_INF("Disconnect from cloud..."); + err = nrf_cloud_disconnect(); + if (err) { + LOG_ERR("Error closing cloud: %d", + err); + } + + LOG_INF("Power off LTE..."); + err = lte_lc_power_off(); + if (err) { + LOG_ERR("Error powering off LTE: %d", + err); + } + + LOG_INF("Shutdown modem..."); + err = nrf_modem_shutdown(); + if (err) { + LOG_ERR("Error on bsd_shutdown(): %d", + err); + } + + if (reboot) { + LOG_INF("Rebooting..."); + k_sleep(K_SECONDS(1)); + +#if defined(CONFIG_REBOOT) + sys_reboot(SYS_REBOOT_COLD); +#else + LOG_ERR("sys_reboot not defined: " + "enable CONFIG_REBOOT and rebuild"); +#endif + } else { + LOG_INF("Power down."); + k_sleep(K_SECONDS(1)); + NRF_REGULATORS_NS->SYSTEMOFF = 1; + } +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/ble/gateway.h b/samples/cellular/nrf_cloud_ble_gateway/src/ble/gateway.h new file mode 100644 index 000000000000..31ed9b8e3c04 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/ble/gateway.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef GATEWAY_CLOUD_TRANSPORT__ +#define GATEWAY_CLOUD_TRANSPORT__ + +#include +#define NRF_CLOUD_CLIENT_ID_LEN 128 +extern char gateway_id[NRF_CLOUD_CLIENT_ID_LEN+1]; + +struct cloud_msg; + +void device_shutdown(bool reboot); +void control_cloud_connection(bool enable); +void cli_init(void); +void set_log_panic(void); +void init_gateway(void); +void reset_gateway(void); +int g2c_send(const struct nrf_cloud_data *output); +int gw_shadow_publish(const struct nrf_cloud_data *output); +int gateway_shadow_delta_handler(const struct nrf_cloud_obj_shadow_delta *delta); +int gateway_shadow_accepted_handler(const struct nrf_cloud_obj *desired); +int gateway_shadow_transform_handler(const struct nrf_cloud_obj *desired_conns); +int gateway_data_handler(const struct nrf_cloud_data *const dev_msg); + +#if defined(CONFIG_ENTER_52840_MCUBOOT_VIA_BUTTON) +/* functions defined in the board's .c file */ +int nrf52840_reset_to_mcuboot(void); +int nrf52840_wait_boot_complete(int timeout_ms); +#endif + +#endif diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/cloud_connection.c b/samples/cellular/nrf_cloud_ble_gateway/src/cloud_connection.c new file mode 100644 index 000000000000..901034cb18ef --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/cloud_connection.c @@ -0,0 +1,761 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_NRF_CLOUD_COAP) +#include +#include "fota_support_coap.h" +#endif +#include + +#include "cloud_connection.h" +#include "provisioning_support.h" +#include "fota_support.h" +#include "location_tracking.h" +#include "led_control.h" +#include "shadow_config.h" +#if defined(CONFIG_NRF_CLOUD_GATEWAY) +#include "gateway.h" +#endif + +LOG_MODULE_REGISTER(cloud_connection, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +/* Internal state */ + +/* Pendable events that threads can wait for. */ +#define NETWORK_READY BIT(0) +#define CLOUD_CONNECTED BIT(1) +#define CLOUD_READY BIT(2) +#define CLOUD_DISCONNECTED BIT(3) +#define DATE_TIME_KNOWN BIT(4) +static K_EVENT_DEFINE(cloud_events); + +/* Atomic status flag tracking whether an initial association is in progress. */ +static atomic_t initial_association; +static bool device_deleted; +static bool connection_allowed = true; +static int reconnect_seconds = CONFIG_CLOUD_CONNECTION_RETRY_TIMEOUT_SECONDS; + +/* Helper functions for pending on pendable events. */ +bool await_network_ready(k_timeout_t timeout) +{ + return k_event_wait(&cloud_events, NETWORK_READY, false, timeout) != 0; +} + +bool await_cloud_ready(k_timeout_t timeout) +{ + return k_event_wait(&cloud_events, CLOUD_READY, false, timeout) != 0; +} + +bool await_cloud_disconnected(k_timeout_t timeout) +{ + return k_event_wait(&cloud_events, CLOUD_DISCONNECTED, false, timeout) != 0; +} + +bool await_date_time_known(k_timeout_t timeout) +{ + return k_event_wait(&cloud_events, DATE_TIME_KNOWN, false, timeout) != 0; +} + +bool is_device_deleted(void) +{ + return device_deleted; +} + +void control_cloud_connection(bool enable) +{ + connection_allowed = enable; +} + +/* Wait for a connection result, and return true if connection was successful within the timeout, + * otherwise return false. + */ +static bool await_connection_result(k_timeout_t timeout) +{ + /* After a connection attempt, either CLOUD_CONNECTED or CLOUD_DISCONNECTED will be + * raised, depending on whether the connection succeeded. + */ + uint32_t events = CLOUD_CONNECTED | CLOUD_DISCONNECTED; + + return (k_event_wait(&cloud_events, events, false, timeout) & CLOUD_CONNECTED) != 0; +} + +/* Delayable work item for handling cloud readiness timeout. + * The work item is scheduled at a delay any time connection starts and is cancelled when the + * connection to nRF Cloud becomes ready to use (signalled by NRF_CLOUD_EVT_READY). + * + * If the work item executes, that means the nRF Cloud connection did not become ready for use + * within the delay, and thus the connection should be reset (and then try again later). + */ +static void ready_timeout_work_fn(struct k_work *work) +{ + ARG_UNUSED(work); + LOG_INF("nRF Cloud connection did not become ready in time, disconnecting and retrying..."); + disconnect_cloud(); +} + +static K_WORK_DELAYABLE_DEFINE(ready_timeout_work, ready_timeout_work_fn); + +/* Start the readiness timeout if readiness is not already achieved. */ +static void start_readiness_timeout(void) +{ + /* It doesn't make sense to start the readiness timeout if we're already ready. */ + if (k_event_test(&cloud_events, CLOUD_READY)) { + LOG_DBG("Already ready."); + return; + } + + LOG_DBG("Starting cloud connection readiness timeout for %d seconds", + CONFIG_CLOUD_READY_TIMEOUT_SECONDS); + + k_work_reschedule(&ready_timeout_work, K_SECONDS(CONFIG_CLOUD_READY_TIMEOUT_SECONDS)); +} + +static void clear_readiness_timeout(void) +{ + LOG_DBG("Stopping cloud connection readiness timeout"); + k_work_cancel_delayable(&ready_timeout_work); +} + +/** + * @brief Update internal state in response to achieving connection. + */ +static void cloud_connected(void) +{ + LOG_INF("Connected to nRF Cloud"); + + shadow_config_cloud_connected(); + + /* Notify that the nRF Cloud connection is established. */ + k_event_post(&cloud_events, CLOUD_CONNECTED); +} + +/** + * @brief Update internal state in response to achieving readiness. + */ +static void cloud_ready(void) +{ + /* Clear the readiness timeout, since we have become ready. */ + clear_readiness_timeout(); + + reconnect_seconds = CONFIG_CLOUD_CONNECTION_RETRY_TIMEOUT_SECONDS; + + /* Notify that the nRF Cloud connection is ready for use. */ + k_event_post(&cloud_events, CLOUD_READY); + LOG_DBG("Setting CLOUD_READY"); + + if (IS_ENABLED(CONFIG_NRF_PROVISIONING)) { + LOG_INF("Reducing provisioning check interval to %d minutes", + CONFIG_POST_PROVISIONING_INTERVAL_M); + nrf_provisioning_set_interval(CONFIG_POST_PROVISIONING_INTERVAL_M * SEC_PER_MIN); + } +} + +/* A callback that the application may register in order to handle custom device messages. + * This is really a convenience callback to help keep this sample clean and modular. You could + * implement device message handling directly in the cloud_event_handler if desired. + */ +static dev_msg_handler_cb_t general_dev_msg_handler; +void register_general_dev_msg_handler(dev_msg_handler_cb_t handler_cb) +{ + general_dev_msg_handler = handler_cb; +} + +/* This function causes the cloud to disconnect, and updates internal state accordingly. + * + * It is also triggered by cloud disconnection, to update internal state. + * + * In this latter case, an unnecessary "Disconnecting from nRF Cloud" and + * "Already disconnected from nRF Cloud" will be printed. + * + * This is done to keep the sample simple, though the log messages may be a bit confusing. + */ +void disconnect_cloud(void) +{ + /* Clear the readiness timeout in case it was running. */ + clear_readiness_timeout(); + + /* Clear the Ready and Connected events, no longer accurate. */ + k_event_clear(&cloud_events, CLOUD_READY | CLOUD_CONNECTED); + LOG_DBG("Cleared CLOUD_READY and CLOUD_CONNECTED"); + + /* Clear the initial association flag, no longer accurate. */ + atomic_set(&initial_association, false); + + /* Disconnect from nRF Cloud -- Blocking call + * Will no-op and return -EACCES if already disconnected. + */ + LOG_INF("Disconnecting from nRF Cloud"); + int err; + +#if defined(CONFIG_NRF_CLOUD_MQTT) + err = nrf_cloud_disconnect(); +#elif defined(CONFIG_NRF_CLOUD_COAP) + err = nrf_cloud_coap_disconnect(); +#endif + /* nrf_cloud_disconnect returns -EACCES if we are not currently in a connected state. + * nrf_cloud_coap_disconnect returns -ENOTCONN if socket is not open. + */ + if ((err == -EACCES) || (err == -ENOTCONN)) { + LOG_DBG("Already disconnected from nRF Cloud"); + } else if (err) { + LOG_ERR("Cannot disconnect from nRF Cloud, error: %d. Continuing anyways", err); + } else { + LOG_INF("Successfully disconnected from nRF Cloud"); + } + + /* Fire the disconnected event. */ + k_event_post(&cloud_events, CLOUD_DISCONNECTED); +} + +static void network_disconnected(void) +{ +#if defined(CONFIG_NRF_CLOUD_MQTT) + disconnect_cloud(); +#elif defined(CONFIG_NRF_CLOUD_COAP) + if (!nrf_cloud_coap_keepopen_is_supported()) { + disconnect_cloud(); + return; + } + /* When using CoAP we keep the socket open during brief network outages. + * There is no need to fully disconnect and cause additional network traffic. + */ + if (k_event_test(&cloud_events, CLOUD_DISCONNECTED)) { + return; + } + clear_readiness_timeout(); + k_event_clear(&cloud_events, CLOUD_READY | CLOUD_CONNECTED); + k_event_post(&cloud_events, CLOUD_DISCONNECTED); + int err = nrf_cloud_coap_pause(); + + if ((err < 0) && (err != -EBADF)) { + /* -EBADF means it was disconnected, for example by FOTA. */ + LOG_ERR("Error pausing connection: %d", err); + } +#endif +} + +void cloud_transport_error_detected(void) +{ + /* We could do some kind of ping to verify the connection, but for now, + * just assume the detector was correct and force a reconnect. + */ + LOG_INF("Communication error detected."); + network_disconnected(); +} + +/** + * @brief Attempt to connect to nRF Cloud and update internal state accordingly. + * + * Blocks until connection attempt either succeeds or fails. + * + * @retval true if successful + * @retval false if connection failed + */ +static bool connect_cloud(void) +{ + int err; + + LOG_INF("Connecting to nRF Cloud"); + + /* Clear the disconnected flag, no longer accurate. */ + k_event_clear(&cloud_events, CLOUD_DISCONNECTED); + +#if defined(CONFIG_NRF_CLOUD_MQTT) + /* Connect to nRF Cloud -- Non-blocking. State updates are handled in callbacks. */ + err = nrf_cloud_connect(); +#elif defined(CONFIG_NRF_CLOUD_COAP) + /* Connect via CoAP -- blocking. */ + err = nrf_cloud_coap_connect(CONFIG_APP_VERSION); + + /* Cloud is immediately ready and connected since nrf_cloud_coap_connect is blocking. */ + if (!err) { + cloud_connected(); + cloud_ready(); + return true; + } +#endif + + /* If we were already connected, treat as a successful connection, but do nothing. */ + if (err == NRF_CLOUD_CONNECT_RES_ERR_ALREADY_CONNECTED) { + LOG_WRN("Already connected to nRF Cloud"); + return true; + } + + /* If the connection attempt fails immediately, report and exit. */ + if (err != 0) { + LOG_ERR("Could not connect to nRF Cloud, error: %d", err); + return false; + } + + /* Wait for the connection to either complete or fail. */ + if (!await_connection_result(K_FOREVER)) { + LOG_ERR("Could not connect to nRF Cloud"); + return false; + } + + /* If connection succeeded and we aren't already ready, start the readiness timeout. + * (Readiness check is performed by start_readiness_timeout). + */ + start_readiness_timeout(); + return true; +} + +/* External event handlers */ + +/* Handler for L4/connectivity events + * This allows the cloud module to react to network gain and loss. + * The conn_mgr subsystem is responsible for seeking / maintaining network connectivity and + * firing these events. + */ +static struct net_mgmt_event_callback l4_callback; +static void l4_event_handler(struct net_mgmt_event_callback *cb, + uint32_t event, struct net_if *iface) +{ + if (event == NET_EVENT_L4_CONNECTED) { + LOG_INF("Network connectivity gained!"); + + k_sleep(K_MSEC(1000)); + + /* Set the network ready flag */ + k_event_post(&cloud_events, NETWORK_READY); + + /* If LTE-event-driven date_time updates are disabled, manually trigger a date_time + * timestamp refresh. + * + * Note: The CONFIG_DATE_TIME_AUTO_UPDATE setting controls specifically whether + * LTE-event-driven date_time updates are enabled. The date_time library will still + * periodically refresh its timestamp if CONFIG_DATE_TIME_AUTO_UPDATE is disabled, + * but this refresh is infrequent, so we are manually requesting a refresh + * whenever internet access becomes available so that we get a timestamp + * immediately. + */ + if (!IS_ENABLED(CONFIG_DATE_TIME_AUTO_UPDATE)) { + date_time_update_async(NULL); + } + + } else if (event == NET_EVENT_L4_DISCONNECTED) { + LOG_INF("Network connectivity lost!"); + + /* Clear the network ready flag */ + k_event_clear(&cloud_events, NETWORK_READY); + + /* Network is now disconnected */ + network_disconnected(); + } +} + +/* Handler for date_time library events, used to keep track of whether the current time is known */ +static void date_time_event_handler(const struct date_time_evt *date_time_evt) +{ + if (date_time_is_valid()) { + k_event_post(&cloud_events, DATE_TIME_KNOWN); + } else { + k_event_clear(&cloud_events, DATE_TIME_KNOWN); + } +} + +#if defined(CONFIG_NRF_CLOUD_MQTT) +/* Handler for nRF Cloud shadow events */ +static void handle_shadow_event(struct nrf_cloud_obj_shadow_data *const shadow) +{ + if (!shadow) { + return; + } + + int err; + + if ((shadow->type == NRF_CLOUD_OBJ_SHADOW_TYPE_DELTA) && shadow->delta) { + bool accept = true; + + err = shadow_config_delta_process(&shadow->delta->state); + if (err == -EBADF) { + LOG_INF("Rejecting shadow delta"); + accept = false; + } else if (err == -ENOMEM) { + LOG_ERR("Error handling shadow delta"); + return; + } else if (err == -EAGAIN) { + LOG_DBG("Ignoring delta until accepted shadow is received"); + return; + } + + if (!shadow->delta->state.encoded_data.ptr && + (shadow->delta->state.enc_src != NRF_CLOUD_ENC_SRC_NONE)) { + LOG_ERR("Encoded data ptr is NULL"); + return; + } + if (!shadow->delta->state.json) { + LOG_ERR("JSON is NULL"); + return; + } + LOG_INF("Shadow: Delta - version: %d, timestamp: %lld, %*s", + shadow->delta->ver, + shadow->delta->ts, + shadow->delta->state.encoded_data.len, + (shadow->delta->state.enc_src != NRF_CLOUD_ENC_SRC_NONE) ? + (const char *)shadow->delta->state.encoded_data.ptr : + "(not encoded)"); + +#if defined(CONFIG_NRF_CLOUD_GATEWAY) + LOG_INF("Processing gateway connection changes"); + err = gateway_shadow_delta_handler(shadow->delta); + if (err) { + LOG_ERR("Error handling delta: %d", err); + } +#endif + + err = nrf_cloud_obj_shadow_delta_response_encode(&shadow->delta->state, accept); + if (err) { + LOG_ERR("Failed to encode shadow response: %d", err); + return; + } + + err = nrf_cloud_obj_shadow_update(&shadow->delta->state); + if (err) { + LOG_ERR("Failed to send shadow response, error: %d", err); + } + + } else if ((shadow->type == NRF_CLOUD_OBJ_SHADOW_TYPE_ACCEPTED) && shadow->accepted) { + LOG_DBG("Shadow: Accepted"); + err = shadow_config_accepted_process(&shadow->accepted->config); + if (err) { + /* Send the config on an error */ + (void)shadow_config_reported_send(); + } +#if defined(CONFIG_NRF_CLOUD_GATEWAY) + LOG_INF("Requesting gateway desired connections"); + (void)gateway_shadow_accepted_handler(&shadow->accepted->desired); +#endif + } else if ((shadow->type == NRF_CLOUD_OBJ_SHADOW_TYPE_TF) && shadow->transform) { + LOG_DBG("Shadow: Transform"); +#if defined(CONFIG_NRF_CLOUD_GATEWAY) + LOG_INF("Processing gateway desired connections"); + (void)gateway_shadow_transform_handler(&shadow->transform->result.obj); +#endif + } +} + +/* Handler for events from nRF Cloud Lib. */ +static void cloud_event_handler(const struct nrf_cloud_evt *nrf_cloud_evt) +{ + switch (nrf_cloud_evt->type) { + case NRF_CLOUD_EVT_TRANSPORT_CONNECTED: + LOG_DBG("NRF_CLOUD_EVT_TRANSPORT_CONNECTED"); + + /* Handle connection success. */ + cloud_connected(); + break; + case NRF_CLOUD_EVT_TRANSPORT_CONNECTING: + LOG_DBG("NRF_CLOUD_EVT_TRANSPORT_CONNECTING"); + break; + case NRF_CLOUD_EVT_TRANSPORT_CONNECT_ERROR: + LOG_DBG("NRF_CLOUD_EVT_TRANSPORT_CONNECT_ERROR: %d", nrf_cloud_evt->status); + + /* Disconnect from cloud immediately rather than wait for retry timeout. */ + disconnect_cloud(); + + break; + case NRF_CLOUD_EVT_USER_ASSOCIATION_REQUEST: + LOG_DBG("NRF_CLOUD_EVT_USER_ASSOCIATION_REQUEST"); + /* This event indicates that the user must associate the device with their + * nRF Cloud account in the nRF Cloud portal. + * + * The device must then disconnect and reconnect to nRF Cloud after association + * succeeds. + */ + LOG_INF("Please add this device to your cloud account in the nRF Cloud portal."); +#if defined(CONFIG_NRF_CLOUD_GATEWAY) + reset_gateway(); +#endif + + /* Store the fact that this is an initial association. + * + * This will cause the next NRF_CLOUD_EVT_USER_ASSOCIATED event to + * disconnect and reconnect the device to nRF Cloud, which is required + * when devices are first associated with an nRF Cloud account. + */ + atomic_set(&initial_association, true); + break; + case NRF_CLOUD_EVT_USER_ASSOCIATED: + LOG_DBG("NRF_CLOUD_EVT_USER_ASSOCIATED"); + /* Indicates successful association with an nRF Cloud account. + * Fired every time the device connects (unless the device is not associated). + * + * If this is an initial association, the device must disconnect and + * and reconnect before using nRF Cloud. + */ + device_deleted = false; + if (atomic_get(&initial_association)) { + /* Disconnect as is required. + * The connection loop will handle reconnection afterwards. + */ + LOG_INF("Device successfully associated with cloud! Reconnecting"); + reconnect_seconds = 1; + disconnect_cloud(); + } + break; + case NRF_CLOUD_EVT_READY: + LOG_DBG("NRF_CLOUD_EVT_READY"); + + /* Handle achievement of readiness */ + cloud_ready(); + + /* The nRF Cloud library will automatically update the + * device's shadow based on the build configuration. + * See config NRF_CLOUD_SEND_SHADOW_INFO for details. + */ + break; + case NRF_CLOUD_EVT_SENSOR_DATA_ACK: + LOG_DBG("NRF_CLOUD_EVT_SENSOR_DATA_ACK"); + break; + case NRF_CLOUD_EVT_TRANSPORT_DISCONNECTED: + LOG_DBG("NRF_CLOUD_EVT_TRANSPORT_DISCONNECTED"); + + /* The nRF Cloud library itself has disconnected for some reason. + * Execute a manual disconnect so that the event flags are updated. + * The internal nrf_cloud_disconnect call will no-op. + */ + disconnect_cloud(); + + break; + case NRF_CLOUD_EVT_ERROR: + LOG_DBG("NRF_CLOUD_EVT_ERROR: %d", nrf_cloud_evt->status); + break; + case NRF_CLOUD_EVT_RX_DATA_GENERAL: + LOG_DBG("NRF_CLOUD_EVT_RX_DATA_GENERAL"); + LOG_DBG("%d bytes received from cloud", nrf_cloud_evt->data.len); + /* Pass the device message along to the application, if it is listening */ + if (general_dev_msg_handler) { + /* To keep the sample simple, we invoke the callback directly. + * If you want to do complex operations in this callback without blocking + * receipt of data from nRF Cloud, you should set up a work queue and pass + * messages to it either here, or from inside the callback. + */ + general_dev_msg_handler(&nrf_cloud_evt->data); + } +#if defined(CONFIG_NRF_CLOUD_GATEWAY) + gateway_data_handler(&nrf_cloud_evt->data); +#endif + break; + case NRF_CLOUD_EVT_RX_DATA_DISCON: + LOG_DBG("NRF_CLOUD_EVT_RX_DATA_DISCON"); + LOG_INF("Device was removed from your account."); + device_deleted = true; + break; + case NRF_CLOUD_EVT_RX_DATA_SHADOW: { + LOG_DBG("NRF_CLOUD_EVT_RX_DATA_SHADOW"); + LOG_DBG("shadow: %*s", nrf_cloud_evt->data.len, + (const char *)nrf_cloud_evt->data.ptr); + handle_shadow_event(nrf_cloud_evt->shadow); + break; + } + case NRF_CLOUD_EVT_FOTA_START: + LOG_DBG("NRF_CLOUD_EVT_FOTA_START"); + break; + case NRF_CLOUD_EVT_FOTA_DONE: { + enum nrf_cloud_fota_type fota_type = NRF_CLOUD_FOTA_TYPE__INVALID; + + if (nrf_cloud_evt->data.ptr) { + fota_type = *((enum nrf_cloud_fota_type *) nrf_cloud_evt->data.ptr); + } + + LOG_DBG("NRF_CLOUD_EVT_FOTA_DONE, FOTA type: %s", + fota_type == NRF_CLOUD_FOTA_APPLICATION ? "Application" : + fota_type == NRF_CLOUD_FOTA_MODEM_DELTA ? "Modem (delta)" : + fota_type == NRF_CLOUD_FOTA_MODEM_FULL ? "Modem (full)" : + fota_type == NRF_CLOUD_FOTA_BOOTLOADER ? "Bootloader" : + "Invalid"); + + /* Notify fota_support of the completed download. */ + on_fota_downloaded(); + break; + } + case NRF_CLOUD_EVT_FOTA_ERROR: + LOG_DBG("NRF_CLOUD_EVT_FOTA_ERROR"); + break; + default: + LOG_DBG("Unknown event type: %d", nrf_cloud_evt->type); + break; + } +} +#endif /* CONFIG_NRF_CLOUD_MQTT */ + +/** + * @brief Set up for the nRF Cloud connection (without connecting) + * + * Sets up required event hooks and initializes the nrf_cloud library. + * + * @return int - 0 on success, otherwise negative error code. + */ +static int setup_cloud(void) +{ + int err; + + /* Register to be notified of network availability changes. + * + * If the chosen connectivity layer becomes ready instantaneously, it is possible that + * L4_CONNECTED will be fired before reaching this function, in which case we will miss + * the notification. + * + * If that is a serious concern, use SYS_INIT with priority 0 (less than + * CONFIG_NET_CONNECTION_MANAGER_PRIORITY) to register this hook before conn_mgr + * initializes. + * + * In reality, connectivity layers such as LTE take some time to go online, so registering + * the hook here is fine. + */ + net_mgmt_init_event_callback( + &l4_callback, l4_event_handler, NET_EVENT_L4_CONNECTED | NET_EVENT_L4_DISCONNECTED + ); + net_mgmt_add_event_callback(&l4_callback); + + /* Register to be notified when the modem has figured out the current time. */ + date_time_register_handler(date_time_event_handler); + +#if defined(CONFIG_NRF_CLOUD_MQTT) + /* Initialize nrf_cloud library. */ + struct nrf_cloud_init_param params = { + .event_handler = cloud_event_handler, + .fmfu_dev_inf = get_full_modem_fota_fdev(), + .application_version = CONFIG_APP_VERSION + }; + + err = nrf_cloud_init(¶ms); + if (err) { + LOG_ERR("nRF Cloud library could not be initialized, error: %d", err); + return err; + } + +#if defined(CONFIG_NRF_CLOUD_GATEWAY) + err = lte_lc_psm_req(IS_ENABLED(CONFIG_LTE_PSM_REQ)); + if (err) { + LOG_ERR("Unable to disable PSM: %d", err); + } +#endif + +#elif defined(CONFIG_NRF_CLOUD_COAP) +#if defined(CONFIG_COAP_FOTA) + err = coap_fota_init(); + if (err) { + LOG_ERR("Error initializing FOTA: %d", err); + return err; + } +#endif /* CONFIG_COAP_FOTA */ + err = nrf_cloud_coap_init(); + if (err) { + LOG_ERR("Failed to initialize CoAP client: %d", err); + return err; + } +#endif /* CONFIG_NRF_CLOUD_COAP */ + + return 0; +} + +/* Check whether nRF Cloud credentials are installed. If not, sleep forever. */ +static void check_credentials(void) +{ + int status = nrf_cloud_credentials_configured_check(); + +#if defined(CONFIG_NRF_CLOUD_GATEWAY) + if (status) { + reset_gateway(); + } +#endif + if (status == -ENOTSUP) { + if (IS_ENABLED(CONFIG_NRF_PROVISIONING)) { + LOG_WRN("nRF Cloud credentials are not installed. " + "Claim and onboard device on nrfcloud.com to continue."); + } else { + LOG_WRN("nRF Cloud credentials are not installed. " + "Please install and reboot."); + } + } else if (status == -ENOPROTOOPT) { + LOG_WRN("Required root CA certificate is missing."); + } else { + if (status) { + LOG_ERR("Error while checking for credentials: %d. Proceeding anyway.", + status); + } + return; + } + if (!IS_ENABLED(CONFIG_NRF_PROVISIONING)) { + /* Save power since credentials will not work, and we are not using the + * cloud-based nrf_provisioning service. + */ + conn_mgr_all_if_down(true); + } + k_sleep(K_FOREVER); +} + +void cloud_connection_thread_fn(void) +{ + long_led_pattern(LED_WAITING); + + LOG_INF("Enabling connectivity..."); + conn_mgr_all_if_connect(true); + + LOG_INF("Setting up nRF Cloud library..."); + if (setup_cloud()) { + LOG_ERR("Fatal: nRF Cloud library setup failed"); + long_led_pattern(LED_FAILURE); + return; + } + + /* Check for credentials, if the feature is enabled. */ + if (IS_ENABLED(CONFIG_NRF_CLOUD_CHECK_CREDENTIALS)) { + check_credentials(); + } + + /* Indefinitely maintain a connection to nRF Cloud whenever the network is reachable. */ + while (true) { + LOG_INF("Waiting for network ready..."); + + if (IS_ENABLED(CONFIG_LED_VERBOSE_INDICATION)) { + long_led_pattern(LED_WAITING); + } + + (void)await_network_ready(K_FOREVER); + + LOG_INF("Network is ready"); + + /* Wait for provisioning to complete, if the provisioning library is enabled. */ + if (IS_ENABLED(CONFIG_NRF_PROVISIONING)) { + LOG_DBG("Awaiting provisioning idle"); + (void)await_provisioning_idle(K_FOREVER); + } + + /* Obtain time before connecting to the cloud, + * otherwise NTP query will fail. + */ + if (IS_ENABLED(CONFIG_WIFI)) { + LOG_INF("Waiting to obtain date/time"); + (void)await_date_time_known(K_FOREVER); + } + + /* Attempt to connect to nRF Cloud. */ + if (connect_cloud()) { + LOG_DBG("Monitoring nRF Cloud connection"); + + /* and then wait patiently for a connection problem. */ + (void)await_cloud_disconnected(K_FOREVER); + + LOG_INF("Disconnected from nRF Cloud"); + } + + LOG_INF("Retrying in %d seconds...", reconnect_seconds); + + /* Wait a bit before trying again. */ + k_sleep(K_SECONDS(reconnect_seconds)); + } +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/cloud_connection.h b/samples/cellular/nrf_cloud_ble_gateway/src/cloud_connection.h new file mode 100644 index 000000000000..d06960ee1794 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/cloud_connection.h @@ -0,0 +1,97 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _CLOUD_CONNECTION_H_ +#define _CLOUD_CONNECTION_H_ +#include + +/** + * @brief nRF Cloud device message handler. + * + * @param[in] dev_msg The received device message as a NULL-terminated string + */ +typedef void (*dev_msg_handler_cb_t)(const struct nrf_cloud_data *const dev_msg); + +/** + * @brief Sleep until the network is ready for use, or the specified timeout elapses. + * + * @param timeout - The time to wait before timing out. + * @return true if the network became ready in time. + * @return false if timed out. + */ +bool await_network_ready(k_timeout_t timeout); + +/** + * @brief Sleep until the nRF Cloud connection is ready for use, or the specified timeout elapses. + * + * @param timeout - The time to wait before timing out. + * @return true if nRF Cloud became ready in time. + * @return false if timed out. + */ +bool await_cloud_ready(k_timeout_t timeout); + +/** + * @brief Sleep until connection to nRF Cloud is lost, or the specified timeout elapses. + * + * @param timeout - The time to wait before timing out. + * @return true if the connection was lost within the timeout. + * @return false if timed out. + */ +bool await_cloud_disconnected(k_timeout_t timeout); + +/** + * @brief Sleep until the current date-time is known, or the specified timeout elapses. + * + * @param timeout - The time to wait before timing out. + * @return true if the current date-time was determined within the timeout. + * @return false if timed out. + */ +bool await_date_time_known(k_timeout_t timeout); + +/** + * @brief Check if device has been removed from the cloud. + * + * @return true if it was just removed. + * @return false if not removed. + */ +bool is_device_deleted(void); + +/** + * @brief Register a device message handler to receive general device messages from nRF Cloud. + * + * The callback will be called directly from the nRF Cloud connection poll thread, so it will block + * receipt of data from nRF Cloud until complete. Avoid lengthy operations. + * + * @param handler_cb - The callback to handle device messages + */ +void register_general_dev_msg_handler(dev_msg_handler_cb_t handler_cb); + +/** + * @brief Disconnect from nRF Cloud and update internal state accordingly. + * + * May also be called to report an external disconnection. + */ +void disconnect_cloud(void); + +/** + * @brief Allow automatic reconnections to the cloud. + */ +void control_cloud_connection(bool enable); + +/** + * @brief Report a communications error so the cloud_connection can evaluate and + * recover. + */ +void cloud_transport_error_detected(void); + +/** + * @brief Cloud connection thread function. + * + * This function manages the cloud connection thread. Once called, it begins monitoring network + * status and persistently maintains a connection to nRF Cloud whenever Internet is available. + */ +void cloud_connection_thread_fn(void); + +#endif /* _CLOUD_CONNECTION_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/fota_support.c b/samples/cellular/nrf_cloud_ble_gateway/src/fota_support.c new file mode 100644 index 000000000000..a9d7ce931119 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/fota_support.c @@ -0,0 +1,45 @@ +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include "fota_support.h" +#include "sample_reboot.h" + +LOG_MODULE_REGISTER(fota_support, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +void on_fota_downloaded(void) +{ + sample_reboot_normal(); +} + +struct dfu_target_fmfu_fdev *get_full_modem_fota_fdev(void) +{ + if (IS_ENABLED(CONFIG_NRF_CLOUD_FOTA_FULL_MODEM_UPDATE)) { + static struct dfu_target_fmfu_fdev ext_flash_dev = { + .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 + }; + + return &ext_flash_dev; + } + + return NULL; +} + +/* You may notice there is no logic to actually receive/download/apply FOTA updates. + * That is because these features are automatically implemented for MQTT by enabling + * CONFIG_NRF_CLOUD_FOTA (which is implicitly enabled by CONFIG_NRF_CLOUD_MQTT). + * + * Note, also that connection.c is responsible for actually enabling FOTA in the nRF + * Cloud portal, (by correctly updating the device shadow) as well as for notifying + * fota_support.c of FOTA download completion. + */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/fota_support.h b/samples/cellular/nrf_cloud_ble_gateway/src/fota_support.h new file mode 100644 index 000000000000..b0d65ed4bbed --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/fota_support.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _FOTA_SUPPORT_H_ +#define _FOTA_SUPPORT_H_ + +#include + +/* Time in seconds to wait before rebooting after a FOTA update. */ +#define FOTA_REBOOT_DELAY_S 5 +#define PENDING_REBOOT_S 10 +#define ERROR_REBOOT_S 30 +#define FOTA_REBOOT_S 10 + +/** + * @brief Notify fota_support that a FOTA download has finished. + * + * Besides updating the device shadow (handled in connection.c), this is the only additional + * code needed to get FOTA working properly, and its sole function is to reboot the microcontroller + * after FOTA download completes. + * + */ +void on_fota_downloaded(void); + +/** + * @brief Get the external flash device used for full modem FOTA updates. + * + * This function returns NULL if CONFIG_NRF_CLOUD_FOTA_FULL_MODEM_UPDATE is not + * enabled. + * + */ +struct dfu_target_fmfu_fdev *get_full_modem_fota_fdev(void); + +#endif /* _FOTA_SUPPORT_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/fota_support_coap.c b/samples/cellular/nrf_cloud_ble_gateway/src/fota_support_coap.c new file mode 100644 index 000000000000..bc54fb7593f7 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/fota_support_coap.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cloud_connection.h" +#include "fota_support.h" +#include "fota_support_coap.h" +#include "sample_reboot.h" + +LOG_MODULE_REGISTER(fota_support_coap, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +#define FOTA_THREAD_DELAY_S 10 + +static void fota_reboot(enum nrf_cloud_fota_reboot_status status); + +/* FOTA support context */ +static struct nrf_cloud_fota_poll_ctx ctx = { + .reboot_fn = fota_reboot +}; + +void fota_reboot(enum nrf_cloud_fota_reboot_status status) +{ + switch (status) { + case FOTA_REBOOT_REQUIRED: + sample_reboot_normal(); + break; + case FOTA_REBOOT_SUCCESS: + LOG_INF("Rebooting to complete FOTA update..."); + sample_reboot_normal(); + break; + case FOTA_REBOOT_FAIL: + case FOTA_REBOOT_SYS_ERROR: + default: + sample_reboot_error(); + break; + } +} + +int coap_fota_init(void) +{ + int err = nrf_cloud_fota_poll_init(&ctx); + + if (err) { + return err; + } + + /* Process pending FOTA job, the FOTA type is returned */ + err = nrf_cloud_fota_poll_process_pending(&ctx); + if (err < 0) { + return err; + } else if (err != NRF_CLOUD_FOTA_TYPE__INVALID) { + LOG_INF("Processed pending FOTA job type: %d", err); + } + + return 0; +} + +int coap_fota_thread_fn(void) +{ + int err; + + while (1) { + /* Wait until we are able to communicate. */ + LOG_DBG("Waiting for valid connection before processing FOTA"); + (void)await_cloud_ready(K_FOREVER); + + /* Query for any queued FOTA jobs. If one is found, download and install + * it. This is a blocking operation which can take a long time. + * This function is likely to reboot in order to complete the FOTA update. + */ + err = nrf_cloud_fota_poll_process(&ctx); + if (err == -EAGAIN) { + LOG_INF("Checking for FOTA job in %d seconds", + CONFIG_COAP_FOTA_JOB_CHECK_RATE_MINUTES * SEC_PER_MIN); + k_sleep(K_MINUTES(CONFIG_COAP_FOTA_JOB_CHECK_RATE_MINUTES)); + continue; + } + if (err == -ENOENT) { + cloud_transport_error_detected(); + } + k_sleep(K_SECONDS(FOTA_THREAD_DELAY_S)); + } + return 0; +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/fota_support_coap.h b/samples/cellular/nrf_cloud_ble_gateway/src/fota_support_coap.h new file mode 100644 index 000000000000..286f2dcf7721 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/fota_support_coap.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef FOTA_SUPPORT_COAP_H +#define FOTA_SUPPORT_COAP_H + +int coap_fota_init(void); +int coap_fota_thread_fn(void); + +#endif /* FOTA_SUPPORT_COAP_H */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/led_control.c b/samples/cellular/nrf_cloud_ble_gateway/src/led_control.c new file mode 100644 index 000000000000..536e2b23e9b5 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/led_control.c @@ -0,0 +1,296 @@ +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +#include +#include +#include +#include + +#include "led_control.h" + +/* We implement, here, both 4-led and RGB LED control, but you may feel free to implement + * whatever your board can support. You must make sure your board Device Tree Source or overlays + * include a device marked as compatible with either the pwm-leds or gpio-leds driver in order to + * use the LED driver provided by Zephyr, as we do here. Both the nRF9160dk, nRF9161dk and thingy91 + * have such devices defined in their default Device Tree Sources. See nrf9160dk_nrf9160_common.dts + * nrf9161dk_nrf9161_common.dts and thingy91_nrf9160_common.dts. + */ + +LOG_MODULE_REGISTER(led_control, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +/* Find a Device Tree node compatible with either the pwm_leds or gpio_leds driver, depending on + * what has been configured. + */ +#if defined(CONFIG_LED_INDICATION_PWM) +const static struct device *led_device = DEVICE_DT_GET_ANY(pwm_leds); +#elif defined(CONFIG_LED_INDICATION_GPIO) +const static struct device *led_device = DEVICE_DT_GET_ANY(gpio_leds); +#else +const static struct device *led_device; +#endif + +#define LED_RGB_R 0 +#define LED_RGB_G 1 +#define LED_RGB_B 2 + +#define LED_4LED_1 0 +#define LED_4LED_2 1 +#define LED_4LED_3 2 +#define LED_4LED_4 3 + +/** + * @brief If the LED mode is 4 binary LEDs, set the LED state + * + * @param l1 - LED 1 on? + * @param l2 - LED 2 on? + * @param l3 - LED 3 on? + * @param l4 - LED 4 on? + */ +static void led_set_4led(bool l1, bool l2, bool l3, bool l4) +{ + led_set_brightness(led_device, LED_4LED_1, l1 ? 100 : 0); + led_set_brightness(led_device, LED_4LED_2, l2 ? 100 : 0); + led_set_brightness(led_device, LED_4LED_3, l3 ? 100 : 0); + led_set_brightness(led_device, LED_4LED_4, l4 ? 100 : 0); +} + +/** + * @brief If the LED mode is RGB, set the RGB values. + * + * @param r - LED Red channel. + * @param g - LED Green channel. + * @param b - LED Blue channel. + */ +static void led_set_rgb(int r, int g, int b) +{ + led_set_brightness(led_device, LED_RGB_R, r); + led_set_brightness(led_device, LED_RGB_G, g); + led_set_brightness(led_device, LED_RGB_B, b); +} + +/** + * @brief Change the LEDs to a pattern that indicates success. + * + * @param frame_number - An integer used for basic animations. + */ +static void led_pattern_success(int frame_number) +{ + /* Square wave with period 12 frames and duty cycle 25% */ + bool blink = (frame_number % 12) < 3; + + if (IS_ENABLED(CONFIG_LED_INDICATOR_RGB)) { + /* Blink the green channel */ + led_set_rgb(0, blink ? 100 : 0, 0); + } else if (IS_ENABLED(CONFIG_LED_INDICATOR_4LED)) { + /* Blink alternating diagonals on a 4LED square */ + led_set_4led(blink, !blink, !blink, blink); + } +} + +/** + * @brief Change the LEDs to a pattern that indicates failure. + * + * @param frame_number - An integer used for basic animations. + */ +static void led_pattern_failure(int frame_number) +{ + /* Square wave with period 20 frames and duty cycle 75% */ + int blink = (frame_number % 20) < 15; + + if (IS_ENABLED(CONFIG_LED_INDICATOR_RGB)) { + /* Blink red channel */ + led_set_rgb(blink ? 100 : 0, 0, 0); + } else if (IS_ENABLED(CONFIG_LED_INDICATOR_4LED)) { + /* Blink all four LEDs */ + led_set_4led(blink, blink, blink, blink); + } +} + +/** + * @brief Change the LEDs to a pattern that indicates no relevant activity. + * + * @param frame_number - An integer used for basic animations. + */ +static void led_pattern_idle(int frame_number) +{ + if (IS_ENABLED(CONFIG_LED_INDICATOR_RGB)) { + /* A triangle wave between 0 and 20 */ + int breath = abs((frame_number % 40) - 20); + + /* Breathe back and forth between cyan and pure blue */ + led_set_rgb(0, breath, 20); + } else if (IS_ENABLED(CONFIG_LED_INDICATOR_4LED)) { + /* Square wave with period 40 frames and duty cycle 50% */ + bool blink = (frame_number % 40) < 20; + + /* Blink between two LEDs on opposite sides of a diagonal, of the 4LED square, + * namely "LED1" and "LED4" on 91 Series DK boards + */ + led_set_4led(blink, false, false, !blink); + } +} + +/** + * @brief Change the LEDs to a pattern that indicates waiting. + * + * @param frame_number - An integer used for basic animations. + */ +static void led_pattern_waiting(int frame_number) +{ + if (IS_ENABLED(CONFIG_LED_INDICATOR_RGB)) { + /* A triangle wave between 0 and 10 */ + int breath = abs((frame_number % 20) - 10); + + /* Pulsating orange */ + led_set_rgb(breath * 10, breath, 0); + } else if (IS_ENABLED(CONFIG_LED_INDICATOR_4LED)) { + /* Spin a single LED around the 4LED square */ + led_set_4led((frame_number % 4) == 0, + (frame_number % 4) == 1, + (frame_number % 4) == 3, + (frame_number % 4) == 2); + } +} + +/** + * @brief Disable the LEDs. + */ +static void led_pattern_disabled(void) +{ + if (IS_ENABLED(CONFIG_LED_INDICATOR_RGB)) { + led_set_rgb(0, 0, 0); + } else if (IS_ENABLED(CONFIG_LED_INDICATOR_4LED)) { + led_set_4led(false, false, false, false); + } +} + +/* Zephyr event for signaling cross-thread when an LED pattern has been requested */ +#define LED_PATTERN_REQUESTED (1 << 1) +static K_EVENT_DEFINE(led_events); + +/* The currently requested pattern, frames of it remaining, and total number of frames it has + * been executed for. Negative frames remaining indicates an indefinite effect. + */ +atomic_t requested_pattern = LED_DISABLED; +atomic_t requested_pattern_frames_remaining = -1; +atomic_t requested_pattern_animation_frame; + +void start_led_pattern(int frames, enum led_pattern pattern) +{ + if (!IS_ENABLED(CONFIG_LED_INDICATION_DISABLED)) { + atomic_set(&requested_pattern, pattern); + atomic_set(&requested_pattern_frames_remaining, frames); + atomic_set(&requested_pattern_animation_frame, 0); + k_event_post(&led_events, LED_PATTERN_REQUESTED); + LOG_DBG("LED Pattern Requested"); + } +} + +static K_TIMER_DEFINE(led_animate_timer, NULL, NULL); +/** + * @brief Display a single frame of a specified pattern + * + * @param frame_number - The frame number to show. + * @param led_pattern - The pattern to show. + */ +static void animate_leds_frame(int frame_number, enum led_pattern pattern) +{ + /* Wait for the timer from the previous call to this function to expire, and then + * immediately restart the timer. + * This guarantees that this function completes, at most, once every frame interval + */ + + k_timer_status_sync(&led_animate_timer); + k_timer_start(&led_animate_timer, K_MSEC(100), K_FOREVER); + + /* Run a single frame of the pattern */ + switch (pattern) { + case LED_IDLE: + led_pattern_idle(frame_number); + break; + case LED_WAITING: + led_pattern_waiting(frame_number); + break; + case LED_FAILURE: + led_pattern_failure(frame_number); + break; + case LED_SUCCESS: + led_pattern_success(frame_number); + break; + default: + led_pattern_disabled(); + } +} + +void led_animation_thread_fn(void) +{ + LOG_DBG("LED Management Started"); + + if (!device_is_ready(led_device)) { + LOG_ERR("No LED device found, cancelling LED indication."); + return; + } + + /* Self-request an initial LED update, using the default state, or otherwise keeping what + * any pre-existing thread already requested. + */ + k_event_post(&led_events, LED_PATTERN_REQUESTED); + + while (true) { + /* Wait for a pattern to be started */ + (void)k_event_wait(&led_events, LED_PATTERN_REQUESTED, false, K_FOREVER); + + LOG_DBG("LED Pattern Started"); + while (true) { + /* Decrement frames remaining, and exit if we hit zero, or if + * the requested pattern is LED_DISABLED. + * + * Negative values will indefinitely decrement without hitting zero. + */ + int pattern = atomic_get(&requested_pattern); + int fr = atomic_dec(&requested_pattern_frames_remaining); + + if (fr == 0 || pattern == LED_DISABLED) { + break; + } + + /* Perform an animation frame, incrementing the current animation frame + * counter. Note that LED_DISABLED will never be performed by this call, + * since that pattern causes us to break out of the loop above. + */ + animate_leds_frame(atomic_inc(&requested_pattern_animation_frame), pattern); + } + + /* The pattern has ended, clear the pattern event flag so that a new one can + * be requested. + */ + k_event_set(&led_events, 0); + + /* Either switch to the IDLE pattern, or disable LEDs without activating a new + * pattern, depending on whether continuous indication is requested. + */ + if (IS_ENABLED(CONFIG_LED_CONTINUOUS_INDICATION)) { + long_led_pattern(LED_IDLE); + } else { + animate_leds_frame(0, LED_DISABLED); + } + + LOG_DBG("LED Pattern Ended"); + } +} + +void long_led_pattern(enum led_pattern pattern) +{ + start_led_pattern(-1, pattern); +} + +void short_led_pattern(enum led_pattern pattern) +{ + start_led_pattern(30, pattern); +} + +void stop_led_pattern(void) +{ + short_led_pattern(LED_DISABLED); +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/led_control.h b/samples/cellular/nrf_cloud_ble_gateway/src/led_control.h new file mode 100644 index 000000000000..6506eaa6e1af --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/led_control.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _LED_CONTROL_H_ +#define _LED_CONTROL_H_ + + +enum led_pattern {LED_DISABLED, LED_IDLE, LED_WAITING, LED_FAILURE, LED_SUCCESS}; + +/** + * @brief Show an LED pattern for a specified number of animation frames, then go idle. + * + * @param frames - The number of frames for which the pattern should be visible. + - Negative if forever. + * @param pattern - The LED pattern to show. + */ +void start_led_pattern(int frames, enum led_pattern pattern); + +/** + * @brief Show an LED pattern for a few seconds, then go idle. + * + * @param pattern - The LED pattern to show + */ +void short_led_pattern(enum led_pattern pattern); + +/** + * @brief Show an LED pattern until further notice + * + * @param pattern - The LED pattern to show + */ +void long_led_pattern(enum led_pattern pattern); + +/** + * @brief Stop whatever LED pattern is currently being shown. + */ +void stop_led_pattern(void); + +/** + * @brief The LED animation thread function. + * Run the LED management/animation process. + */ +void led_animation_thread_fn(void); + +#endif /* _LED_CONTROL_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/location_tracking.c b/samples/cellular/nrf_cloud_ble_gateway/src/location_tracking.c new file mode 100644 index 000000000000..6bae9beb94d4 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/location_tracking.c @@ -0,0 +1,159 @@ +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + + +#include +#include +#include +#include +#include +#include + +#include "cloud_connection.h" +#include "location_tracking.h" + +LOG_MODULE_REGISTER(location_tracking, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +static location_update_cb_t location_update_handler; +static bool location_initialized; + +static void location_event_handler(const struct location_event_data *event_data) +{ + switch (event_data->id) { + case LOCATION_EVT_LOCATION: + LOG_DBG("Location Event: Got location"); + if (location_update_handler) { + /* Pass received location data along to our handler. */ + location_update_handler(event_data); + } + break; + + case LOCATION_EVT_TIMEOUT: + LOG_DBG("Location Event: Timed out"); + if (event_data->method != LOCATION_METHOD_GNSS) { + /* GNSS can timeout due to obstructed satellite signals. Other methods + * timeout when there is a communications error with the cloud. + */ + cloud_transport_error_detected(); + } + break; + + case LOCATION_EVT_ERROR: + LOG_DBG("Location Event: Error"); + break; + + case LOCATION_EVT_GNSS_ASSISTANCE_REQUEST: + LOG_DBG("Location Event: GNSS assistance requested"); + break; + + case LOCATION_EVT_STARTED: + break; + + default: + LOG_DBG("Location Event: Unknown event"); + break; + } +} + +static void enable_modem_gnss(void) +{ + if (IS_ENABLED(CONFIG_LTE_LINK_CONTROL)) { + int err = lte_lc_func_mode_set(LTE_LC_FUNC_MODE_ACTIVATE_GNSS); + + if (err) { + LOG_ERR("Activating GNSS failed, error: %d. Continuing without GNSS", err); + } + } else { + LOG_WRN("CONFIG_LTE_LINK_CONTROL must be enabled in order to use GNSS"); + } +} + +int start_location_tracking(location_update_cb_t handler_cb, int interval) +{ + int err; + + LOG_DBG("Starting location tracking"); + + if (!date_time_is_valid()) { + LOG_WRN("Date and time unknown. Location Services results may suffer"); + } + + /* Enable GNSS on the modem if appropriate */ + if (IS_ENABLED(CONFIG_LOCATION_TRACKING_GNSS)) { + enable_modem_gnss(); + } + + /* Update the location update handler. */ + location_update_handler = handler_cb; + + /* Initialize the Location Services Library. */ + err = location_init(location_event_handler); + if (err) { + LOG_ERR("Initializing the Location library failed, error: %d", err); + return err; + } + location_initialized = true; + + /* Construct a request for a periodic location report. */ + struct location_config config; + + /* Select methods based on configuration and load default settings */ + /* Methods will be attempted in the order they are listed here */ + enum location_method methods[] = { + IF_ENABLED(CONFIG_LOCATION_TRACKING_GNSS, (LOCATION_METHOD_GNSS,)) + IF_ENABLED(CONFIG_LOCATION_TRACKING_WIFI, (LOCATION_METHOD_WIFI,)) + IF_ENABLED(CONFIG_LOCATION_TRACKING_CELLULAR, (LOCATION_METHOD_CELLULAR,)) + }; + + if (IS_ENABLED(CONFIG_LOCATION_TRACKING_GNSS)) { + LOG_DBG("Selected the GNSS location tracking method."); + } + if (IS_ENABLED(CONFIG_LOCATION_TRACKING_WIFI)) { + LOG_DBG("Selected the Wi-Fi location tracking method."); + } + if (IS_ENABLED(CONFIG_LOCATION_TRACKING_CELLULAR)) { + LOG_DBG("Selected the Cellular location tracking method."); + } + if (ARRAY_SIZE(methods) == 0) { + LOG_DBG("No positioning methods selected"); + return 0; + } + + /* Load default settings accordingly */ + location_config_defaults_set(&config, ARRAY_SIZE(methods), methods); + + /* Set the mode to walk through all methods. This ensures all methods + * are tested and demonstrated. + * In a real product, the default is the better option to use, since the location + * library will try them in priority order. + */ + config.mode = LOCATION_REQ_MODE_ALL; + + /* Set the location report interval. */ + config.interval = interval; + + /* If GNSS location method is enabled, configure it + * (note: the GNSS location method is enabled by default in this sample) + */ + if (IS_ENABLED(CONFIG_LOCATION_TRACKING_GNSS)) { + /* Set the GNSS timeout and desired accuracy. + * The order of config.methods matches the order of methods, so GNSS will always be + * the first element of the array if enabled. + */ + config.methods[0].gnss.timeout = CONFIG_GNSS_FIX_TIMEOUT_SECONDS * MSEC_PER_SEC; + config.methods[0].gnss.accuracy = LOCATION_ACCURACY_NORMAL; + } + + /* Submit request for periodic location report. + * This will cause the configured location_event_handler to start being called with + * location data. + */ + err = location_request(&config); + if (err) { + LOG_ERR("Requesting location failed, error: %d\n", err); + return err; + } + return 0; +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/location_tracking.h b/samples/cellular/nrf_cloud_ble_gateway/src/location_tracking.h new file mode 100644 index 000000000000..e0bb61e707d4 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/location_tracking.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _LOCATION_TRACKING_H_ +#define _LOCATION_TRACKING_H_ + +/* Definition found in location.h */ +struct location_event_data; + +/** + * @brief Callback to receive tracked locations. + * + * @param[in] location_data The tracked location data. + */ +typedef void (*location_update_cb_t)(const struct location_event_data * const location_data); + +/** + * @brief Start tracking our location at the given interval in seconds. + * + * @param handler_cb - Handler callback to receive location updates. + * @param interval - The interval, in seconds, at which to track our location. + */ +int start_location_tracking(location_update_cb_t handler_cb, int interval); + +/** + * @brief Check whether one of the location tracking methods is enabled + * + * @return bool - Whether location tracking of any form is enabled + */ +static inline bool location_tracking_enabled(void) +{ + return IS_ENABLED(CONFIG_LOCATION_TRACKING_GNSS) || + IS_ENABLED(CONFIG_LOCATION_TRACKING_CELLULAR) || + IS_ENABLED(CONFIG_LOCATION_TRACKING_WIFI); +} + + +#endif /* _LOCATION_TRACKING_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/main.c b/samples/cellular/nrf_cloud_ble_gateway/src/main.c new file mode 100644 index 000000000000..b17f4f7d5d62 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/main.c @@ -0,0 +1,70 @@ +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include "application.h" +#include "cloud_connection.h" +#include "message_queue.h" +#include "led_control.h" +#include "fota_support_coap.h" +#include "shadow_support_coap.h" + +LOG_MODULE_REGISTER(main, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +/* Here, we start the various threads that our application will run in */ + +#ifndef CONFIG_LED_INDICATION_DISABLED +/* Define, and automatically start the LED animation thread. See led_control.c */ +K_THREAD_DEFINE(led_thread, CONFIG_LED_THREAD_STACK_SIZE, led_animation_thread_fn, + NULL, NULL, NULL, 0, 0, 0); +#endif + +/* Define, and automatically start the main application thread. See application.c */ +K_THREAD_DEFINE(app_thread, CONFIG_APPLICATION_THREAD_STACK_SIZE, main_application_thread_fn, + NULL, NULL, NULL, 0, 0, 0); + +/* Define, and automatically start the message queue thread. See message_queue.c */ +K_THREAD_DEFINE(msg_thread, CONFIG_MESSAGE_THREAD_STACK_SIZE, message_queue_thread_fn, + NULL, NULL, NULL, 0, 0, 0); + +/* Define, and automatically start the cloud connection thread. See cloud_connection.c */ +K_THREAD_DEFINE(con_thread, CONFIG_CONNECTION_THREAD_STACK_SIZE, cloud_connection_thread_fn, + NULL, NULL, NULL, 0, 0, 0); + +#if defined(CONFIG_NRF_CLOUD_COAP) +#if defined(CONFIG_COAP_FOTA) +/* Define, and automatically start the CoAP FOTA check thread. See fota_support_coap.c */ +K_THREAD_DEFINE(coap_fota, CONFIG_COAP_FOTA_THREAD_STACK_SIZE, coap_fota_thread_fn, + NULL, NULL, NULL, 0, 0, 0); +#endif + +#if defined(CONFIG_COAP_SHADOW) +/* Define, and automatically start the CoAP shadow check thread. See shadow_support_coap.c */ +K_THREAD_DEFINE(coap_shadow, CONFIG_COAP_SHADOW_THREAD_STACK_SIZE, coap_shadow_thread_fn, + NULL, NULL, NULL, 0, 0, 0); +#endif +#endif /* CONFIG_NRF_CLOUD_COAP */ + +/* main() is called from the main thread, which defaults to priority zero, + * but for illustrative purposes we don't use it. main_application() could be called directly + * from this function, rather than given its own dedicated thread. + */ +int main(void) +{ + const char *protocol; + + if (IS_ENABLED(CONFIG_NRF_CLOUD_MQTT)) { + protocol = "MQTT"; + } else if (IS_ENABLED(CONFIG_NRF_CLOUD_COAP)) { + protocol = "CoAP"; + } + + LOG_INF("nRF Cloud BLE gateway sample has started, version: %s, protocol: %s", + CONFIG_APP_VERSION, protocol); + + return 0; +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/message_queue.c b/samples/cellular/nrf_cloud_ble_gateway/src/message_queue.c new file mode 100644 index 000000000000..45679b260f57 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/message_queue.c @@ -0,0 +1,217 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#if defined(CONFIG_NRF_CLOUD_COAP) +#include +#endif +#include +#include +#include + +#include "message_queue.h" +#include "cloud_connection.h" + +#include "led_control.h" + +LOG_MODULE_REGISTER(message_queue, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +/* Message Queue for enqueuing outgoing messages during offline periods. */ +K_MSGQ_DEFINE(device_message_queue, + sizeof(struct nrf_cloud_obj *), + CONFIG_MAX_OUTGOING_MESSAGES, + sizeof(struct nrf_cloud_obj *)); + +/* Tracks the number of consecutive message-send failures. A total count greater than + * CONFIG_MAX_CONSECUTIVE_SEND_FAILURES will trigger a connection reset and cooldown. + * Resets on every successful device message send. + */ +static int send_failure_count; + +static struct nrf_cloud_obj *allocate_dev_msg_for_queue(struct nrf_cloud_obj *msg_to_copy) +{ + struct nrf_cloud_obj *new_msg = k_malloc(sizeof(struct nrf_cloud_obj)); + + if (new_msg && msg_to_copy) { + *new_msg = *msg_to_copy; + } + + return new_msg; +} + +static int enqueue_device_message(struct nrf_cloud_obj *const msg_obj, const bool create_copy) +{ + if (!msg_obj) { + return -EINVAL; + } + + struct nrf_cloud_obj *q_msg = msg_obj; + uint32_t msgs_in_q = k_msgq_num_used_get(&device_message_queue); + + LOG_DBG("Messages queue status: %u/%u", msgs_in_q, CONFIG_MAX_OUTGOING_MESSAGES); + + if (msgs_in_q == CONFIG_MAX_OUTGOING_MESSAGES) { + LOG_WRN("Message queue is full"); + return -ENOSPC; + } + + if (create_copy) { + /* Allocate a new nrf_cloud_obj structure for the message queue. + * Copy the contents of msg_obj, which contains a pointer to the + * original message data, into the new structure. + */ + q_msg = allocate_dev_msg_for_queue(msg_obj); + if (!q_msg) { + return -ENOMEM; + } + } + + /* Attempt to append data onto message queue. */ + LOG_DBG("Adding device message to queue"); + if (k_msgq_put(&device_message_queue, &q_msg, K_NO_WAIT)) { + LOG_ERR("Device message rejected, outgoing message queue is full"); + if (create_copy) { + k_free(q_msg); + } + return -ENOMEM; + } + + return 0; +} + +static void free_queued_dev_msg_message(struct nrf_cloud_obj *msg_obj) +{ + /* Free the memory pointed to by the msg_obj struct */ + nrf_cloud_obj_free(msg_obj); + /* Free the msg_obj struct itself */ + k_free(msg_obj); +} + +/** + * @brief Consume (attempt to send) a single device message from the device message queue. + * Waits for nRF Cloud readiness before sending each message. + * If the message fails to send, it will be reenqueued. + * + * @return int - 0 on success, otherwise negative error code. + */ +static int consume_device_message(void) +{ + struct nrf_cloud_obj *queued_msg; + int ret; + + LOG_DBG("Consuming an enqueued device message"); + + /* Wait until a message is available to send. */ + ret = k_msgq_get(&device_message_queue, &queued_msg, K_FOREVER); + if (ret) { + LOG_ERR("Failed to retrieve item from outgoing message queue, error: %d", ret); + return -ret; + } + + /* Wait until we are able to send it. */ + LOG_DBG("Waiting for valid connection before transmitting device message"); + (void)await_cloud_ready(K_FOREVER); + + /* Attempt to send it. + * + * Note, it is possible (and better) to batch-send device messages when more than one is + * queued up. We limit this sample to sending individual messages mainly to keep the sample + * simple and accessible. See the Asset Tracker V2 application for an example of batch + * message sending. + */ + LOG_DBG("Attempting to transmit enqueued device message"); + LOG_DBG("Messages remaining in queue: %u", k_msgq_num_used_get(&device_message_queue)); + +#if defined(CONFIG_NRF_CLOUD_MQTT) + struct nrf_cloud_tx_data mqtt_msg = { + .qos = MQTT_QOS_1_AT_LEAST_ONCE, + .topic_type = NRF_CLOUD_TOPIC_MESSAGE, + .obj = queued_msg + }; + + /* Send message */ + ret = nrf_cloud_send(&mqtt_msg); + +#elif defined(CONFIG_NRF_CLOUD_COAP) + ret = nrf_cloud_coap_obj_send(queued_msg, IS_ENABLED(CONFIG_COAP_SEND_CONFIRMABLE)); + +#endif /* CONFIG_NRF_CLOUD_COAP */ + + if (ret < 0) { + LOG_ERR("Transmission of enqueued device message failed, nrf_cloud_send " + "gave error: %d. The message will be re-enqueued and tried again " + "later.", ret); + + /* Re-enqueue the message for later retry. + * No need to create a copy since we already copied the + * message object struct when it was first enqueued. + */ + ret = enqueue_device_message(queued_msg, false); + if (ret) { + LOG_ERR("Could not re-enqueue message, discarding."); + free_queued_dev_msg_message(queued_msg); + } + + /* Increment the failure counter. */ + send_failure_count += 1; + + /* If we have failed too many times in a row, there is likely a bigger problem, + * and we should reset our connection to nRF Cloud, and wait for a few seconds. + */ + if (send_failure_count > CONFIG_MAX_CONSECUTIVE_SEND_FAILURES) { + /* Disconnect. */ + disconnect_cloud(); + + /* Wait for a few seconds before trying again. */ + k_sleep(K_SECONDS(CONFIG_CONSECUTIVE_SEND_FAILURE_COOLDOWN_SECONDS)); + } + } else { + /* Clean up the message receive from the queue */ + free_queued_dev_msg_message(queued_msg); + + LOG_DBG("Enqueued device message consumed successfully"); + + /* Either overwrite the existing pattern with a short success pattern, or just + * disable the previously requested pattern, depending on if we are in verbose mode. + */ + if (IS_ENABLED(CONFIG_LED_VERBOSE_INDICATION)) { + short_led_pattern(LED_SUCCESS); + } else { + stop_led_pattern(); + } + + /* Reset the failure counter, since we succeeded. */ + send_failure_count = 0; + } + + return ret; +} + +int send_device_message(struct nrf_cloud_obj *const msg_obj) +{ + /* Enqueue the message, creating a copy to be managed by the queue. */ + int ret = enqueue_device_message(msg_obj, true); + + if (ret) { + LOG_ERR("Cannot add message to queue"); + nrf_cloud_obj_free(msg_obj); + } else { + /* The message data now belongs to the queue. + * Reset the provided object so it cannot be modified. + */ + nrf_cloud_obj_reset(msg_obj); + } + + return ret; +} + +void message_queue_thread_fn(void) +{ + /* Continually attempt to consume device messages */ + while (true) { + (void) consume_device_message(); + } +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/message_queue.h b/samples/cellular/nrf_cloud_ble_gateway/src/message_queue.h new file mode 100644 index 000000000000..f4498aac1a44 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/message_queue.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _MESSAGE_QUEUE_H_ +#define _MESSAGE_QUEUE_H_ +#include + +/** + * @brief Schedule a cloud object to be sent as a device message payload. The message will + * be held asynchronously until a valid nRF Cloud connection is established. + * Caller is no longer responsible for device message memory after function returns. + * @return int - 0 on success, otherwise negative error. + */ +int send_device_message(struct nrf_cloud_obj *const msg_obj); + +/** + * @brief The message queue thread function. + * Continually consumes device messages from the device message queue. + */ +void message_queue_thread_fn(void); + +#endif /* _MESSAGE_QUEUE_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/provisioning_support.c b/samples/cellular/nrf_cloud_ble_gateway/src/provisioning_support.c new file mode 100644 index 000000000000..48f812cc11db --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/provisioning_support.c @@ -0,0 +1,213 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include + +#include "cloud_connection.h" +#include "sample_reboot.h" + +LOG_MODULE_REGISTER(cloud_provisioning, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +#define NETWORK_UP BIT(0) +#define NETWORK_DOWN BIT(1) +#define PROVISIONING_IDLE BIT(2) + +static K_EVENT_DEFINE(prov_events); + +static bool provisioning_started; + +/* Called by the provisioning library to request the LTE modem be taken offline before credential + * installation, and then again after credential installation is complete. This is necessary because + * credentials cannot be installed to the modem while it is online. + * + * This limitation only applies to the nrf91 modem; Wi-Fi, for instance, is unaffected. But for + * simplicity we use conn_mgr to take take all network interfaces offline and online. + */ +static int modem_mode_cb(enum lte_lc_func_mode new_mode, void *user_data) +{ + enum lte_lc_func_mode fmode; + + ARG_UNUSED(user_data); + + /* The nrf_provisioning library requires us to return the previous functional mode. */ + if (lte_lc_func_mode_get(&fmode)) { + LOG_ERR("Failed to read modem functional mode"); + return -EFAULT; + } + + if (new_mode == LTE_LC_FUNC_MODE_NORMAL || new_mode == LTE_LC_FUNC_MODE_ACTIVATE_LTE) { + LOG_INF("Provisioning library requests normal mode"); + + /* We are done installing credentials, reactivate GNSS and network */ + + /* Reactivate GNSS */ + lte_lc_func_mode_set(LTE_LC_FUNC_MODE_ACTIVATE_GNSS); + + /* Reactivate LTE */ + conn_mgr_all_if_connect(true); + + /* Wait for network readiness to be re-established before returning. */ + LOG_DBG("Waiting for network up"); + k_event_wait(&prov_events, NETWORK_UP, false, K_FOREVER); + + LOG_DBG("Network is up."); + } else if (new_mode == LTE_LC_FUNC_MODE_OFFLINE || + new_mode == LTE_LC_FUNC_MODE_DEACTIVATE_LTE) { + LOG_INF("Provisioning library requests offline mode"); + + /* The provisioning library wants to install or generate credentials. + * Deactivate LTE and GNSS to allow this. + */ + + /* Shut down LTE */ + conn_mgr_all_if_disconnect(true); + + /* Shut down GNSS */ + lte_lc_func_mode_set(LTE_LC_FUNC_MODE_DEACTIVATE_GNSS); + + /* Note: + * You could shut down both LTE and GNSS at once by using + * lte_lc_func_mode_set(LTE_LC_FUNC_MODE_OFFLINE); + * But conn_mgr will interpret this as an unintended connectivity loss. + * Using conn_mgr_all_if_disconnect lets conn_mgr know the LTE loss is intentional. + * + * lte_lc_func_mode_set(LTE_LC_FUNC_MODE_DEACTIVATE_GNSS) can then be used to shut + * down GNSS, since conn_mgr does not interfere with GNSS state. + */ + + /* Wait for network disconnection before returning. */ + LOG_DBG("Waiting for network down."); + k_event_wait(&prov_events, NETWORK_DOWN, false, K_FOREVER); + + LOG_DBG("Network is down."); + } + + return fmode; +} + +/* Delayable work item for marking provisioning as idle. */ +static void mark_provisioning_idle_work_fn(struct k_work *work) +{ + ARG_UNUSED(work); + + LOG_INF("Provisioning is idle."); + k_event_post(&prov_events, PROVISIONING_IDLE); +} + +static K_WORK_DELAYABLE_DEFINE(provisioning_idle_work, mark_provisioning_idle_work_fn); + +/* Called by the provisioning library when each provisioning attempt starts, stops, or finishes + * with reboot required. + */ +static void device_mode_cb(enum nrf_provisioning_event event, void *user_data) +{ + ARG_UNUSED(user_data); + + switch (event) { + case NRF_PROVISIONING_EVENT_START: + /* Called when the provisioning library begins checking for provisioning commands. + */ + LOG_DBG("NRF_PROVISIONING_EVENT_START"); + + /* Mark provisioning as active. */ + LOG_INF("Provisioning is active."); + k_event_clear(&prov_events, PROVISIONING_IDLE); + break; + case NRF_PROVISIONING_EVENT_STOP: + /* Called when the current provisioning command check has completed. */ + LOG_DBG("NRF_PROVISIONING_EVENT_STOP"); + + /* Mark provisioning as idle after a small delay. + * This delay is to prevent false starts if the provisioning library performs an + * immediate retry. + */ + k_work_reschedule(&provisioning_idle_work, K_SECONDS(5)); + break; + case NRF_PROVISIONING_EVENT_DONE: + /* Called when there are no further provisioning commands, + * and a reboot is needed. + */ + LOG_DBG("NRF_PROVISIONING_EVENT_DONE"); + + LOG_INF("Provisioning completed."); + sample_reboot_normal(); + break; + default: + LOG_ERR("Unknown event: %d", event); + break; + } +} + +static struct nrf_provisioning_mm_change mmode = { .cb = modem_mode_cb }; +static struct nrf_provisioning_dm_change dmode = { .cb = device_mode_cb }; + +/* Work item to initialize the provisioning library and start checking for provisioning commands. + * Called automatically the first time network connectivity is established. + * Needs to be a work item since nrf_provisioning_init may attempt to install certs in a blocking + * fashion. + */ +static void start_provisioning_work_fn(struct k_work *work) +{ + LOG_INF("Initializing the nRF Provisioning library..."); + + int ret = nrf_provisioning_init(&mmode, &dmode); + + if (ret) { + LOG_ERR("Failed to initialize provisioning client, error: %d", ret); + } +} + +static K_WORK_DEFINE(start_provisioning_work, start_provisioning_work_fn); + +/* Callback to track network connectivity */ +static struct net_mgmt_event_callback l4_callback; + +static void l4_event_handler(struct net_mgmt_event_callback *cb, + uint32_t event, struct net_if *iface) +{ + if (event == NET_EVENT_L4_CONNECTED) { + /* Mark network as up. */ + k_event_clear(&prov_events, NETWORK_DOWN); + k_event_post(&prov_events, NETWORK_UP); + + /* Start the provisioning library after network readiness is first established. + * We offload this to a workqueue item to avoid a deadlock. + * (nrf_provisioning_init might attempt to install certs, and in the process, + * trigger a blocking wait for L4_DOWN, which cannot fire until this handler exits.) + */ + if (!provisioning_started) { + k_work_submit(&start_provisioning_work); + provisioning_started = true; + } + } else if (event == NET_EVENT_L4_DISCONNECTED) { + /* Mark network as down. */ + k_event_clear(&prov_events, NETWORK_UP); + k_event_post(&prov_events, NETWORK_DOWN); + } +} + +/* Set up any requirements for provisioning on boot */ +static int prepare_provisioning(void) +{ + /* Start tracking network availability */ + net_mgmt_init_event_callback( + &l4_callback, l4_event_handler, NET_EVENT_L4_CONNECTED | NET_EVENT_L4_DISCONNECTED + ); + net_mgmt_add_event_callback(&l4_callback); + return 0; +} + +SYS_INIT(prepare_provisioning, APPLICATION, 0); + +bool await_provisioning_idle(k_timeout_t timeout) +{ + return k_event_wait(&prov_events, PROVISIONING_IDLE, false, timeout) != 0; +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/provisioning_support.h b/samples/cellular/nrf_cloud_ble_gateway/src/provisioning_support.h new file mode 100644 index 000000000000..8efcde941930 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/provisioning_support.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _SAMPLE_REBOOT_H_ +#define _SAMPLE_REBOOT_H_ + +#include + +/** + * @brief Sleep until the provisioning library is idle. + * + * @param timeout - The time to wait before timing out. + * @return true if the provisioning library became idle in time. + * @return false if timed out. + */ +#if defined(CONFIG_NRF_PROVISIONING) + +bool await_provisioning_idle(k_timeout_t timeout); + +#else /*CONFIG_NRF_PROVISIONING*/ + +static inline bool await_provisioning_idle(k_timeout_t timeout) +{ + return true; +} + +#endif /*CONFIG_NRF_PROVISIONING*/ + +#endif /* _SAMPLE_REBOOT_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/sample_reboot.c b/samples/cellular/nrf_cloud_ble_gateway/src/sample_reboot.c new file mode 100644 index 000000000000..f15b7ba75e61 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/sample_reboot.c @@ -0,0 +1,38 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include "sample_reboot.h" + +#define ERROR_REBOOT_S 30 +#define NORMAL_REBOOT_S 10 + +LOG_MODULE_REGISTER(sample_reboot, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +/* Called by various subsystems in the sample when a clean reboot is needed. */ +void sample_reboot(const bool error) +{ + unsigned int delay_s = error ? ERROR_REBOOT_S : NORMAL_REBOOT_S; + + LOG_INF("Rebooting in %us%s", delay_s, error ? " due to error" : "..."); + +#if defined(CONFIG_LTE_LINK_CONTROL) + if (error) { + /* We must do this before we reboot, otherwise we might trip LTE boot loop + * prevention, and the modem will be temporarily disable itself. + */ + (void)lte_lc_power_off(); + } +#endif + + LOG_PANIC(); + k_sleep(K_SECONDS(delay_s)); + sys_reboot(SYS_REBOOT_COLD); +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/sample_reboot.h b/samples/cellular/nrf_cloud_ble_gateway/src/sample_reboot.h new file mode 100644 index 000000000000..61b18844e506 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/sample_reboot.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _SAMPLE_REBOOT_H_ +#define _SAMPLE_REBOOT_H_ + +#include + +/** + * @brief Safely reboot sample. + * + * @param error - Is the reboot due to an error? + */ +void sample_reboot(const bool error); + +static inline void sample_reboot_error(void) +{ + sample_reboot(true); +} + +static inline void sample_reboot_normal(void) +{ + sample_reboot(false); +} + +#endif /* _SAMPLE_REBOOT_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/shadow_config.c b/samples/cellular/nrf_cloud_ble_gateway/src/shadow_config.c new file mode 100644 index 000000000000..b26a1c8b8ae9 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/shadow_config.c @@ -0,0 +1,198 @@ +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#include "shadow_config.h" +#if defined(CONFIG_NRF_CLOUD_COAP) +#include "shadow_support_coap.h" +#endif +#include "application.h" + +LOG_MODULE_REGISTER(shadow_config, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +#define TEST_COUNTER_EN "counterEnable" + +/* Flag to indicate that the accepted shadow data has been received (MQTT only) */ +static bool accepted_rcvd; + +static int add_cfg_data(struct nrf_cloud_obj *const cfg_obj) +{ + /* Add the test counter state */ + return nrf_cloud_obj_bool_add(cfg_obj, TEST_COUNTER_EN, test_counter_enable_get(), false); +} + +static int process_cfg(struct nrf_cloud_obj *const cfg_obj) +{ + bool val; + int err = nrf_cloud_obj_bool_get(cfg_obj, TEST_COUNTER_EN, &val); + + if (err == 0) { + /* The expected key/value was found, set the test counter enable state */ + test_counter_enable_set(val); + } else if (err == -ENOMSG) { + /* The key was found, but the value was not a boolean */ + LOG_WRN("Invalid configuration value"); + /* Reject the config */ + err = -EBADF; + } else { + LOG_DBG("Expected data not found in config object"); + err = -ENOMSG; + } + + return err; +} + +static int send_config(void) +{ + int err; + + NRF_CLOUD_OBJ_JSON_DEFINE(root_obj); + NRF_CLOUD_OBJ_JSON_DEFINE(state_obj); + NRF_CLOUD_OBJ_JSON_DEFINE(reported_obj); + NRF_CLOUD_OBJ_JSON_DEFINE(cfg_obj); + + if (nrf_cloud_obj_init(&cfg_obj) || nrf_cloud_obj_init(&reported_obj) || + nrf_cloud_obj_init(&state_obj) || nrf_cloud_obj_init(&root_obj)) { + err = -ENOMEM; + goto cleanup; + } + + /* Add the supported configuration data */ + err = add_cfg_data(&cfg_obj); + if (err) { + goto cleanup; + } + +#if defined(CONFIG_NRF_CLOUD_MQTT) + + /* Add config to reported */ + err = nrf_cloud_obj_object_add(&reported_obj, NRF_CLOUD_JSON_KEY_CFG, &cfg_obj, false); + if (err) { + goto cleanup; + } + + /* Add reported to state */ + err = nrf_cloud_obj_object_add(&state_obj, NRF_CLOUD_JSON_KEY_REP, &reported_obj, false); + if (err) { + goto cleanup; + } + + /* Add state to the root object */ + err = nrf_cloud_obj_object_add(&root_obj, NRF_CLOUD_JSON_KEY_STATE, &state_obj, false); + if (err) { + goto cleanup; + } + + /* Send to the cloud */ + err = nrf_cloud_obj_shadow_update(&root_obj); +#else /* CONFIG_NRF_CLOUD_COAP */ + + /* Add config to root */ + err = nrf_cloud_obj_object_add(&root_obj, NRF_CLOUD_JSON_KEY_CFG, &cfg_obj, false); + if (err) { + goto cleanup; + } + + err = shadow_support_coap_obj_send(&root_obj, false); +#endif + +cleanup: + nrf_cloud_obj_free(&root_obj); + nrf_cloud_obj_free(&state_obj); + nrf_cloud_obj_free(&reported_obj); + nrf_cloud_obj_free(&cfg_obj); + return err; +} + +void shadow_config_cloud_connected(void) +{ + accepted_rcvd = false; +} + +int shadow_config_reported_send(void) +{ + LOG_INF("Sending reported configuration"); + + int err = send_config(); + + if (err) { + LOG_ERR("Failed to send configuration, error: %d", err); + } + + return err; +} + +int shadow_config_delta_process(struct nrf_cloud_obj *const delta_obj) +{ + if (!delta_obj) { + return -EINVAL; + } + + if ((delta_obj->type != NRF_CLOUD_OBJ_TYPE_JSON) || !delta_obj->json) { + /* No state JSON */ + return -ENOMSG; + } + + /* If there is a pending delta event when the device establishes a cloud connection + * it is possible that it will be received before the accepted shadow data. + * Do not process a delta event until the accepted shadow data has been received. + * This is only a concern for MQTT. + */ + if (!accepted_rcvd && IS_ENABLED(CONFIG_NRF_CLOUD_MQTT)) { + return -EAGAIN; + } + + int err; + + NRF_CLOUD_OBJ_JSON_DEFINE(cfg_obj); + + /* Get the config object */ + err = nrf_cloud_obj_object_detach(delta_obj, NRF_CLOUD_JSON_KEY_CFG, &cfg_obj); + if (err == -ENODEV) { + /* No config in the delta */ + return -ENOMSG; + } + + /* Process the configuration */ + err = process_cfg(&cfg_obj); + + if (err == -EBADF) { + /* Clear incoming config and replace it with a good one */ + nrf_cloud_obj_free(&cfg_obj); + nrf_cloud_obj_init(&cfg_obj); + if (add_cfg_data(&cfg_obj)) { + LOG_ERR("Failed to create delta response"); + } + } + + /* Add the config object back into the state so the response can be sent */ + if (nrf_cloud_obj_object_add(delta_obj, NRF_CLOUD_JSON_KEY_CFG, &cfg_obj, false)) { + nrf_cloud_obj_free(&cfg_obj); + return -ENOMEM; + } + + return err; +} + +int shadow_config_accepted_process(struct nrf_cloud_obj *const accepted_obj) +{ + if (!accepted_obj) { + return -EINVAL; + } + + /* The accepted shadow has been received */ + accepted_rcvd = true; + + if ((accepted_obj->type != NRF_CLOUD_OBJ_TYPE_JSON) || !accepted_obj->json) { + /* No config JSON */ + return -ENOMSG; + } + + return process_cfg(accepted_obj); +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/shadow_config.h b/samples/cellular/nrf_cloud_ble_gateway/src/shadow_config.h new file mode 100644 index 000000000000..5ef2daef51d1 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/shadow_config.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _SHADOW_CONFIG_H_ +#define _SHADOW_CONFIG_H_ + +#include +#include + + +/** + * @brief For MQTT, this function should be called when the NRF_CLOUD_EVT_TRANSPORT_CONNECTED + * event is received. + */ +void shadow_config_cloud_connected(void); + +/** + * @brief Send the reported device configuration. + * + * @return 0 on success, otherwise negative on error. + */ +int shadow_config_reported_send(void); + +/** + * @brief Process an incoming delta event. + * + * @retval 0 Success, accept the delta. + * @retval -EBADF Invalid config data, reject the delta. + * @retval -ENOMSG The provided data did not contain JSON or a config section. + * @retval -EAGAIN Ignore delta event until the accepted shadow is received. MQTT only. + * @retval -EINVAL Error; Invalid parameter. + * @retval -ENOMEM Error; out of memory. + */ +int shadow_config_delta_process(struct nrf_cloud_obj *const delta_obj); + +/** + * @brief Process an incoming accepted shadow event. + * + * @retval 0 Success. + * @retval -EBADF Invalid config data. + * @retval -ENOMSG The provided data did not contain JSON or the expected config data. + * @retval -EINVAL Error; Invalid parameter. + * @return A negative value indicates an error, the device should send its current config to + * the reported section of the shadow. + */ +int shadow_config_accepted_process(struct nrf_cloud_obj *const accepted_obj); + +#endif /* _SHADOW_CONFIG_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/shadow_support_coap.c b/samples/cellular/nrf_cloud_ble_gateway/src/shadow_support_coap.c new file mode 100644 index 000000000000..745bb12d9854 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/shadow_support_coap.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cloud_connection.h" +#include "shadow_support_coap.h" +#include "shadow_config.h" + +LOG_MODULE_REGISTER(shadow_support_coap, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +#define COAP_SHADOW_MAX_SIZE 512 + +static int process_delta(struct nrf_cloud_data *const delta) +{ + if (delta->len == 0) { + return -ENODATA; + } + + int err; + bool update_desired = false; + struct nrf_cloud_obj delta_obj = {0}; + + LOG_DBG("Shadow delta: len:%zd, %s", delta->len, (const char *)delta->ptr); + + err = nrf_cloud_coap_shadow_delta_process(delta, &delta_obj); + if (err < 0) { + LOG_ERR("Failed to process shadow delta, err: %d", err); + return -EIO; + } else if (err == 0) { + /* No application specific delta data */ + return -ENODATA; + } + + /* There is an application specific shadow delta to process. + * This application only needs to deal with the configuration section. + */ + err = shadow_config_delta_process(&delta_obj); + if (err == -EBADF) { + LOG_INF("Rejecting shadow delta"); + update_desired = true; + } else if (err == -ENOMEM) { + LOG_ERR("Error handling shadow delta"); + return err; + } + + if (delta_obj.type == NRF_CLOUD_OBJ_TYPE_JSON) { + err = gateway_state_handler(delta_obj.json); + } + + /* Reject the delta by updating desired. + * Accept the delta by updating reported. + */ + err = shadow_support_coap_obj_send(&delta_obj, update_desired); + + if (err) { + LOG_ERR("Failed to send shadow delta update, err: %d", err); + err = -EFAULT; + } + + return err; +} + +static int check_shadow(void) +{ + /* Ensure the config is sent once */ + static bool config_sent; + + int err; + char buf[COAP_SHADOW_MAX_SIZE] = {0}; + size_t length = sizeof(buf); + struct nrf_cloud_data in_data = { + .ptr = buf + }; + + LOG_INF("Checking for shadow delta..."); + + err = nrf_cloud_coap_shadow_get(buf, &length, true, COAP_CONTENT_FORMAT_APP_JSON); + if (err == -EACCES) { + LOG_DBG("Not connected yet."); + return err; + } else if (err) { + LOG_ERR("Failed to request shadow delta: %d", err); + return err; + } + + in_data.len = length; + + err = process_delta(&in_data); + if (err == -ENODATA) { + /* There was no application specific delta data to process. + * Return -EAGAIN so the thread sleeps for a longer duration. + */ + err = -EAGAIN; + } + + if (!config_sent) { + /* If the config needs to be sent, return so that the thread sleeps + * for a shorter duration. + */ + if (shadow_config_reported_send() == 0) { + config_sent = true; + return 0; + } else { + return -EIO; + } + } + + return err; +} + +int shadow_support_coap_obj_send(struct nrf_cloud_obj *const shadow_obj, const bool desired) +{ + /* Encode the data for the cloud */ + int err = nrf_cloud_obj_cloud_encode(shadow_obj); + + /* Free the object */ + (void)nrf_cloud_obj_free(shadow_obj); + + if (!err) { + /* Send the encoded data */ + if (desired) { + err = nrf_cloud_coap_shadow_desired_update(shadow_obj->encoded_data.ptr); + } else { + err = nrf_cloud_coap_shadow_state_update(shadow_obj->encoded_data.ptr); + } + } else { + LOG_ERR("Failed to encode cloud data, err: %d", err); + return err; + } + + /* Free the encoded data */ + (void)nrf_cloud_obj_cloud_encoded_free(shadow_obj); + + return err; +} + +#define SHADOW_THREAD_DELAY_S 10 + +int coap_shadow_thread_fn(void) +{ + int err; + + while (1) { + /* Wait until we are able to communicate. */ + if (!await_cloud_ready(K_NO_WAIT)) { + LOG_DBG("Waiting for valid connection before checking shadow"); + (void)await_cloud_ready(K_FOREVER); + } + + err = check_shadow(); + if (err == -EAGAIN) { + LOG_INF("Checking shadow again in %d seconds", + CONFIG_COAP_SHADOW_CHECK_RATE_SECONDS); + k_sleep(K_SECONDS(CONFIG_COAP_SHADOW_CHECK_RATE_SECONDS)); + continue; + } + if (err == -ETIMEDOUT) { + cloud_transport_error_detected(); + } + LOG_DBG("check_shadow() returned %d", err); + k_sleep(K_SECONDS(SHADOW_THREAD_DELAY_S)); + } + return 0; +} diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/shadow_support_coap.h b/samples/cellular/nrf_cloud_ble_gateway/src/shadow_support_coap.h new file mode 100644 index 000000000000..3ff7eab398e3 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/shadow_support_coap.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef SHADOW_SUPPORT_COAP_H +#define SHADOW_SUPPORT_COAP_H + +int shadow_support_coap_obj_send(struct nrf_cloud_obj *const shadow_obj, const bool desired); + +int coap_shadow_thread_fn(void); + +#endif /* SHADOW_SUPPORT_COAP_H */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/temperature.c b/samples/cellular/nrf_cloud_ble_gateway/src/temperature.c new file mode 100644 index 000000000000..2b4939e4a781 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/temperature.c @@ -0,0 +1,60 @@ +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#if CONFIG_TEMP_DATA_USE_SENSOR + +#include +#include + +#else /* CONFIG_TEMP_DATA_USE_SENSOR */ + +#include + +#endif /* CONFIG_TEMP_DATA_USE_SENSOR */ + +#include "temperature.h" + +LOG_MODULE_REGISTER(temperature, CONFIG_NRFCLOUD_BLE_GATEWAY_LOG_LEVEL); + +#if CONFIG_TEMP_DATA_USE_SENSOR + +static const struct device *temp_sensor = DEVICE_DT_GET(DT_ALIAS(temp_sensor)); + +int get_temperature(double *temp) +{ + int err; + struct sensor_value data = {0}; + + /* Fetch all data the sensor supports. */ + err = sensor_sample_fetch_chan(temp_sensor, SENSOR_CHAN_ALL); + if (err) { + LOG_ERR("Failed to sample temperature sensor, error %d", err); + return -ENODATA; + } + + /* Pick out the ambient temperature data. */ + err = sensor_channel_get(temp_sensor, SENSOR_CHAN_AMBIENT_TEMP, &data); + if (err) { + LOG_ERR("Failed to read temperature, error %d", err); + return -ENODATA; + } + + *temp = sensor_value_to_double(&data); + + return 0; +} + +#else /* CONFIG_TEMP_DATA_USE_SENSOR */ + +int get_temperature(double *temp) +{ + *temp = 22.0 + (sys_rand32_get() % 100) / 40.0; + return 0; +} + +#endif /* CONFIG_TEMP_DATA_USE_SENSOR */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/src/temperature.h b/samples/cellular/nrf_cloud_ble_gateway/src/temperature.h new file mode 100644 index 000000000000..6793c98c65d8 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/src/temperature.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _TEMPERATURE_H_ +#define _TEMPERATURE_H_ + +/** + * @brief Take a temperature sample. + * + * Value will either be a real sensor value taken from a sensor with device tree alias temp_sensor, + * (if CONFIG_TEMP_DATA_USE_SENSOR is set) or will otherwise be a pseudorandom dummy reading. + * + * @param[out] temp - Pointer to the double to be filled with the taken temperature sample. + * @return int - 0 on success, otherwise, negative error code. + */ +int get_temperature(double *temp); + +#endif /* _TEMPERATURE_H_ */ diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild.conf new file mode 100644 index 000000000000..9ef3c5f38dd7 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild.conf @@ -0,0 +1,8 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +SB_CONFIG_BOOTLOADER_MCUBOOT=y +SB_CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=n diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/app.overlay b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/app.overlay new file mode 100644 index 000000000000..74d3dfbfd22f --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/app.overlay @@ -0,0 +1,5 @@ +/ { + chosen { + zephyr,code-partition = &boot_partition; + }; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/apricity_gateway_nrf9160.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/apricity_gateway_nrf9160.conf new file mode 100644 index 000000000000..b992af6919ee --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/apricity_gateway_nrf9160.conf @@ -0,0 +1,32 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 +CONFIG_SPI_NRFX_RAM_BUFFER_SIZE=32 + +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y + +CONFIG_FPROTECT=n + +# Enabling SPI increases the MCUBoot image size so that it does not fit in the default +# partition size (0xC000). The minimum required size is 0xD000 +CONFIG_PM_PARTITION_SIZE_MCUBOOT=0xD000 + +# MCUboot requires a large stack size, otherwise an MPU fault will occur +CONFIG_MAIN_STACK_SIZE=16384 + +# Enable flash operations +CONFIG_FLASH=y + +CONFIG_BOOT_MAX_IMG_SECTORS=256 + +CONFIG_MULTITHREADING=y + +CONFIG_LOG=y +CONFIG_LOG_MODE_MINIMAL=y +CONFIG_LOG_DEFAULT_LEVEL=0 diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9151dk_nrf9151.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9151dk_nrf9151.conf new file mode 100644 index 000000000000..827ba33cb5e4 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9151dk_nrf9151.conf @@ -0,0 +1,39 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# MCUboot config to enable secondary slot on the external flash + +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_SFDP_DEVICETREE=y +CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 +CONFIG_SPI_NRFX_RAM_BUFFER_SIZE=32 +CONFIG_MULTITHREADING=y + +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y + +CONFIG_FPROTECT=y + +# Enabling SPI increases the MCUBoot image size so that it does not fit in the default +# partition size (0xC000). The minimum required size is 0xD000 +CONFIG_PM_PARTITION_SIZE_MCUBOOT=0x13E00 + +# MCUboot requires a large stack size, otherwise an MPU fault will occur +CONFIG_MAIN_STACK_SIZE=16384 + +# Enable flash operations +CONFIG_FLASH=y + +CONFIG_BOOT_MAX_IMG_SECTORS=256 + +CONFIG_MULTITHREADING=y + +CONFIG_LOG=y +CONFIG_LOG_MODE_MINIMAL=y +CONFIG_LOG_DEFAULT_LEVEL=0 + +# Use minimal C library instead of the Picolib +CONFIG_MINIMAL_LIBC=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9151dk_nrf9151.overlay b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9151dk_nrf9151.overlay new file mode 100644 index 000000000000..416594d58f6d --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9151dk_nrf9151.overlay @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + zephyr,code-partition = &boot_partition; + nordic,pm-ext-flash = &gd25wb256; + }; +}; + +/* External flash device is disabled by default */ +&gd25wb256 { + status = "okay"; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9160dk_nrf9160.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9160dk_nrf9160.conf new file mode 100644 index 000000000000..e745194b56ac --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9160dk_nrf9160.conf @@ -0,0 +1,32 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 +CONFIG_SPI_NRFX_RAM_BUFFER_SIZE=32 + +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y + +CONFIG_FPROTECT=y + +# Enabling SPI increases the MCUBoot image size so that it does not fit in the default +# partition size (0xC000). The minimum required size is 0xD000 +CONFIG_PM_PARTITION_SIZE_MCUBOOT=0xD000 + +# MCUboot requires a large stack size, otherwise an MPU fault will occur +CONFIG_MAIN_STACK_SIZE=16384 + +# Enable flash operations +CONFIG_FLASH=y + +CONFIG_BOOT_MAX_IMG_SECTORS=256 + +CONFIG_MULTITHREADING=y + +CONFIG_LOG=y +CONFIG_LOG_MODE_MINIMAL=y +CONFIG_LOG_DEFAULT_LEVEL=0 diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9160dk_nrf9160_0_14_0.overlay b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9160dk_nrf9160_0_14_0.overlay new file mode 100644 index 000000000000..f9deaa4c0ddc --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9160dk_nrf9160_0_14_0.overlay @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + zephyr,code-partition = &boot_partition; + nordic,pm-ext-flash = &mx25r64; + /* zephyr,bt-uart=&lpuart; */ + zephyr,bt-hci=&bt_hci_uart; + }; +}; + +/* External flash device is disabled by default */ +&mx25r64 { + status = "okay"; +}; + +&nrf52840_reset { + status = "okay"; +}; + +&gpiote { + interrupts = <49 NRF_DEFAULT_IRQ_PRIORITY>; +}; + +&uart2 { + current-speed = <1000000>; + status = "okay"; + /delete-property/ hw-flow-control; + + pinctrl-0 = <&uart2_default_alt>; + pinctrl-1 = <&uart2_sleep_alt>; + pinctrl-names = "default", "sleep"; + bt_hci_uart: bt_hci_uart { + compatible = "zephyr,bt-hci-uart"; + status = "okay"; + /*rts-pin = <21>; <&interface_to_nrf52840 3 0>; */ + /*cts-pin = <19>; <&interface_to_nrf52840 2 0>; */ + }; +}; + +&pinctrl { + uart2_default_alt: uart2_default_alt { + group1 { + psels = , + , + , + ; + }; + }; + + uart2_sleep_alt: uart2_sleep_alt { + group1 { + psels = , + , + , + ; + low-power-enable; + }; + }; + +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9161dk_nrf9161.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9161dk_nrf9161.conf new file mode 100644 index 000000000000..a49422bdc940 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9161dk_nrf9161.conf @@ -0,0 +1,39 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# MCUboot config to enable secondary slot on the external flash + +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_SFDP_DEVICETREE=y +CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 +CONFIG_SPI_NRFX_RAM_BUFFER_SIZE=32 +CONFIG_MULTITHREADING=y + +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y + +CONFIG_FPROTECT=y + +# Enabling SPI increases the MCUBoot image size so that it does not fit in the default +# partition size (0xC000). The minimum required size is 0xD000 +CONFIG_PM_PARTITION_SIZE_MCUBOOT=0x13E00 + +# MCUboot requires a large stack size, otherwise an MPU fault will occur +CONFIG_MAIN_STACK_SIZE=16384 + +# Enable flash operations +CONFIG_FLASH=y + +CONFIG_BOOT_MAX_IMG_SECTORS=256 + +CONFIG_MULTITHREADING=y + +CONFIG_LOG=y +CONFIG_LOG_MODE_MINIMAL=y +CONFIG_LOG_DEFAULT_LEVEL=0 + +# Use minimal C library instead of the Picolib +CONFIG_MINIMAL_LIBC=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9161dk_nrf9161.overlay b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9161dk_nrf9161.overlay new file mode 100644 index 000000000000..fcf8d1ae1b48 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9161dk_nrf9161.overlay @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + zephyr,code-partition = &boot_partition; + nordic,pm-ext-flash = &gd25wb256; + }; +}; + +/* External flash device is disabled by default */ +&gd25wb256 { + status = "okay"; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9161dk_nrf9161_0_7_0.overlay b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9161dk_nrf9161_0_7_0.overlay new file mode 100644 index 000000000000..3f2322d387cf --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/nrf9161dk_nrf9161_0_7_0.overlay @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/delete-node/ &gd25wb256; + +/ { + chosen { + zephyr,code-partition = &boot_partition; + nordic,pm-ext-flash = &gd25lb256; + }; +}; + +&gd25lb256 { + status = "okay"; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/thingy91_nrf9160.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/thingy91_nrf9160.conf new file mode 100644 index 000000000000..1bf2e424d0d3 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/thingy91_nrf9160.conf @@ -0,0 +1,13 @@ +# Disable Zephyr console +CONFIG_CONSOLE=n +CONFIG_CONSOLE_HANDLER=n +CONFIG_UART_CONSOLE=n + +# Disable Flash protection +CONFIG_FPROTECT=n + +# MCUBoot settings +CONFIG_BOOT_MAX_IMG_SECTORS=256 + +# MCUboot serial recovery +CONFIG_MCUBOOT_SERIAL=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/thingy91x_nrf9151.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/thingy91x_nrf9151.conf new file mode 100644 index 000000000000..7c2042de649c --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/thingy91x_nrf9151.conf @@ -0,0 +1,21 @@ +# MCUBoot settings +CONFIG_BOOT_MAX_IMG_SECTORS=512 + +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 +CONFIG_SPI_NOR_SFDP_DEVICETREE=y +CONFIG_MULTITHREADING=y + +# Disable Zephyr console and use UART for MCUboot serial recovery instead +CONFIG_CONSOLE=n +CONFIG_CONSOLE_HANDLER=n +CONFIG_UART_CONSOLE=n +CONFIG_MCUBOOT_SERIAL=y +CONFIG_MCUBOOT_SERIAL_DIRECT_IMAGE_UPLOAD=y +CONFIG_BOOT_SERIAL_IMG_GRP_IMAGE_STATE=y + +CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=y +CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y + +CONFIG_FW_INFO_FIRMWARE_VERSION=2 diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/thingy91x_nrf9151.overlay b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/thingy91x_nrf9151.overlay new file mode 100644 index 000000000000..7f2818c0d280 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/boards/thingy91x_nrf9151.overlay @@ -0,0 +1,4 @@ +&uart0 { + status = "okay"; + current-speed = < 1000000 >; +}; diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/prj.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/prj.conf new file mode 100644 index 000000000000..2b1d662c393c --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild/mcuboot/prj.conf @@ -0,0 +1,39 @@ +CONFIG_PM=n + +CONFIG_MAIN_STACK_SIZE=10240 +CONFIG_MBEDTLS_CFG_FILE="mcuboot-mbedtls-cfg.h" + +CONFIG_BOOT_SWAP_SAVE_ENCTLV=n +CONFIG_BOOT_ENCRYPT_IMAGE=n + +CONFIG_BOOT_UPGRADE_ONLY=n +CONFIG_BOOT_BOOTSTRAP=n + +### mbedTLS has its own heap +# CONFIG_HEAP_MEM_POOL_SIZE is not set + +### We never want Zephyr's copy of tinycrypt. If tinycrypt is needed, +### MCUboot has its own copy in tree. +# CONFIG_TINYCRYPT is not set +# CONFIG_TINYCRYPT_ECC_DSA is not set +# CONFIG_TINYCRYPT_SHA256 is not set + +CONFIG_FLASH=y +CONFIG_FPROTECT=y + +CONFIG_BT=y +### Various Zephyr boards enable features that we don't want. +# CONFIG_BT_CTLR is not set +# CONFIG_I2C is not set + +CONFIG_LOG=y +CONFIG_LOG_MODE_MINIMAL=y # former CONFIG_MODE_MINIMAL +### Ensure Zephyr logging changes don't use more resources +CONFIG_LOG_DEFAULT_LEVEL=0 +### Use info log level by default +CONFIG_MCUBOOT_LOG_LEVEL_INF=y +### Decrease footprint by ~4 KB in comparison to CBPRINTF_COMPLETE=y +CONFIG_CBPRINTF_NANO=y +CONFIG_NRF_RTC_TIMER_USER_CHAN_COUNT=0 +### Use the minimal C library to reduce flash usage +CONFIG_MINIMAL_LIBC=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild_ext_flash.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild_ext_flash.conf new file mode 100644 index 000000000000..d1b77d36f2bf --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild_ext_flash.conf @@ -0,0 +1,8 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +SB_CONFIG_BOOTLOADER_MCUBOOT=y +SB_CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=y diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild_nrf700x-wifi-conn.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild_nrf700x-wifi-conn.conf new file mode 100644 index 000000000000..4d19da213751 --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild_nrf700x-wifi-conn.conf @@ -0,0 +1,12 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +SB_CONFIG_WIFI_NRF70=y +SB_CONFIG_WIFI_NRF70_SCAN_ONLY=n + +# MCUboot disabled for Wi-Fi connectivity builds in order to save flash +SB_CONFIG_BOOTLOADER_NONE=y +SB_CONFIG_BOOTLOADER_MCUBOOT=n diff --git a/samples/cellular/nrf_cloud_ble_gateway/sysbuild_nrf700x-wifi-scan.conf b/samples/cellular/nrf_cloud_ble_gateway/sysbuild_nrf700x-wifi-scan.conf new file mode 100644 index 000000000000..66837a35384d --- /dev/null +++ b/samples/cellular/nrf_cloud_ble_gateway/sysbuild_nrf700x-wifi-scan.conf @@ -0,0 +1,11 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +SB_CONFIG_WIFI_NRF70=y +SB_CONFIG_WIFI_NRF70_SCAN_ONLY=y + +# MCUboot enabled for scan-only Wi-Fi builds +SB_CONFIG_BOOTLOADER_MCUBOOT=y