From b77fd5457ca4afb767d56a827d924af33e484f13 Mon Sep 17 00:00:00 2001 From: Sarath_Chandra_D Date: Tue, 18 Feb 2025 11:23:10 +0530 Subject: [PATCH] ble mesh chat sample to send 255 bytes of data using nRF21540dk/nRF52840 where build 2 configurations for nRF2140dk/nRF52840, build3 is for enabling FEM using nRF52840dk/nRF52840 + shield nRF21540ek --- .gitignore | 6 + CMakeLists.txt | 19 + Kconfig | 33 ++ Kconfig.sysbuild | 13 + README.rst | 21 + VERSION | 5 + boards/nrf52dk_nrf52832.conf | 9 + boards/nrf54l15dk_nrf54l15_cpuapp.conf | 19 + chat_cli.rst | 110 +++++ include/chat_cli.h | 222 ++++++++++ include/model_handler.h | 27 ++ nrf52840dk_nrf52840.overlay | 12 + prj.conf | 72 ++++ sample.yaml | 15 + sample_description.rst | 227 ++++++++++ src/chat_cli.c | 320 ++++++++++++++ src/main.c | 68 +++ src/model_handler.c | 571 +++++++++++++++++++++++++ 18 files changed, 1769 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Kconfig create mode 100644 Kconfig.sysbuild create mode 100644 README.rst create mode 100644 VERSION create mode 100644 boards/nrf52dk_nrf52832.conf create mode 100644 boards/nrf54l15dk_nrf54l15_cpuapp.conf create mode 100644 chat_cli.rst create mode 100644 include/chat_cli.h create mode 100644 include/model_handler.h create mode 100644 nrf52840dk_nrf52840.overlay create mode 100644 prj.conf create mode 100644 sample.yaml create mode 100644 sample_description.rst create mode 100644 src/chat_cli.c create mode 100644 src/main.c create mode 100644 src/model_handler.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..635a99b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# editors +*.swp +*~ + +# build +/build*/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f6ff486 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(NONE) + +# NORDIC SDK APP START +target_sources(app PRIVATE + src/main.c + src/model_handler.c + src/chat_cli.c) +target_include_directories(app PRIVATE include) + + +# NORDIC SDK APP END diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..f8fe493 --- /dev/null +++ b/Kconfig @@ -0,0 +1,33 @@ +# +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +source "Kconfig.zephyr" + +menu "Nordic Bluetooth Mesh chat client sample" + +config BT_MESH_CHAT_CLI_MESSAGE_LENGTH + int "Max length of the message to be sent over mesh" + default 90 + range 0 255 + help + Impacts on memory size occupied by the buffer of a message to be + published. This does not include null terminator. The message must also + fit into the application SDU. Adjust BT_MESH_TX_SEG_MAX and + BT_MESH_RX_SEG_MAX to be able to receive the longest messages. + +config BT_MESH_CHAT_SAMPLE_PRESENCE_CACHE_SIZE + int "Presence cache size" + default 10 + range 0 32767 + help + Presence cache stores previously received presence of chat clients. + Recommended to be as big as number of chat clients in the mesh network. + +endmenu + +module = BT_MESH_CHAT_CLI +module-str = BT Mesh Chat Client model +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" diff --git a/Kconfig.sysbuild b/Kconfig.sysbuild new file mode 100644 index 0000000..d561707 --- /dev/null +++ b/Kconfig.sysbuild @@ -0,0 +1,13 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +source "${ZEPHYR_BASE}/share/sysbuild/Kconfig" + +config NRF_DEFAULT_IPC_RADIO + default y + +config NETCORE_IPC_RADIO_BT_HCI_IPC + default y diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..a5650e1 --- /dev/null +++ b/README.rst @@ -0,0 +1,21 @@ +.. _bt_mesh_chat: + +Bluetooth Mesh: Chat +#################### + +.. contents:: + :local: + :depth: 2 + +The Bluetooth® Mesh chat sample demonstrates how to use the mesh network to facilitate communication between nodes by text, using the :ref:`bt_mesh_chat_client_model`. + +See the subpages for detailed documentation on the sample and its internal model. + +.. _mesh_chat_subpages: + +.. toctree:: + :maxdepth: 1 + :caption: Subpages: + + sample_description.rst + chat_cli.rst diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..e18e5c5 --- /dev/null +++ b/VERSION @@ -0,0 +1,5 @@ +VERSION_MAJOR = 2 +VERSION_MINOR = 9 +PATCHLEVEL = 0 +VERSION_TWEAK = 0 +EXTRAVERSION = diff --git a/boards/nrf52dk_nrf52832.conf b/boards/nrf52dk_nrf52832.conf new file mode 100644 index 0000000..ce4f104 --- /dev/null +++ b/boards/nrf52dk_nrf52832.conf @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +################################################################################ +# Application overlay - nrf52dk_nrf52832 + +CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE=n diff --git a/boards/nrf54l15dk_nrf54l15_cpuapp.conf b/boards/nrf54l15dk_nrf54l15_cpuapp.conf new file mode 100644 index 0000000..e12c92c --- /dev/null +++ b/boards/nrf54l15dk_nrf54l15_cpuapp.conf @@ -0,0 +1,19 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +################################################################################ +# Application overlay - nrf54l15 + +CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE=n +CONFIG_NVS=n +CONFIG_NVS_LOOKUP_CACHE=n +CONFIG_SETTINGS_NVS_NAME_CACHE=n +CONFIG_ZMS=y +CONFIG_SETTINGS_ZMS_SECTOR_COUNT=8 +CONFIG_ZMS_LOOKUP_CACHE=y +CONFIG_ZMS_LOOKUP_CACHE_SIZE=512 +CONFIG_SETTINGS_ZMS_NAME_CACHE=y +CONFIG_SETTINGS_ZMS_NAME_CACHE_SIZE=512 +CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS=y diff --git a/chat_cli.rst b/chat_cli.rst new file mode 100644 index 0000000..c2df695 --- /dev/null +++ b/chat_cli.rst @@ -0,0 +1,110 @@ +.. _bt_mesh_chat_client_model: + +Chat Client model +################# + +.. contents:: + :local: + :depth: 2 + +The Chat Client model is a vendor model that allows communication with other such models, by sending text messages and providing the presence of the model instance. +It demonstrates basics of a vendor model implementation. +The model doesn't have a limitation on per-node instantiations of the model, and therefore can be instantiated on each element of the node. + +Overview +******** + +In this section, you can find more detailed information about the following aspects of the Chat Client: + +* `Composition data structure`_ +* `Messages`_ + +.. _bt_mesh_chat_client_model_composition: + +Composition data structure +========================== + +The Chat Client model is a vendor model, and therefore in the application, when defining the node composition data, it needs to be declared in the third argument in the :c:macro:`BT_MESH_ELEM` macro: + +.. code-block:: c + + static struct bt_mesh_elem elements[] = { + BT_MESH_ELEM(1, + BT_MESH_MODEL_LIST( + BT_MESH_MODEL_CFG_SRV(&cfg_srv), + BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub)), + BT_MESH_MODEL_LIST( + BT_MESH_MODEL_CHAT_CLI(&chat))), + }; + +.. _bt_mesh_chat_client_model_messages: + +Messages +======== + +The Chat Client model defines the following messages: + +Presence + Used to report the current model presence. + When the model periodic publication is configured, the Chat Client model will publish its current presence, regardless of whether it has been changed or not. + Presence message has a defined length of 1 byte. + +Presence Get + Used to retrieve the current model presence. + Upon receiving the Presence Get message, the Chat Client model will send the Presence message with the current model presence stored in the response. + The message doesn't have any payload. + +Message + Used to send a non-private text message. + The payload consists of the text string terminated by ``\0``. + The length of the text string can be configured at the compile-time using :ref:`CONFIG_BT_MESH_CHAT_CLI_MESSAGE_LENGTH ` option. + +Private Message + Used to send a private text message. + When the model receives this message, it replies with the Message Reply. + The payload consists of the text string terminated by ``\0``. + The length of the text string can be configured at the compile-time using :ref:`CONFIG_BT_MESH_CHAT_CLI_MESSAGE_LENGTH ` option. + +Message Reply + Used to reply on the received Private Message to confirm the reception. + The message doesn't have any payload. + +Configuration +************* +|config| + +Configuration options +===================== + +The following configuration parameters are associated with the Chat Client model: + +.. _CONFIG_BT_MESH_CHAT_CLI_MESSAGE_LENGTH: + +CONFIG_BT_MESH_CHAT_CLI_MESSAGE_LENGTH - Message length configuration + Maximum length of the message to be sent over the mesh network. + +.. _bt_mesh_chat_client_model_states: + +States +****** + +The Chat Client model contains the following states: + +Presence: ``bt_mesh_chat_cli_presence``: + The Chat Client model enables a user to set a current presence of the client instantiated on the element of the node. + It can have the following values: + + * :c:enumerator:`BT_MESH_CHAT_CLI_PRESENCE_AVAILABLE` - The client is available. + * :c:enumerator:`BT_MESH_CHAT_CLI_PRESENCE_AWAY` - The client is away. + * :c:enumerator:`BT_MESH_CHAT_CLI_PRESENCE_INACTIVE` - The client is inactive. + * :c:enumerator:`BT_MESH_CHAT_CLI_PRESENCE_DO_NOT_DISTURB` - The client is in "do not disturb" state. + +Extended models +*************** + +None. + +Persistent storage +****************** + +If :kconfig:option:`CONFIG_BT_SETTINGS` is enabled, the Chat Client stores its presence state. diff --git a/include/chat_cli.h b/include/chat_cli.h new file mode 100644 index 0000000..0a5b7df --- /dev/null +++ b/include/chat_cli.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * @defgroup bt_mesh_chat_cli + * @{ + * @brief API for the Bluetooth Mesh Chat Client model. + */ + +#ifndef BT_MESH_CHAT_CLI_H__ +#define BT_MESH_CHAT_CLI_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* .. include_startingpoint_chat_cli_rst_1 */ +/** Company ID of the Bluetooth Mesh Chat Client model. */ +#define BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID CONFIG_BT_COMPANY_ID_NORDIC + +/** Model ID of the Bluetooth Mesh Chat Client model. */ +#define BT_MESH_CHAT_CLI_VENDOR_MODEL_ID 0x000A + +/** Non-private message opcode. */ +#define BT_MESH_CHAT_CLI_OP_MESSAGE BT_MESH_MODEL_OP_3(0x0A, \ + BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID) + +/** Private message opcode. */ +#define BT_MESH_CHAT_CLI_OP_PRIVATE_MESSAGE BT_MESH_MODEL_OP_3(0x0B, \ + BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID) + +/** Message reply opcode. */ +#define BT_MESH_CHAT_CLI_OP_MESSAGE_REPLY BT_MESH_MODEL_OP_3(0x0C, \ + BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID) + +/** Presence message opcode. */ +#define BT_MESH_CHAT_CLI_OP_PRESENCE BT_MESH_MODEL_OP_3(0x0D, \ + BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID) + +/** Presence get message opcode. */ +#define BT_MESH_CHAT_CLI_OP_PRESENCE_GET BT_MESH_MODEL_OP_3(0x0E, \ + BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID) +/* .. include_endpoint_chat_cli_rst_1 */ + +#define BT_MESH_CHAT_CLI_MSG_MINLEN_MESSAGE 1 +#define BT_MESH_CHAT_CLI_MSG_MAXLEN_MESSAGE (\ + CONFIG_BT_MESH_CHAT_CLI_MESSAGE_LENGTH \ + + 1) /* + \0 */ +#define BT_MESH_CHAT_CLI_MSG_LEN_MESSAGE_REPLY 0 +#define BT_MESH_CHAT_CLI_MSG_LEN_PRESENCE 1 +#define BT_MESH_CHAT_CLI_MSG_LEN_PRESENCE_GET 0 + +/** Bluetooth Mesh Chat Client Presence values. */ +enum bt_mesh_chat_cli_presence { + BT_MESH_CHAT_CLI_PRESENCE_AVAILABLE, + BT_MESH_CHAT_CLI_PRESENCE_AWAY, + BT_MESH_CHAT_CLI_PRESENCE_INACTIVE, + BT_MESH_CHAT_CLI_PRESENCE_DO_NOT_DISTURB +}; + +/* Forward declaration of the Bluetooth Mesh Chat Client model context. */ +struct bt_mesh_chat_cli; + +/* .. include_startingpoint_chat_cli_rst_2 */ +/** @def BT_MESH_MODEL_CHAT_CLI + * + * @brief Bluetooth Mesh Chat Client model composition data entry. + * + * @param[in] _chat Pointer to a @ref bt_mesh_chat_cli instance. + */ +#define BT_MESH_MODEL_CHAT_CLI(_chat) \ + BT_MESH_MODEL_VND_CB(BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID, \ + BT_MESH_CHAT_CLI_VENDOR_MODEL_ID, \ + _bt_mesh_chat_cli_op, &(_chat)->pub, \ + BT_MESH_MODEL_USER_DATA(struct bt_mesh_chat_cli, \ + _chat), \ + &_bt_mesh_chat_cli_cb) +/* .. include_endpoint_chat_cli_rst_2 */ + +/** Bluetooth Mesh Chat Client model handlers. */ +struct bt_mesh_chat_cli_handlers { + /** @brief Called after the node has been provisioned, or after all + * mesh data has been loaded from persistent storage. + * + * @param[in] cli Chat Client instance that has been started. + */ + void (*const start)(struct bt_mesh_chat_cli *chat); + + /** @brief Handler for a presence message. + * + * @param[in] cli Chat client instance that received the text message. + * @param[in] ctx Context of the incoming message. + * @param[in] presence Presence of a Chat Client that published + * the message. + */ + void (*const presence)(struct bt_mesh_chat_cli *chat, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_chat_cli_presence presence); + + /** @brief Handler for a non-private text message. + * + * @param[in] cli Chat client instance that received the text message. + * @param[in] ctx Context of the incoming message. + * @param[in] msg Pointer to a received text message terminated with + * a null character, '\0'. + */ + void (*const message)(struct bt_mesh_chat_cli *chat, + struct bt_mesh_msg_ctx *ctx, + const uint8_t *msg); + + /** @brief Handler for a private message. + * + * @param[in] cli Chat client that received the text message. + * @param[in] ctx Context of the incoming message. + * @param[in] msg Pointer to a received text message terminated with + * a null character, '\0'. + */ + void (*const private_message)(struct bt_mesh_chat_cli *chat, + struct bt_mesh_msg_ctx *ctx, + const uint8_t *msg); + + /** @brief Handler for a reply on a private message. + * + * @param[in] cli Chat client instance that received the reply. + * @param[in] ctx Context of the incoming message. + */ + void (*const message_reply)(struct bt_mesh_chat_cli *chat, + struct bt_mesh_msg_ctx *ctx); +}; + +/* .. include_startingpoint_chat_cli_rst_3 */ +/** + * Bluetooth Mesh Chat Client model context. + */ +struct bt_mesh_chat_cli { + /** Access model pointer. */ + const struct bt_mesh_model *model; + /** Publish parameters. */ + struct bt_mesh_model_pub pub; + /** Publication message. */ + struct net_buf_simple pub_msg; + /** Publication message buffer. */ + uint8_t buf[BT_MESH_MODEL_BUF_LEN(BT_MESH_CHAT_CLI_OP_MESSAGE, + BT_MESH_CHAT_CLI_MSG_MAXLEN_MESSAGE)]; + /** Handler function structure. */ + const struct bt_mesh_chat_cli_handlers *handlers; + /** Current Presence value. */ + enum bt_mesh_chat_cli_presence presence; +}; +/* .. include_endpoint_chat_cli_rst_3 */ + +/** @brief Set the client presence and publish the presence to the mesh network. + * + * @param[in] chat Chat Client model instance to set presence on. + * @param[in] presence Presence status to be published. + * + * @retval 0 Successfully set the preceive and sent the message. + * @retval -EADDRNOTAVAIL Publishing is not configured. + * @retval -EAGAIN The device has not been provisioned. + */ +int bt_mesh_chat_cli_presence_set(struct bt_mesh_chat_cli *chat, + enum bt_mesh_chat_cli_presence presence); + +/** @brief Get current presence value of a chat client. + * + * @param[in] chat Chat Client model instance to send the message. + * @param[in] addr Address of the chat client to get presence value of. + * + * @retval 0 Successfully sent the message. + * @retval -EINVAL The model is not bound to an application key. + * @retval -EAGAIN The device has not been provisioned. + */ +int bt_mesh_chat_cli_presence_get(struct bt_mesh_chat_cli *chat, + uint16_t addr); + +/** @brief Send a text message. + * + * @param[in] cli Chat Client model instance to send the message. + * @param[in] msg Pointer to a text message to send. Must be terminated with + * a null character, '\0'. + * + * @retval 0 Successfully sent the message. + * @retval -EADDRNOTAVAIL Publishing is not configured. + * @retval -EAGAIN The device has not been provisioned. + */ +int bt_mesh_chat_cli_message_send(struct bt_mesh_chat_cli *chat, + const uint8_t *msg); + +/** @brief Send a text message to a specified destination. + * + * @param[in] cli Chat Client model instance to send the message. + * @param[in] addr Address of the chat client to send message to. + * @param[in] msg Pointer to a text message to send. Must be terminated with + * a null character, '\0'. + * + * @retval 0 Successfully sent the message. + * @retval -EINVAL The model is not bound to an application key. + * @retval -EAGAIN The device has not been provisioned. + */ +int bt_mesh_chat_cli_private_message_send(struct bt_mesh_chat_cli *chat, + uint16_t addr, + const uint8_t *msg); + +/** @cond INTERNAL_HIDDEN */ +extern const struct bt_mesh_model_op _bt_mesh_chat_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_chat_cli_cb; +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* BT_MESH_CHAT_CLI_H__ */ + +/** @} */ diff --git a/include/model_handler.h b/include/model_handler.h new file mode 100644 index 0000000..dc8cd08 --- /dev/null +++ b/include/model_handler.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * @brief Model handler + */ + +#ifndef MODEL_HANDLER_H__ +#define MODEL_HANDLER_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +const struct bt_mesh_comp *model_handler_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* MODEL_HANDLER_H__ */ diff --git a/nrf52840dk_nrf52840.overlay b/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000..251ad7f --- /dev/null +++ b/nrf52840dk_nrf52840.overlay @@ -0,0 +1,12 @@ +// To get started, press Ctrl+Space to bring up the completion menu and view the available nodes. + +// You can also use the buttons in the sidebar to perform actions on nodes. +// Actions currently available include: + +// * Enabling / disabling the node +// * Adding the bus to a bus +// * Removing the node +// * Connecting ADC channels + +// For more help, browse the DeviceTree documentation at https://docs.zephyrproject.org/latest/guides/dts/index.html +// You can also visit the nRF DeviceTree extension documentation at https://docs.nordicsemi.com/bundle/nrf-connect-vscode/page/guides/ncs_configure_app.html#devicetree-support-in-the-extension diff --git a/prj.conf b/prj.conf new file mode 100644 index 0000000..740d690 --- /dev/null +++ b/prj.conf @@ -0,0 +1,72 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +CONFIG_NCS_SAMPLES_DEFAULTS=y + +# Deferred logging helps improve LPN power consumption +# when friendship is established. +CONFIG_LOG_MODE_DEFERRED=y + +# General configuration +CONFIG_NCS_APPLICATION_BOOT_BANNER_STRING="Mesh Chat" +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y +CONFIG_SETTINGS=y +CONFIG_NVS_LOOKUP_CACHE=y +CONFIG_SETTINGS_NVS_NAME_CACHE=y +CONFIG_HWINFO=y +CONFIG_DK_LIBRARY=y +CONFIG_PM_SINGLE_IMAGE=y +CONFIG_PM_PARTITION_SIZE_SETTINGS_STORAGE=0x8000 +CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE=y + +# Bluetooth configuration +CONFIG_BT=y +CONFIG_BT_DEVICE_NAME="Mesh Chat" +CONFIG_BT_L2CAP_TX_BUF_COUNT=8 +CONFIG_BT_OBSERVER=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_SETTINGS=y + +# Disable unused Bluetooth features +CONFIG_BT_CTLR_LE_ENC=n +CONFIG_BT_PHY_UPDATE=n +CONFIG_BT_CTLR_MIN_USED_CHAN=n +CONFIG_BT_CTLR_PRIVACY=n + +# Bluetooth Mesh configuration +CONFIG_BT_MESH=y +CONFIG_BT_MESH_RELAY=y +CONFIG_BT_MESH_FRIEND=y +CONFIG_BT_MESH_RX_SEG_MAX=22 +CONFIG_BT_MESH_TX_SEG_MAX=22 +CONFIG_BT_MESH_CHAT_CLI_MESSAGE_LENGTH=255 +CONFIG_BT_MESH_PB_GATT=y +CONFIG_BT_MESH_GATT_PROXY=y +CONFIG_BT_MESH_DK_PROV=y + +# Enable Bluetooth Mesh models debug logs +CONFIG_BT_MESH_LOG_LEVEL_DBG=y + +# Enable Shell module and use UART as a backend +CONFIG_SHELL=y +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_FLASH_SHELL=n + +CONFIG_LOG_BACKEND_RTT=n + +# configure 21540dk and enable MPSL and FEM +CONFIG_MPSL=y +CONFIG_MPSL_FEM=y +CONFIG_FEM=y # not set for nrf21540dk +CONFIG_MPSL_FEM_NRF21540_GPIO_SPI=y +CONFIG_MPSL_FEM_NRF21540_TX_GAIN_DB=20 +# CONFIG_BT_CTLR_TX_PWR_DBM=0 // not set for nrf21540dk +CONFIG_BT_CTLR_TX_PWR_ANTENNA=20 + +CONFIG_MPSL_FEM_POWER_MODEL=y +CONFIG_MPSL_FEM_POWER_MODEL_NRF21540_USE_BUILTIN=y diff --git a/sample.yaml b/sample.yaml new file mode 100644 index 0000000..a1b780f --- /dev/null +++ b/sample.yaml @@ -0,0 +1,15 @@ +sample: + description: Bluetooth Mesh Chat sample + name: Bluetooth Mesh Chat +tests: + sample.bluetooth.mesh.chat: + sysbuild: true + build_only: true + integration_platforms: + - nrf52dk/nrf52832 + - nrf52840dk/nrf52840 + - nrf21540dk/nrf52840 + - nrf54l15dk/nrf54l15/cpuapp + platform_allow: nrf52dk/nrf52832 nrf52840dk/nrf52840 nrf21540dk/nrf52840 + nrf54l15dk/nrf54l15/cpuapp + tags: bluetooth ci_build sysbuild diff --git a/sample_description.rst b/sample_description.rst new file mode 100644 index 0000000..23e902c --- /dev/null +++ b/sample_description.rst @@ -0,0 +1,227 @@ +.. _bt_mesh_chat_description: + +Sample description +################## + +.. contents:: + :local: + :depth: 2 + +The Bluetooth® Mesh chat sample demonstrates how the mesh network can be used to facilitate communication between nodes by text, using the :ref:`bt_mesh_chat_client_model`. + +Requirements +************ + +The sample supports the following development kits: + +.. table-from-sample-yaml:: + +The sample also requires a smartphone with Nordic Semiconductor's nRF Mesh mobile app installed in one of the following versions: + +* `nRF Mesh mobile app for Android`_ +* `nRF Mesh mobile app for iOS`_ + +Overview +******** + +By means of the mesh network, the clients as mesh nodes can communicate with each other without the need of a server. +The mesh chat sample is mainly designed for group communication, but it also supports one-on-one communication, as well as sharing the nodes presence. + +This sample is used in :ref:`ug_bt_mesh_vendor_model` as an example of how to implement a vendor model for the Bluetooth Mesh in |NCS|. + +The clients are nodes with a provisionee role in a mesh network. +Provisioning is performed using the `nRF Mesh mobile app`_. +This mobile application is also used to configure key bindings, and publication and subscription settings of the Bluetooth Mesh model instances in the sample. +After provisioning and configuring the mesh models supported by the sample in the `nRF Mesh mobile app`_, you can communicate with other mesh nodes by sending text messages and obtaining their presence using the :ref:`shell module `. + +Provisioning +============ + +The provisioning is handled by the :ref:`bt_mesh_dk_prov`. +It supports four types of out-of-band (OOB) authentication methods, and uses the Hardware Information driver to generate a deterministic UUID to uniquely represent the device. + +Models +====== + +The following table shows the Bluetooth Mesh chat composition data for this sample: + + +---------------+ + | Element 1 | + +===============+ + | Config Server | + +---------------+ + | Health Server | + +---------------+ + | Chat Client | + +---------------+ + +The models are used for the following purposes: + +* The :ref:`bt_mesh_chat_client_model` instance in the first element is used to communicate with the other Chat Client models instantiated on the other mesh nodes. +* Config Server allows configurator devices to configure the node remotely. +* Health Server provides ``attention`` callbacks that are used during provisioning to call your attention to the device. + These callbacks trigger blinking of the LEDs. + +The model handling is implemented in :file:`src/model_handler.c`. + +User interface +************** + +Buttons: + Can be used to input the OOB authentication value during provisioning. + All buttons have the same functionality during this procedure. + +LEDs: + Show the OOB authentication value during provisioning if the "Push button" OOB method is used. + +Terminal emulator: + Used for the interaction with the sample. + +Configuration +************* + +|config| + +Source file setup +================= + +This sample is split into the following source files: + +* A :file:`main.c` file to handle initialization. +* A file for handling the Chat Client model, :file:`chat_cli.c`. +* A file for handling Bluetooth Mesh models and communication with the :ref:`shell module `, :file:`model_handler.c`. + +FEM support +=========== + +.. include:: /includes/sample_fem_support.txt + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/bluetooth/mesh/chat` + +.. include:: /includes/build_and_run.txt + +.. _bluetooth_mesh_chat_testing: + +Testing +======= + +After programming the sample to your development kit, you can test it by using a smartphone with `nRF Mesh mobile app`_ installed. +Testing consists of provisioning the device and configuring it for communication with other nodes. + +After configuring the device, you can interact with the sample using the terminal emulator. + +Provisioning the device +----------------------- + +.. |device name| replace:: :guilabel:`Mesh Chat` + +.. include:: /includes/mesh_device_provisioning.txt + +Configuring models +------------------ + +See :ref:`ug_bt_mesh_model_config_app` for details on how to configure the mesh models with the nRF Mesh mobile app. + +Create a new group and name it *Chat Channel*, then configure the Vendor model on the **Mesh Chat** node: + +* Bind the model to **Application Key 1**. +* Set the publication parameters: + + * Destination/publish address: Select the created group **Chat Channel**. + * Publication interval: Set the interval to recommended value of 10 seconds. + * Retransmit count: Change the count as preferred. + +* Set the subscription parameters: Select the created group **Chat Channel**. + +Interacting with the sample +--------------------------- + +1. Connect the development kit to the computer using a USB cable. + The development kit is assigned a COM port (Windows), ttyACM device (Linux) or tty.usbmodem (MacOS). +#. |connect_terminal_specific_ANSI| +#. Enable local echo in the terminal to see the text you are typing. + +After completing the steps above, a command can be sent to the sample. +The sample supports the following commands: + +chat \-\-help + Prints help message together with the list of supported commands. + +chat presence set + Sets presence of the current client. + The following values are supported: available, away, dnd, inactive. + +chat presence get + Gets presence of a specified chat client. + +chat private + Sends a private text message to a specified chat client. + Remember to wrap the message in double quotes if it has 2 or more words. + +chat msg + Sends a text message to the chat. + Remember to wrap the message in double quotes if it has 2 or more words. + +Whenever the node changes its presence, or the local node receives another model's presence the first time, you will see the following message: + +.. code-block:: none + + <0x0002> is now available + +When the model receives a message from another node, together with the message you will see the address of the element of the node that sent the message: + +.. code-block:: none + + <0x0002>: Hi there! + +The messages posted by the local node will have ```` instead of the address of the element: + +.. code-block:: none + + : Hello, 0x0002! + are now away + +Private messages can be identified by the address of the element of the node that posted the message (enclosed in asterisks): + +.. code-block:: none + + : *0x0004* See you! + <0x0004>: *you* Bye! + +When the reply is received, you will see the following: + +.. code-block:: none + + <0x0004> received the message + +Note that private messages are only seen by those the messages are addressed to. + +Dependencies +************ + +This sample uses the following |NCS| libraries: + +* :ref:`bt_mesh_dk_prov` +* :ref:`dk_buttons_and_leds_readme` + +In addition, it uses the following Zephyr libraries: + +* :ref:`zephyr:kernel_api`: + + * :file:`include/kernel.h` + +* :ref:`zephyr:shell_api`: + + * :file:`include/shell.h` + * :file:`include/shell_uart.h` + +* :ref:`zephyr:bluetooth_api`: + + * :file:`include/bluetooth/bluetooth.h` + +* :ref:`zephyr:bluetooth_mesh`: + + * :file:`include/bluetooth/mesh.h` diff --git a/src/chat_cli.c b/src/chat_cli.c new file mode 100644 index 0000000..04b2fbf --- /dev/null +++ b/src/chat_cli.c @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include "chat_cli.h" +#include "mesh/net.h" +#include + +#include +LOG_MODULE_DECLARE(chat); + +BUILD_ASSERT(BT_MESH_MODEL_BUF_LEN(BT_MESH_CHAT_CLI_OP_MESSAGE, + BT_MESH_CHAT_CLI_MSG_MAXLEN_MESSAGE) <= + BT_MESH_RX_SDU_MAX, + "The message must fit inside an application SDU."); +BUILD_ASSERT(BT_MESH_MODEL_BUF_LEN(BT_MESH_CHAT_CLI_OP_MESSAGE, + BT_MESH_CHAT_CLI_MSG_MAXLEN_MESSAGE) <= + BT_MESH_TX_SDU_MAX, + "The message must fit inside an application SDU."); + +static void encode_presence(struct net_buf_simple *buf, + enum bt_mesh_chat_cli_presence presence) +{ + bt_mesh_model_msg_init(buf, BT_MESH_CHAT_CLI_OP_PRESENCE); + net_buf_simple_add_u8(buf, presence); +} + +static const uint8_t *extract_msg(struct net_buf_simple *buf) +{ + buf->data[buf->len - 1] = '\0'; + return net_buf_simple_pull_mem(buf, buf->len); +} + +static int handle_message(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + const uint8_t *msg; + + msg = extract_msg(buf); + + if (chat->handlers->message) { + chat->handlers->message(chat, ctx, msg); + } + + return 0; +} + +/* .. include_startingpoint_chat_cli_rst_1 */ +static void send_message_reply(struct bt_mesh_chat_cli *chat, + struct bt_mesh_msg_ctx *ctx) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, BT_MESH_CHAT_CLI_OP_MESSAGE_REPLY, + BT_MESH_CHAT_CLI_MSG_LEN_MESSAGE_REPLY); + bt_mesh_model_msg_init(&msg, BT_MESH_CHAT_CLI_OP_MESSAGE_REPLY); + + (void)bt_mesh_model_send(chat->model, ctx, &msg, NULL, NULL); +} + +static int handle_private_message(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + const uint8_t *msg; + + msg = extract_msg(buf); + + if (chat->handlers->private_message) { + chat->handlers->private_message(chat, ctx, msg); + } + + send_message_reply(chat, ctx); + return 0; +} +/* .. include_endpoint_chat_cli_rst_1 */ + +static int handle_message_reply(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + + if (chat->handlers->message_reply) { + chat->handlers->message_reply(chat, ctx); + } + + return 0; +} + +static int handle_presence(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + enum bt_mesh_chat_cli_presence presence; + + presence = net_buf_simple_pull_u8(buf); + + if (chat->handlers->presence) { + chat->handlers->presence(chat, ctx, presence); + } + + return 0; +} + +static int handle_presence_get(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + + BT_MESH_MODEL_BUF_DEFINE(msg, BT_MESH_CHAT_CLI_OP_PRESENCE, + BT_MESH_CHAT_CLI_MSG_LEN_PRESENCE); + + encode_presence(&msg, chat->presence); + + (void)bt_mesh_model_send(chat->model, ctx, &msg, NULL, NULL); + + return 0; +} + +/* .. include_startingpoint_chat_cli_rst_2 */ +const struct bt_mesh_model_op _bt_mesh_chat_cli_op[] = { + { + BT_MESH_CHAT_CLI_OP_MESSAGE, + BT_MESH_LEN_MIN(BT_MESH_CHAT_CLI_MSG_MINLEN_MESSAGE), + handle_message + }, + { + BT_MESH_CHAT_CLI_OP_PRIVATE_MESSAGE, + BT_MESH_LEN_MIN(BT_MESH_CHAT_CLI_MSG_MINLEN_MESSAGE), + handle_private_message + }, + { + BT_MESH_CHAT_CLI_OP_MESSAGE_REPLY, + BT_MESH_LEN_EXACT(BT_MESH_CHAT_CLI_MSG_LEN_MESSAGE_REPLY), + handle_message_reply + }, + { + BT_MESH_CHAT_CLI_OP_PRESENCE, + BT_MESH_LEN_EXACT(BT_MESH_CHAT_CLI_MSG_LEN_PRESENCE), + handle_presence + }, + { + BT_MESH_CHAT_CLI_OP_PRESENCE_GET, + BT_MESH_LEN_EXACT(BT_MESH_CHAT_CLI_MSG_LEN_PRESENCE_GET), + handle_presence_get + }, + BT_MESH_MODEL_OP_END, +}; +/* .. include_endpoint_chat_cli_rst_2 */ + +static int bt_mesh_chat_cli_update_handler(const struct bt_mesh_model *model) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + + /* Continue publishing current presence. */ + encode_presence(model->pub->msg, chat->presence); + + return 0; +} + +/* .. include_startingpoint_chat_cli_rst_3 */ +#ifdef CONFIG_BT_SETTINGS +static int bt_mesh_chat_cli_settings_set(const struct bt_mesh_model *model, + const char *name, + size_t len_rd, + settings_read_cb read_cb, + void *cb_arg) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + + if (name) { + return -ENOENT; + } + + ssize_t bytes = read_cb(cb_arg, &chat->presence, + sizeof(chat->presence)); + if (bytes < 0) { + return bytes; + } + + if (bytes != 0 && bytes != sizeof(chat->presence)) { + return -EINVAL; + } + + return 0; +} +#endif +/* .. include_endpoint_chat_cli_rst_3 */ + +/* .. include_startingpoint_chat_cli_rst_4 */ +static int bt_mesh_chat_cli_init(const struct bt_mesh_model *model) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + + chat->model = model; + + net_buf_simple_init_with_data(&chat->pub_msg, chat->buf, + sizeof(chat->buf)); + chat->pub.msg = &chat->pub_msg; + chat->pub.update = bt_mesh_chat_cli_update_handler; + + return 0; +} +/* .. include_endpoint_chat_cli_rst_4 */ + +/* .. include_startingpoint_chat_cli_rst_5 */ +static int bt_mesh_chat_cli_start(const struct bt_mesh_model *model) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + + if (chat->handlers->start) { + chat->handlers->start(chat); + } + + return 0; +} +/* .. include_endpoint_chat_cli_rst_5 */ + +/* .. include_startingpoint_chat_cli_rst_6 */ +static void bt_mesh_chat_cli_reset(const struct bt_mesh_model *model) +{ + struct bt_mesh_chat_cli *chat = model->rt->user_data; + + chat->presence = BT_MESH_CHAT_CLI_PRESENCE_AVAILABLE; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) { + (void) bt_mesh_model_data_store(model, true, NULL, NULL, 0); + } +} +/* .. include_endpoint_chat_cli_rst_6 */ + +/* .. include_startingpoint_chat_cli_rst_7 */ +const struct bt_mesh_model_cb _bt_mesh_chat_cli_cb = { + .init = bt_mesh_chat_cli_init, + .start = bt_mesh_chat_cli_start, +#ifdef CONFIG_BT_SETTINGS + .settings_set = bt_mesh_chat_cli_settings_set, +#endif + .reset = bt_mesh_chat_cli_reset, +}; +/* .. include_endpoint_chat_cli_rst_7 */ + +/* .. include_startingpoint_chat_cli_rst_8 */ +int bt_mesh_chat_cli_presence_set(struct bt_mesh_chat_cli *chat, + enum bt_mesh_chat_cli_presence presence) +{ + if (presence != chat->presence) { + chat->presence = presence; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) { + (void) bt_mesh_model_data_store(chat->model, true, + NULL, &presence, + sizeof(chat->presence)); + } + } + + encode_presence(chat->model->pub->msg, chat->presence); + + return bt_mesh_model_publish(chat->model); +} +/* .. include_endpoint_chat_cli_rst_8 */ + +int bt_mesh_chat_cli_presence_get(struct bt_mesh_chat_cli *chat, + uint16_t addr) +{ + struct bt_mesh_msg_ctx ctx = { + .addr = addr, + .app_idx = chat->model->keys[0], + .send_ttl = BT_MESH_TTL_DEFAULT, + .send_rel = true, + }; + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_CHAT_CLI_OP_PRESENCE_GET, + BT_MESH_CHAT_CLI_MSG_LEN_PRESENCE_GET); + bt_mesh_model_msg_init(&buf, BT_MESH_CHAT_CLI_OP_PRESENCE_GET); + + return bt_mesh_model_send(chat->model, &ctx, &buf, NULL, NULL); +} + +int bt_mesh_chat_cli_message_send(struct bt_mesh_chat_cli *chat, + const uint8_t *msg) +{ + struct net_buf_simple *buf = chat->model->pub->msg; + + bt_mesh_model_msg_init(buf, BT_MESH_CHAT_CLI_OP_MESSAGE); + + net_buf_simple_add_mem(buf, msg, + strnlen(msg, + CONFIG_BT_MESH_CHAT_CLI_MESSAGE_LENGTH)); + net_buf_simple_add_u8(buf, '\0'); + + return bt_mesh_model_publish(chat->model); +} + +/* .. include_startingpoint_chat_cli_rst_9 */ +int bt_mesh_chat_cli_private_message_send(struct bt_mesh_chat_cli *chat, + uint16_t addr, + const uint8_t *msg) +{ + struct bt_mesh_msg_ctx ctx = { + .addr = addr, + .app_idx = chat->model->keys[0], + .send_ttl = BT_MESH_TTL_DEFAULT, + .send_rel = true, + }; + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_CHAT_CLI_OP_PRIVATE_MESSAGE, + BT_MESH_CHAT_CLI_MSG_MAXLEN_MESSAGE); + bt_mesh_model_msg_init(&buf, BT_MESH_CHAT_CLI_OP_PRIVATE_MESSAGE); + + net_buf_simple_add_mem(&buf, msg, + strnlen(msg, + CONFIG_BT_MESH_CHAT_CLI_MESSAGE_LENGTH)); + net_buf_simple_add_u8(&buf, '\0'); + + return bt_mesh_model_send(chat->model, &ctx, &buf, NULL, NULL); +} +/* .. include_endpoint_chat_cli_rst_9 */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..d0d74b5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** @file + * @brief Nordic Mesh light sample + */ +#include +#include +#include +#include +#include "model_handler.h" + +#include +LOG_MODULE_REGISTER(chat, CONFIG_LOG_DEFAULT_LEVEL); + +static void bt_ready(int err) +{ + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + return; + } + + printk("Bluetooth initialized\n"); + + err = dk_leds_init(); + if (err) { + printk("Initializing LEDs failed (err %d)\n", err); + return; + } + + err = dk_buttons_init(NULL); + if (err) { + printk("Initializing buttons failed (err %d)\n", err); + return; + } + + err = bt_mesh_init(bt_mesh_dk_prov_init(), model_handler_init()); + if (err) { + printk("Initializing mesh failed (err %d)\n", err); + return; + } + + if (IS_ENABLED(CONFIG_SETTINGS)) { + settings_load(); + } + + /* This will be a no-op if settings_load() loaded provisioning info */ + bt_mesh_prov_enable(BT_MESH_PROV_ADV | BT_MESH_PROV_GATT); + + printk("Mesh initialized\n"); +} + +int main(void) +{ + int err; + + printk("Initializing...\n"); + + err = bt_enable(bt_ready); + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + } + + return 0; +} diff --git a/src/model_handler.c b/src/model_handler.c new file mode 100644 index 0000000..341cf88 --- /dev/null +++ b/src/model_handler.c @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include +#include + +#include +#include + +#include "chat_cli.h" +#include "model_handler.h" + +#include +LOG_MODULE_DECLARE(chat); + +static const struct shell *chat_shell; + +#define MAX_NODES 10 + +/******************************************************************************/ +/*************************** Health server setup ******************************/ +/******************************************************************************/ +/* Set up a repeating delayed work to blink the DK's LEDs when attention is + * requested. + */ +static struct k_work_delayable attention_blink_work; +static bool attention; +static volatile bool message_acknowledged = false; + +static void attention_blink(struct k_work *work) +{ + static int idx; + const uint8_t pattern[] = { + BIT(0) | BIT(1), + BIT(1) | BIT(2), + BIT(2) | BIT(3), + BIT(3) | BIT(0), + }; + + if (attention) { + dk_set_leds(pattern[idx++ % ARRAY_SIZE(pattern)]); + k_work_reschedule(&attention_blink_work, K_MSEC(30)); + } else { + dk_set_leds(DK_NO_LEDS_MSK); + } +} + +static void attention_on(const struct bt_mesh_model *mod) +{ + attention = true; + k_work_reschedule(&attention_blink_work, K_NO_WAIT); +} + +static void attention_off(const struct bt_mesh_model *mod) +{ + /* Will stop rescheduling blink timer */ + attention = false; +} + +static const struct bt_mesh_health_srv_cb health_srv_cb = { + .attn_on = attention_on, + .attn_off = attention_off, +}; + +static struct bt_mesh_health_srv health_srv = { + .cb = &health_srv_cb, +}; + +BT_MESH_HEALTH_PUB_DEFINE(health_pub, 0); + +/******************************************************************************/ +/***************************** Chat model setup *******************************/ +/******************************************************************************/ +struct presence_cache { + uint16_t addr; + enum bt_mesh_chat_cli_presence presence; +}; + +/* Cache of Presence values of other chat clients. */ +static struct presence_cache presence_cache[ + CONFIG_BT_MESH_CHAT_SAMPLE_PRESENCE_CACHE_SIZE]; + +static const uint8_t *presence_string[] = { + [BT_MESH_CHAT_CLI_PRESENCE_AVAILABLE] = "available", + [BT_MESH_CHAT_CLI_PRESENCE_AWAY] = "away", + [BT_MESH_CHAT_CLI_PRESENCE_DO_NOT_DISTURB] = "dnd", + [BT_MESH_CHAT_CLI_PRESENCE_INACTIVE] = "inactive", +}; + +/** + * Returns true if the specified address is an address of the local element. + */ +static bool address_is_local(const struct bt_mesh_model *mod, uint16_t addr) +{ + return bt_mesh_model_elem(mod)->rt->addr == addr; +} + +/** + * Returns true if the provided address is unicast address. + */ +static bool address_is_unicast(uint16_t addr) +{ + return (addr > 0) && (addr <= 0x7FFF); +} + +/** + * Returns true if the node is new or the presence status is different from + * the one stored in the cache. + */ +static bool presence_cache_entry_check_and_update(uint16_t addr, + enum bt_mesh_chat_cli_presence presence) +{ + static size_t presence_cache_head; + size_t i; + + /* Find address in cache. */ + for (i = 0; i < ARRAY_SIZE(presence_cache); i++) { + if (presence_cache[i].addr == addr) { + if (presence_cache[i].presence == presence) { + return false; + } + + /* Break since the node in the cache. */ + break; + } + } + + /* Not in cache. */ + if (i == ARRAY_SIZE(presence_cache)) { + for (i = 0; i < ARRAY_SIZE(presence_cache); i++) { + if (!presence_cache[i].addr) { + break; + } + } + + /* Cache is full. */ + if (i == ARRAY_SIZE(presence_cache)) { + i = presence_cache_head; + presence_cache_head = (presence_cache_head + 1) + % CONFIG_BT_MESH_CHAT_SAMPLE_PRESENCE_CACHE_SIZE; + } + } + + /* Update cache. */ + presence_cache[i].addr = addr; + presence_cache[i].presence = presence; + + return true; +} + +static void print_client_status(void); + +static void handle_chat_start(struct bt_mesh_chat_cli *chat) +{ + print_client_status(); +} + +static void handle_chat_presence(struct bt_mesh_chat_cli *chat, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_chat_cli_presence presence) +{ + if (address_is_local(chat->model, ctx->addr)) { + if (address_is_unicast(ctx->recv_dst)) { + shell_print(chat_shell, " are %s", + presence_string[presence]); + } + } else { + if (address_is_unicast(ctx->recv_dst)) { + shell_print(chat_shell, "<0x%04X> is %s", ctx->addr, + presence_string[presence]); + } else if (presence_cache_entry_check_and_update(ctx->addr, + presence)) { + shell_print(chat_shell, "<0x%04X> is now %s", + ctx->addr, + presence_string[presence]); + } + } +} + + + + + + + + +static void handle_chat_message(struct bt_mesh_chat_cli *chat, + struct bt_mesh_msg_ctx *ctx, + const uint8_t *msg) +{ + /* Don't print own messages. */ + if (address_is_local(chat->model, ctx->addr)) { + return; + } + + shell_print(chat_shell, "<0x%04X>: %s", ctx->addr, msg); +} + +struct node_message { + uint16_t addr; + uint8_t msg_count; + bool is_active; + int64_t last_seen; +}; + + +static struct node_message node_stats[MAX_NODES]; + + +static void handle_rpl_full(void) +{ + // Wait for any pending messages to complete + k_sleep(K_MSEC(1000)); + + // Reset node tracking + for (int i = 0; i < MAX_NODES; i++) { + node_stats[i].msg_count = 0; + node_stats[i].last_seen = k_uptime_get(); + } + + LOG_INF("RPL management completed"); +} + + +static void handle_chat_private_message(struct bt_mesh_chat_cli *chat, + struct bt_mesh_msg_ctx *ctx, + const uint8_t *msg) +{ + if (!ctx || !msg) { + LOG_ERR("Invalid message received"); + return; + } + + if (address_is_local(chat->model, ctx->addr)) { + return; + } + + int64_t current_time = k_uptime_get(); + int available_slot = -1; + + for (int i = 0; i < MAX_NODES; i++) { + if (node_stats[i].addr == ctx->addr) { + if ((current_time - node_stats[i].last_seen) > 5000) { + LOG_INF("Node 0x%04X reconnected", ctx->addr); + } + node_stats[i].msg_count++; + node_stats[i].last_seen = current_time; + node_stats[i].is_active = true; + LOG_INF("Message from 0x%04X (#%d): %s", + ctx->addr, node_stats[i].msg_count, msg); + return; + } + if (!node_stats[i].is_active && available_slot == -1) { + available_slot = i; + } + } + + if (available_slot != -1) { + node_stats[available_slot].addr = ctx->addr; + node_stats[available_slot].msg_count = 1; + node_stats[available_slot].last_seen = current_time; + node_stats[available_slot].is_active = true; + LOG_INF("New node 0x%04X registered: %s", ctx->addr, msg); + } else { + LOG_WRN("Node tracking full - Message from 0x%04X dropped", ctx->addr); + } +} + +static void handle_chat_message_reply(struct bt_mesh_chat_cli *chat, + struct bt_mesh_msg_ctx *ctx) +{ + message_acknowledged = true; + shell_print(chat_shell, "<0x%04X> received the message", ctx->addr); +} + +static const struct bt_mesh_chat_cli_handlers chat_handlers = { + .start = handle_chat_start, + .presence = handle_chat_presence, + .message = handle_chat_message, + .private_message = handle_chat_private_message, + .message_reply = handle_chat_message_reply, +}; + +/* .. include_startingpoint_model_handler_rst_1 */ +static struct bt_mesh_chat_cli chat = { + .handlers = &chat_handlers, +}; + +static struct bt_mesh_elem elements[] = { + BT_MESH_ELEM( + 1, + BT_MESH_MODEL_LIST( + BT_MESH_MODEL_CFG_SRV, + BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub)), + BT_MESH_MODEL_LIST(BT_MESH_MODEL_CHAT_CLI(&chat))), +}; +/* .. include_endpoint_model_handler_rst_1 */ + +static void print_client_status(void) +{ + if (!bt_mesh_is_provisioned()) { + shell_print(chat_shell, + "The mesh node is not provisioned. Please provision the mesh node before using the chat."); + } else { + shell_print(chat_shell, + "The mesh node is provisioned. The client address is 0x%04x.", + bt_mesh_model_elem(chat.model)->rt->addr); + } + + shell_print(chat_shell, "Current presence: %s", + presence_string[chat.presence]); +} + +static const struct bt_mesh_comp comp = { + .cid = CONFIG_BT_COMPANY_ID, + .elem = elements, + .elem_count = ARRAY_SIZE(elements), +}; + +/******************************************************************************/ +/******************************** Chat shell **********************************/ +/******************************************************************************/ +static int cmd_status(const struct shell *shell, size_t argc, char *argv[]) +{ + print_client_status(); + + return 0; +} + +static int cmd_message(const struct shell *shell, size_t argc, char *argv[]) +{ + int err; + + if (argc < 2) { + return -EINVAL; + } + + err = bt_mesh_chat_cli_message_send(&chat, argv[1]); + if (err) { + LOG_WRN("Failed to send message: %d", err); + } + + /* Print own messages in the chat. */ + shell_print(shell, ": %s", argv[1]); + + return 0; +} + +static int cmd_private_message(const struct shell *shell, size_t argc, + char *argv[]) +{ + uint16_t addr; + int err; + + if (argc < 3) { + return -EINVAL; + } + + addr = strtol(argv[1], NULL, 0); + + /* Print own message to the chat. */ + shell_print(shell, "%x: %s\n", addr, argv[2]); + + err = bt_mesh_chat_cli_private_message_send(&chat, addr, argv[2]); + if (err) { + LOG_WRN("Failed to publish message: %d", err); + } + + return 0; +} + +static int cmd_presence_set(const struct shell *shell, size_t argc, + char *argv[]) +{ + size_t i; + + if (argc < 2) { + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(presence_string); i++) { + if (!strcmp(argv[1], presence_string[i])) { + enum bt_mesh_chat_cli_presence presence; + int err; + + presence = i; + + err = bt_mesh_chat_cli_presence_set(&chat, presence); + if (err) { + LOG_WRN("Failed to update presence: %d", err); + } + + /* Print own presence in the chat. */ + shell_print(shell, "You are now %s", + presence_string[presence]); + + return 0; + } + } + + shell_print(shell, + "Unknown presence status: %s. Possible presence statuses:", + argv[1]); + for (i = 0; i < ARRAY_SIZE(presence_string); i++) { + shell_print(shell, "%s", presence_string[i]); + } + + return 0; +} + +static int cmd_presence_get(const struct shell *shell, size_t argc, + char *argv[]) +{ + uint16_t addr; + int err; + + if (argc < 2) { + return -EINVAL; + } + + addr = strtol(argv[1], NULL, 0); + + err = bt_mesh_chat_cli_presence_get(&chat, addr); + if (err) { + LOG_WRN("Failed to publish message: %d", err); + } + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(presence_cmds, + SHELL_CMD_ARG(set, NULL, + "Set presence of the current client ", + cmd_presence_set, 2, 0), + SHELL_CMD_ARG(get, NULL, + "Get presence status of the remote node ", + cmd_presence_get, 2, 0), + SHELL_SUBCMD_SET_END +); + +static int cmd_presence(const struct shell *shell, size_t argc, char *argv[]) +{ + if (argc == 1) { + shell_help(shell); + /* shell returns 1 when help is printed */ + return 1; + } + + if (argc != 3) { + return -EINVAL; + } + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(chat_cmds, + SHELL_CMD_ARG(status, NULL, "Print client status", cmd_status, 1, 0), + SHELL_CMD(presence, &presence_cmds, "Presence commands", cmd_presence), + SHELL_CMD_ARG(private, NULL, + "Send a private text message to a client ", + cmd_private_message, 3, 0), + SHELL_CMD_ARG(msg, NULL, "Send a text message to the chat ", + cmd_message, 2, 0), + SHELL_SUBCMD_SET_END +); + +static int cmd_chat(const struct shell *shell, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help(shell); + /* shell returns 1 when help is printed */ + return 1; + } + + shell_error(shell, "%s unknown parameter: %s", argv[0], argv[1]); + + return -EINVAL; +} + +SHELL_CMD_ARG_REGISTER(chat, &chat_cmds, "Bluetooth Mesh Chat Client commands", + cmd_chat, 1, 1); + + +static struct k_work_delayable message_timer; + +static uint32_t msg_counter = 0; + +static uint16_t get_node_address(void) +{ + uint16_t addr = bt_mesh_model_elem(chat.model)->rt->addr; + LOG_INF("Node unicast address: 0x%04x", addr); + return addr; +} + +static bool has_publish_address(void) +{ + const struct bt_mesh_model *model = chat.model; + if (model && model->pub && model->pub->addr != BT_MESH_ADDR_UNASSIGNED) { + LOG_INF("Publish address: 0x%04x", model->pub->addr); + return true; + } + return false; +} + +static void send_periodic_message(struct k_work *work) +{ + if (!has_publish_address()) { + LOG_INF("No publish address assigned yet"); + k_work_reschedule(&message_timer, K_SECONDS(5)); + return; + } + + const char fixed_message[255]; // change the size as needed for payload + uint16_t target_addr = 0x0105; + + snprintf(fixed_message, sizeof(fixed_message), + "%lu 0x%04x dammavalamsarathchandrasrivatsandammavalamsarathchandrasrivatsandammavalamsarathchandrasrivatsandammavalamsarathchandrasrivatsandammavalamsarathchandrasrivatsandammavalamsarathchandrasrivatsandammavalamsarathchandradammavalamsarathchandrasrivatsandammavalamsarathchandrasrivatsandammavalamsarathchandrasrivatsandammavalamsarathchandrasrivatsandammavalamsarathchandrasrivatsa", msg_counter, get_node_address()); + + + // First attempt + message_acknowledged = false; + int err = bt_mesh_chat_cli_private_message_send(&chat, target_addr, fixed_message); + if (err) { + LOG_WRN("First attempt failed, retrying..."); + k_sleep(K_MSEC(10000)); + + // Second attempt + message_acknowledged = false; + err = bt_mesh_chat_cli_private_message_send(&chat, target_addr, fixed_message); + } + else + msg_counter++; + + + if (message_acknowledged) { + shell_print(chat_shell, "Message delivered successfully"); + } + + k_work_reschedule(&message_timer, K_SECONDS(20)); +} + + +// change array size and constant value for payload in 519 & 523. +// change publish time value in line 545 +// comment lines 562 & 563 for receiver nodes 105 +// uncomment lines 562 & 563 for transmitter nodes 120,121,122,123,124 +// + +/******************************************************************************/ +/******************************** Public API **********************************/ +/******************************************************************************/ +const struct bt_mesh_comp *model_handler_init(void) +{ + k_work_init_delayable(&attention_blink_work, attention_blink); + + // k_work_init_delayable(&message_timer, send_periodic_message); + // k_work_schedule(&message_timer, K_NO_WAIT); + + chat_shell = shell_backend_uart_get_ptr(); + printk(">>> Bluetooth Mesh Chat sample <<<\n"); + + + + return ∁ +}