diff --git a/doc/connectivity/bluetooth/api/mesh/shell.rst b/doc/connectivity/bluetooth/api/mesh/shell.rst index 4426f02b668..9c05088931a 100644 --- a/doc/connectivity/bluetooth/api/mesh/shell.rst +++ b/doc/connectivity/bluetooth/api/mesh/shell.rst @@ -903,6 +903,444 @@ The Health Client may use the general messages parameters set by ``mesh target d * ``timer``: Duration of the attention state, in seconds (``0`` to ``255``) + +Binary Large Object (BLOB) Transfer Client model +------------------------------------------------ + +The :ref:`bluetooth_mesh_blob_cli` can be added to the mesh shell by enabling the :kconfig:option:`CONFIG_BT_MESH_BLOB_CLI` option, and disabling the :kconfig:option:`CONFIG_BT_MESH_DFU_CLI` option. + +``mesh models blob cli target `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Add a Target node for the next BLOB transfer. + + * ``addr``: Unicast address of the Target node's BLOB Transfer Server model. + + +``mesh models blob cli bounds []`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get the total boundary parameters of all Target nodes. + + * ``group``: Optional group address to use when communicating with Target nodes. If omitted, the BLOB Transfer Client will address each Target node individually. + + +``mesh models blob cli tx [ []]`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Perform a BLOB transfer to Target nodes. The BLOB Transfer Client will send a dummy BLOB to all Target nodes, then post a message when the transfer is completed. Note that all Target nodes must first be configured to receive the transfer using the ``mesh models blob srv rx`` command. + + * ``id``: 64-bit BLOB transfer ID. + * ``size``: Size of the BLOB in bytes. + * ``block size log`` Logarithmic representation of the BLOB's block size. The final block size will be ``1 << block size log`` bytes. + * ``chunk size``: Chunk size in bytes. + * ``group``: Optional group address to use when communicating with Target nodes. If omitted or set to 0, the BLOB Transfer Client will address each Target node individually. + * ``mode``: BLOB transfer mode to use. Must be either ``push`` (Push BLOB Transfer Mode) or ``pull`` (Pull BLOB Transfer Mode). If omitted, ``push`` will be used by default. + + +``mesh models blob cli tx-cancel`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Cancel an ongoing BLOB transfer. + +``mesh models blob cli tx-get [group]`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Determine the progress of a previously running BLOB transfer. Can be used when not performing a BLOB transfer. + + * ``group``: Optional group address to use when communicating with Target nodes. If omitted or set to 0, the BLOB Transfer Client will address each Target node individually. + + +``mesh models blob cli tx-suspend`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Suspend the ongoing BLOB transfer. + + +``mesh models blob cli tx-resume`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Resume the suspended BLOB transfer. + +``mesh models blob cli instance-set `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Use the BLOB Transfer Client model instance on the specified element when using the other BLOB Transfer Client model commands. + + * ``elem_idx``: The element on which to find the BLOB Transfer Client model instance to use. + +``mesh models blob cli instance-get-all`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get a list of all BLOB Transfer Client model instances on the node. + + +BLOB Transfer Server model +-------------------------- + +The :ref:`bluetooth_mesh_blob_srv` can be added to the mesh shell by enabling the :kconfig:option:`CONFIG_BT_MESH_BLOB_SRV` option. The BLOB Transfer Server model is capable of receiving any BLOB data, but the implementation in the mesh shell will discard the incoming data. + + +``mesh models blob srv rx []`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Prepare to receive a BLOB transfer. + + * ``id``: 64-bit BLOB transfer ID to receive. + * ``timeout base``: Optional additional time to wait for client messages, in 10-second increments. + + +``mesh models blob srv rx-cancel`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Cancel an ongoing BLOB transfer. + +``mesh models blob srv instance-set `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Use the BLOB Transfer Server model instance on the specified element when using the other BLOB Transfer Server model commands. + + * ``elem_idx``: The element on which to find the BLOB Transfer Server model instance to use. + +``mesh models blob srv instance-get-all`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get a list of all BLOB Transfer Server model instances on the node. + + +Firmware Update Client model +---------------------------- + +The Firmware Update Client model can be added to the mesh shell by enabling configuration options :kconfig:option:`CONFIG_BT_MESH_BLOB_CLI` and :kconfig:option:`CONFIG_BT_MESH_DFU_CLI`. The Firmware Update Client demonstrates the firmware update Distributor role by transferring a dummy firmware update to a set of Target nodes. + + +``mesh models dfu slot add [ [ []]]`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Add a virtual DFU image slot that can be transferred as a DFU image. The image slot will be assigned an image slot index, which is printed as a response, and can be used to reference the slot in other commands. To update the image slot, remove it using the ``mesh models dfu slot del`` shell command and then add it again. + + * ``size``: DFU image slot size in bytes. + * ``fwid``: Optional firmware ID, formatted as a hexstring. + * ``metadata``: Optional firmware metadata, formatted as a hexstring. + * ``uri``: Optional URI for the firmware. + + +``mesh models dfu slot del `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Delete the DFU image slot at the given index. + + * ``slot idx``: Index of the slot to delete. + + +``mesh models dfu slot get `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get all available information about a DFU image slot. + + * ``slot idx``: Index of the slot to get. + + +``mesh models dfu cli target `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Add a Target node. + + * ``addr``: Unicast address of the Target node. + * ``img idx``: Image index to address on the Target node. + + +``mesh models dfu cli target-state`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Check the DFU Target state of the device at the configured destination address. + + +``mesh models dfu cli target-imgs []`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get a list of DFU images on the device at the configured destination address. + + * ``max count``: Optional maximum number of images to return. If omitted, there's no limit on the number of returned images. + + +``mesh models dfu cli target-check `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Check whether the device at the configured destination address will accept a DFU transfer from the given DFU image slot to the Target node's DFU image at the given index, and what the effect would be. + + * ``slot idx``: Index of the local DFU image slot to check. + * ``target img idx``: Index of the Target node's DFU image to check. + + +``mesh models dfu cli send []`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Start a DFU transfer to all added Target nodes. + + * ``slot idx``: Index of the local DFU image slot to send. + * ``group``: Optional group address to use when communicating with the Target nodes. If omitted, the Firmware Update Client will address each Target node individually. + + +``mesh models dfu cli apply`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Apply the most recent DFU transfer on all Target nodes. Can only be called after a DFU transfer is completed. + + +``mesh models dfu cli confirm`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Confirm that the most recent DFU transfer was successfully applied on all Target nodes. Can only be called after a DFU transfer is completed and applied. + + +``mesh models dfu cli progress`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Check the progress of the current transfer. + + +``mesh models dfu cli suspend`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Suspend the ongoing DFU transfer. + + +``mesh models dfu cli resume`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Resume the suspended DFU transfer. + +``mesh models dfu srv progress`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Check the progress of the current transfer. + +``mesh models dfu cli instance-set `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Use the Firmware Update Client model instance on the specified element when using the other Firmware Update Client model commands. + + * ``elem_idx``: The element on which to find the Firmware Update Client model instance to use. + +``mesh models dfu cli instance-get-all`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get a list of all Firmware Update Client model instances on the node. + + +Firmware Update Server model +---------------------------- + +The Firmware Update Server model can be added to the mesh shell by enabling configuration options :kconfig:option:`CONFIG_BT_MESH_BLOB_SRV` and :kconfig:option:`CONFIG_BT_MESH_DFU_SRV`. The Firmware Update Server demonstrates the firmware update Target role by accepting any firmware update. The mesh shell Firmware Update Server will discard the incoming firmware data, but otherwise behave as a proper firmware update Target node. + + +``mesh models dfu srv applied`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Mark the most recent DFU transfer as applied. Can only be called after a DFU transfer is completed, and the Distributor has requested that the transfer is applied. + + As the mesh shell Firmware Update Server doesn't actually apply the incoming firmware image, this command can be used to emulate an applied status, to notify the Distributor that the transfer was successful. + + +``mesh models dfu srv progress`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Check the progress of the current transfer. + +``mesh models dfu srv rx-cancel`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Cancel incoming DFU transfer. + +``mesh models dfu srv instance-set `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Use the Firmware Update Server model instance on the specified element when using the other Firmware Update Server model commands. + + * ``elem_idx``: The element on which to find the Firmware Update Server model instance to use. + +``mesh models dfu srv instance-get-all`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get a list of all Firmware Update Server model instances on the node. + + +.. _bluetooth_mesh_shell_dfd_server: + +Firmware Distribution Server model +---------------------------------- + +The Firmware Distribution Server model commands can be added to the mesh shell by enabling the :kconfig:option:`CONFIG_BT_MESH_DFD_SRV` configuration option. +The shell commands for this model mirror the messages sent to the server by a Firmware Distribution Client model. +To use these commands, a Firmware Distribution Server must be instantiated by the application. + +``mesh models dfd receivers-add ,[;,]...`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Add receivers to the Firmware Distribution Server. + Supply receivers as a list of comma-separated addr,fw_idx pairs, separated by semicolons, for example, ``0x0001,0;0x0002,0;0x0004,1``. + Do not use spaces in the receiver list. + Repeated calls to this command will continue populating the receivers list until ``mesh models dfd receivers-delete-all`` is called. + + * ``addr``: Address of the receiving node(s). + * ``fw_idx``: Index of the firmware slot to send to ``addr``. + +``mesh models dfd receivers-delete-all`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Delete all receivers from the server. + +``mesh models dfd receivers-get `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get a list of info about firmware receivers. + + * ``first``: Index of the first receiver to get from the receiver list. + * ``count``: The number of recievers for which to get info. + +``mesh models dfd capabilities-get`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get the capabilities of the server. + +``mesh models dfd get`` +^^^^^^^^^^^^^^^^^^^^^^^ + + Get information about the current distribution state, phase and the transfer parameters. + +``mesh models dfd start [ [ [ [ []]]]]`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Start the firmware distribution. + + * ``app_idx``: Application index to use for sending. The common application key should be bound to the Firmware Update and BLOB Transfer models on the Distributor and Target nodes. + * ``slot_idx``: Index of the local image slot to send. + * ``group``: Optional group address to use when communicating with the Target nodes. If omitted, the Firmware Distribution Server will address each Target node individually. To keep addressing each Target node individually while changing other arguments, set this argument value to 0. + * ``policy_apply``: Optional field that corresponds to the update policy. Setting this to ``true`` will make the Firmware Distribution Server apply the image immediately after the transfer is completed. + * ``ttl``: Optional. TTL value to use when sending. Defaults to configured default TTL. + * ``timeout_base``: Optional additional value used to calculate timeout values in the firmware distribution process. See :ref:`bluetooth_mesh_blob_timeout` for information about how ``timeout_base`` is used to calculate the transfer timeout. Defaults to 0. + * ``xfer_mode``: Optional BLOB transfer mode. 1 = Push mode (Push BLOB Transfer Mode), 2 = Pull mode (Pull BLOB Transfer Mode). Defaults to Push mode. + +``mesh models dfd suspend`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Suspends the ongoing distribution. + +``mesh models dfd cancel`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Cancel the ongoing distribution. + +``mesh models dfd apply`` +^^^^^^^^^^^^^^^^^^^^^^^^^ + + Apply the distributed firmware. + +``mesh models dfd fw-get `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get information about the firmware image uploaded to the server. + + * ``fwid``: Firmware ID of the image to get. + +``mesh models dfd fw-get-by-idx `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get information about the firmware image uploaded to the server in a specific slot. + + * ``idx``: Index of the slot to get the image from. + +``mesh models dfd fw-delete `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Delete a firmware image from the server. + + * ``fwid``: Firmware ID of the image to delete. + +``mesh models dfd fw-delete-all`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Delete all firmware images from the server. + +``mesh models dfd instance-set `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Use the Firmware Distribution Server model instance on the specified element when using the other Firmware Distribution Server model commands. + + * ``elem_idx``: The element on which to find the Firmware Distribution Server model instance to use. + +``mesh models dfd instance-get-all`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Get a list of all Firmware Distribution Server model instances on the node. + + +.. _bluetooth_mesh_shell_dfu_metadata: + +DFU metadata +------------ + +The DFU metadata commands allow generating metadata that can be used by a Target node to check the firmware before accepting it. The commands are enabled through the :kconfig:option:`CONFIG_BT_MESH_DFU_METADATA` configuration option. + +``mesh models dfu metadata comp-clear`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Clear the stored composition data to be used for the Target node. + +``mesh models dfu metadata comp-add `` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Create a header of the Composition Data Page 0. + + * ``cid``: Company identifier assigned by Bluetooth SIG. + * ``pid``: Vendor-assigned product identifier. + * ``vid``: Vendor-assigned version identifier. + * ``crpl``: The size of the replay protection list. + * ``features``: Features supported by the node in bit field format: + * ``0``: Relay. + * ``1``: Proxy. + * ``2``: Friend. + * ``3``: Low Power. + +``mesh models dfu metadata comp-elem-add {| }...`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Add element description of the Target node. + + * ``loc``: Element location. + * ``nums``: Number of SIG models instantiated on the element. + * ``numv``: Number of vendor models instantiated on the element. + * ``sig model id``: SIG Model ID. + * ``vnd company id``: Vendor model company identifier. + * ``vnd model id``: Vendor model identifier. + +``mesh models dfu metadata comp-hash-get [<128-bit key>]`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Generate a hash of the stored Composition Data to be used in metadata. + + * ``128-bit key``: Optional 128-bit key to be used to generate the hash. + +``mesh models dfu metadata encode []`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Encode metadata for the DFU. + + * ``major``: Major version of the firmware. + * ``minor``: Minor version of the firmware. + * ``rev``: Revision number of the firmware. + * ``build_num``: Build number. + * ``size``: Size of the signed bin file. + * ``core type``: New firmware core type in bit field format: + * ``0``: Application core. + * ``1``: Network core. + * ``2``: Applications specific BLOB. + * ``hash``: Hash of the composition data generated using ``mesh models dfu metadata comp-hash-get`` command. + * ``elems``: Number of elements on the new firmware. + * ``user data``: User data supplied with the metadata. + + Configuration database ====================== diff --git a/include/zephyr/bluetooth/mesh/shell.h b/include/zephyr/bluetooth/mesh/shell.h index 19016d3ac2d..6a906311f93 100644 --- a/include/zephyr/bluetooth/mesh/shell.h +++ b/include/zephyr/bluetooth/mesh/shell.h @@ -41,6 +41,18 @@ extern struct bt_mesh_health_srv bt_mesh_shell_health_srv; /** @brief External reference to health client */ extern struct bt_mesh_health_cli bt_mesh_shell_health_cli; +/** @brief External reference to Firmware Update Server */ +extern struct bt_mesh_dfu_srv bt_mesh_shell_dfu_srv; + +/** @brief External reference to Firmware Update Client */ +extern struct bt_mesh_dfu_cli bt_mesh_shell_dfu_cli; + +/** @brief External reference to BLOB Transfer Server */ +extern struct bt_mesh_blob_srv bt_mesh_shell_blob_srv; + +/** @brief External reference to BLOB Transfer Client */ +extern struct bt_mesh_blob_cli bt_mesh_shell_blob_cli; + /** @brief External reference to provisioning handler. */ extern struct bt_mesh_prov bt_mesh_shell_prov; diff --git a/subsys/bluetooth/mesh/cfg_srv.c b/subsys/bluetooth/mesh/cfg_srv.c index 4b684ed269c..ccd63018676 100644 --- a/subsys/bluetooth/mesh/cfg_srv.c +++ b/subsys/bluetooth/mesh/cfg_srv.c @@ -78,7 +78,7 @@ static int comp_add_elem(struct net_buf_simple *buf, struct bt_mesh_elem *elem, return 0; } -static int comp_get_page_0(struct net_buf_simple *buf) +int bt_mesh_comp_get_page_0(struct net_buf_simple *buf) { uint16_t feat = 0U; const struct bt_mesh_comp *comp; @@ -140,7 +140,7 @@ static int dev_comp_data_get(struct bt_mesh_model *model, bt_mesh_model_msg_init(&sdu, OP_DEV_COMP_DATA_STATUS); net_buf_simple_add_u8(&sdu, page); - err = comp_get_page_0(&sdu); + err = bt_mesh_comp_get_page_0(&sdu); if (err) { LOG_ERR("Unable to get composition page 0"); return err; diff --git a/subsys/bluetooth/mesh/foundation.h b/subsys/bluetooth/mesh/foundation.h index 7fb3ddf3188..1399c03f8b6 100644 --- a/subsys/bluetooth/mesh/foundation.h +++ b/subsys/bluetooth/mesh/foundation.h @@ -116,6 +116,8 @@ void bt_mesh_attention(struct bt_mesh_model *model, uint8_t time); #include +int bt_mesh_comp_get_page_0(struct net_buf_simple *buf); + static inline void key_idx_pack(struct net_buf_simple *buf, uint16_t idx1, uint16_t idx2) { diff --git a/subsys/bluetooth/mesh/shell/CMakeLists.txt b/subsys/bluetooth/mesh/shell/CMakeLists.txt index 5af1d74e391..5660fe809fc 100644 --- a/subsys/bluetooth/mesh/shell/CMakeLists.txt +++ b/subsys/bluetooth/mesh/shell/CMakeLists.txt @@ -8,3 +8,15 @@ zephyr_library_sources( zephyr_library_sources_ifdef(CONFIG_BT_MESH_SHELL_HEALTH_CLI health.c) zephyr_library_sources_ifdef(CONFIG_BT_MESH_SHELL_CFG_CLI cfg.c) + +if(CONFIG_BT_MESH_SHELL_BLOB_CLI OR CONFIG_BT_MESH_SHELL_BLOB_SRV OR + CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH) +zephyr_library_sources(blob.c) +endif() + +if(CONFIG_BT_MESH_SHELL_DFU_SLOT OR CONFIG_BT_MESH_SHELL_DFU_METADATA OR + CONFIG_BT_MESH_SHELL_DFU_CLI OR CONFIG_BT_MESH_SHELL_DFU_SRV) +zephyr_library_sources(dfu.c) +endif() + +zephyr_library_sources_ifdef(CONFIG_BT_MESH_SHELL_DFD_SRV dfd.c) diff --git a/subsys/bluetooth/mesh/shell/Kconfig b/subsys/bluetooth/mesh/shell/Kconfig index acf6635ef8c..a0b28c4e569 100644 --- a/subsys/bluetooth/mesh/shell/Kconfig +++ b/subsys/bluetooth/mesh/shell/Kconfig @@ -20,7 +20,7 @@ config BT_MESH_SHELL_PROV This options enables support for provisioning shell commands. config BT_MESH_SHELL_PROV_CTX_INSTANCE - bool "Support for Shell provisioning context instance" + bool "Support for shell provisioning context instance" depends on BT_MESH_SHELL_PROV help This option enables the provisioning context instance in the @@ -50,7 +50,7 @@ config BT_MESH_SHELL_TEST This option enables support for test mesh shell commands. config BT_MESH_SHELL_HEALTH_SRV_INSTANCE - bool "Support for Shell Health Server model instance" + bool "Support for shell Health Server model instance" depends on BT_MESH_SHELL_TEST help This option enables Health Server model instance in the @@ -80,4 +80,60 @@ config BT_MESH_SHELL_CFG_CLI help This option enables support of Configuration Client shell commands. +config BT_MESH_SHELL_DFD_SRV + bool "Support for Firmware Distribution Server shell commands" + depends on BT_MESH_DFD_SRV + default y + help + Firmware Distribution Server shell support. + +config BT_MESH_SHELL_DFU_CLI + bool "Support for Firmware Update Client shell commands" + depends on BT_MESH_DFU_CLI + default y + help + Firmware Update Client shell support. + +config BT_MESH_SHELL_DFU_SRV + bool "Support for Firmware Update Server shell commands" + depends on BT_MESH_DFU_SRV + default y + help + Firmware Update Server shell support. + +config BT_MESH_SHELL_DFU_SLOT + bool "Support for DFU slot API shell commands" + depends on BT_MESH_DFU_SLOTS + default y if BT_MESH_SHELL_DFU_CLI || BT_MESH_SHELL_DFD_SRV + help + DFU slot API shell support. + +config BT_MESH_SHELL_DFU_METADATA + bool "Support for DFU metadata shell commands" + depends on BT_MESH_DFU_METADATA + default y + help + DFU metadata shell support. + +config BT_MESH_SHELL_BLOB_CLI + bool "Support for BLOB Transfer Client shell commands" + depends on BT_MESH_BLOB_CLI + default y + help + BLOB Transfer Client shell support. + +config BT_MESH_SHELL_BLOB_SRV + bool "Support for BLOB Transfer Server shell commands" + depends on BT_MESH_BLOB_SRV + default y + help + BLOB Transfer Server shell support. + +config BT_MESH_SHELL_BLOB_IO_FLASH + bool "Support for BLOB IO flash stream shell commands" + depends on BT_MESH_BLOB_IO_FLASH + default y + help + BLOB Transfer Client shell support. + endif # BT_MESH_SHELL diff --git a/subsys/bluetooth/mesh/shell/blob.c b/subsys/bluetooth/mesh/shell/blob.c new file mode 100644 index 00000000000..763314ea5d4 --- /dev/null +++ b/subsys/bluetooth/mesh/shell/blob.c @@ -0,0 +1,611 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "blob.h" + +#include +#include +#include +#include + +#include "utils.h" + +extern const struct shell *bt_mesh_shell_ctx_shell; + +/*************************************************************************************************** + * Implementation of models' instances + **************************************************************************************************/ + +static uint8_t blob_rx_sum; +bool bt_mesh_shell_blob_valid; +static const char *blob_data = "blob"; + +static int blob_io_open(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + enum bt_mesh_blob_io_mode mode) +{ + blob_rx_sum = 0; + bt_mesh_shell_blob_valid = true; + return 0; +} + +static int blob_chunk_wr(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + const struct bt_mesh_blob_chunk *chunk) +{ + int i; + + for (i = 0; i < chunk->size; ++i) { + blob_rx_sum += chunk->data[i]; + if (chunk->data[i] != + blob_data[(i + chunk->offset) % sizeof(blob_data)]) { + bt_mesh_shell_blob_valid = false; + } + } + + return 0; +} + +static int blob_chunk_rd(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + const struct bt_mesh_blob_chunk *chunk) +{ + for (int i = 0; i < chunk->size; ++i) { + chunk->data[i] = + blob_data[(i + chunk->offset) % sizeof(blob_data)]; + } + + return 0; +} + +static const struct bt_mesh_blob_io dummy_blob_io = { + .open = blob_io_open, + .rd = blob_chunk_rd, + .wr = blob_chunk_wr, +}; + +const struct bt_mesh_blob_io *bt_mesh_shell_blob_io; + +#if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) + +static struct { + struct bt_mesh_blob_cli_inputs inputs; + struct bt_mesh_blob_target targets[32]; + struct bt_mesh_blob_target_pull pull[32]; + uint8_t target_count; + struct bt_mesh_blob_xfer xfer; +} blob_cli_xfer; + +static void blob_cli_lost_target(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target, + enum bt_mesh_blob_status reason) +{ + shell_print(bt_mesh_shell_ctx_shell, "Mesh Blob: Lost target 0x%04x (reason: %u)", + target->addr, reason); +} + +static void blob_cli_caps(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_caps *caps) +{ + static const char * const modes[] = { + "none", + "push", + "pull", + "all", + }; + + if (!caps) { + shell_print(bt_mesh_shell_ctx_shell, + "None of the targets can be used for BLOB transfer"); + return; + } + + shell_print(bt_mesh_shell_ctx_shell, "Mesh BLOB: capabilities:"); + shell_print(bt_mesh_shell_ctx_shell, "\tMax BLOB size: %u bytes", caps->max_size); + shell_print(bt_mesh_shell_ctx_shell, "\tBlock size: %u-%u (%u-%u bytes)", + caps->min_block_size_log, caps->max_block_size_log, + 1 << caps->min_block_size_log, + 1 << caps->max_block_size_log); + shell_print(bt_mesh_shell_ctx_shell, "\tMax chunks: %u", caps->max_chunks); + shell_print(bt_mesh_shell_ctx_shell, "\tChunk size: %u", caps->max_chunk_size); + shell_print(bt_mesh_shell_ctx_shell, "\tMTU size: %u", caps->mtu_size); + shell_print(bt_mesh_shell_ctx_shell, "\tModes: %s", modes[caps->modes]); +} + +static void blob_cli_end(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_xfer *xfer, bool success) +{ + if (success) { + shell_print(bt_mesh_shell_ctx_shell, "Mesh BLOB transfer complete."); + } else { + shell_print(bt_mesh_shell_ctx_shell, "Mesh BLOB transfer failed."); + } +} + +static uint8_t get_progress(const struct bt_mesh_blob_xfer_info *info) +{ + uint8_t total_blocks; + uint8_t blocks_not_rxed = 0; + uint8_t blocks_not_rxed_size; + int i; + + total_blocks = ceiling_fraction(info->size, 1U << info->block_size_log); + + blocks_not_rxed_size = ceiling_fraction(total_blocks, 8); + + for (i = 0; i < blocks_not_rxed_size; i++) { + blocks_not_rxed += info->missing_blocks[i % 8] & (1 << (i % 8)); + } + + return (total_blocks - blocks_not_rxed) / total_blocks; +} + +static void xfer_progress(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target, + const struct bt_mesh_blob_xfer_info *info) +{ + uint8_t progress = get_progress(info); + + shell_print(bt_mesh_shell_ctx_shell, + "BLOB transfer progress received from target 0x%04x:\n" + "\tphase: %d\n" + "\tprogress: %u%%", + target->addr, info->phase, progress); +} + +static void xfer_progress_complete(struct bt_mesh_blob_cli *cli) +{ + shell_print(bt_mesh_shell_ctx_shell, "Determine BLOB transfer progress procedure complete"); +} + +static const struct bt_mesh_blob_cli_cb blob_cli_handlers = { + .lost_target = blob_cli_lost_target, + .caps = blob_cli_caps, + .end = blob_cli_end, + .xfer_progress = xfer_progress, + .xfer_progress_complete = xfer_progress_complete, +}; + +struct bt_mesh_blob_cli bt_mesh_shell_blob_cli = { + .cb = &blob_cli_handlers +}; + +#endif /* CONFIG_BT_MESH_SHELL_BLOB_CLI */ + +#if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) + +static int64_t blob_time; + +static int blob_srv_start(struct bt_mesh_blob_srv *srv, + struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_blob_xfer *xfer) +{ + shell_print(bt_mesh_shell_ctx_shell, "BLOB start"); + blob_time = k_uptime_get(); + return 0; +} + +static void blob_srv_end(struct bt_mesh_blob_srv *srv, uint64_t id, + bool success) +{ + if (success) { + int64_t duration = k_uptime_delta(&blob_time); + + shell_print(bt_mesh_shell_ctx_shell, "BLOB completed in %u.%03u s", + (uint32_t)(duration / MSEC_PER_SEC), + (uint32_t)(duration % MSEC_PER_SEC)); + } else { + shell_print(bt_mesh_shell_ctx_shell, "BLOB cancelled"); + } +} + +static const struct bt_mesh_blob_srv_cb blob_srv_cb = { + .start = blob_srv_start, + .end = blob_srv_end, +}; + +struct bt_mesh_blob_srv bt_mesh_shell_blob_srv = { + .cb = &blob_srv_cb +}; + +#endif /* CONFIG_BT_MESH_SHELL_BLOB_SRV */ + +void bt_mesh_shell_blob_cmds_init(void) +{ + bt_mesh_shell_blob_io = &dummy_blob_io; +} + +/*************************************************************************************************** + * Shell Commands + **************************************************************************************************/ + +#if defined(CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH) + +static struct bt_mesh_blob_io_flash blob_flash_stream; + +static int cmd_flash_stream_set(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t area_id; + uint32_t offset = 0; + int err = 0; + + if (argc < 2) { + return -EINVAL; + } + + area_id = shell_strtoul(argv[1], 0, &err); + + if (argc >= 3) { + offset = shell_strtoul(argv[2], 0, &err); + } + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + err = bt_mesh_blob_io_flash_init(&blob_flash_stream, area_id, offset); + if (err) { + shell_error(sh, "Failed to init BLOB IO Flash module: %d\n", err); + } + + bt_mesh_shell_blob_io = &blob_flash_stream.io; + + shell_print(sh, "Flash stream is initialized with area %u, offset: %u", area_id, offset); + + return 0; +} + +static int cmd_flash_stream_unset(const struct shell *sh, size_t argc, char *argv[]) +{ + bt_mesh_shell_blob_io = &dummy_blob_io; + return 0; +} + +#endif /* CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH */ + +#if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) + +static struct bt_mesh_model *mod_cli; + +static void blob_cli_inputs_prepare(uint16_t group) +{ + int i; + + blob_cli_xfer.inputs.ttl = BT_MESH_TTL_DEFAULT; + blob_cli_xfer.inputs.group = group; + blob_cli_xfer.inputs.app_idx = bt_mesh_shell_target_ctx.app_idx; + sys_slist_init(&blob_cli_xfer.inputs.targets); + + for (i = 0; i < blob_cli_xfer.target_count; ++i) { + /* Reset target context. */ + uint16_t addr = blob_cli_xfer.targets[i].addr; + + memset(&blob_cli_xfer.targets[i], 0, sizeof(struct bt_mesh_blob_target)); + memset(&blob_cli_xfer.pull[i], 0, sizeof(struct bt_mesh_blob_target_pull)); + blob_cli_xfer.targets[i].addr = addr; + blob_cli_xfer.targets[i].pull = &blob_cli_xfer.pull[i]; + + sys_slist_append(&blob_cli_xfer.inputs.targets, + &blob_cli_xfer.targets[i].n); + } +} + +static int cmd_tx(const struct shell *sh, size_t argc, char *argv[]) +{ + uint16_t group; + int err = 0; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { + return -ENODEV; + } + + blob_cli_xfer.xfer.id = shell_strtoul(argv[1], 0, &err); + blob_cli_xfer.xfer.size = shell_strtoul(argv[2], 0, &err); + blob_cli_xfer.xfer.block_size_log = shell_strtoul(argv[3], 0, &err); + blob_cli_xfer.xfer.chunk_size = shell_strtoul(argv[4], 0, &err); + + if (argc >= 6) { + group = shell_strtoul(argv[5], 0, &err); + } else { + group = BT_MESH_ADDR_UNASSIGNED; + } + + if (argc < 7 || !strcmp(argv[6], "push")) { + blob_cli_xfer.xfer.mode = BT_MESH_BLOB_XFER_MODE_PUSH; + } else if (!strcmp(argv[6], "pull")) { + blob_cli_xfer.xfer.mode = BT_MESH_BLOB_XFER_MODE_PULL; + } else { + shell_print(sh, "Mode must be either push or pull"); + return -EINVAL; + } + + if (argc >= 8) { + blob_cli_xfer.inputs.timeout_base = shell_strtoul(argv[7], 0, &err); + } else { + blob_cli_xfer.inputs.timeout_base = 0; + } + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + if (!blob_cli_xfer.target_count) { + shell_print(sh, "Failed: No targets"); + return 0; + } + + blob_cli_inputs_prepare(group); + + shell_print(sh, + "Sending transfer 0x%x (mode: %s, %u bytes) to 0x%04x", + (uint32_t)blob_cli_xfer.xfer.id, + blob_cli_xfer.xfer.mode == BT_MESH_BLOB_XFER_MODE_PUSH ? + "push" : + "pull", + blob_cli_xfer.xfer.size, group); + + err = bt_mesh_blob_cli_send((struct bt_mesh_blob_cli *)mod_cli->user_data, + &blob_cli_xfer.inputs, + &blob_cli_xfer.xfer, bt_mesh_shell_blob_io); + if (err) { + shell_print(sh, "BLOB transfer TX failed (err: %d)", err); + } + + return 0; +} + +static int cmd_target(const struct shell *sh, size_t argc, char *argv[]) +{ + struct bt_mesh_blob_target *t; + int err = 0; + + if (blob_cli_xfer.target_count == + ARRAY_SIZE(blob_cli_xfer.targets)) { + shell_print(sh, "No more room"); + return 0; + } + + t = &blob_cli_xfer.targets[blob_cli_xfer.target_count]; + t->addr = shell_strtoul(argv[1], 0, &err); + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + shell_print(sh, "Added target 0x%04x", t->addr); + + blob_cli_xfer.target_count++; + return 0; +} + +static int cmd_caps(const struct shell *sh, size_t argc, char *argv[]) +{ + uint16_t group; + int err = 0; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { + return -ENODEV; + } + + shell_print(sh, "Retrieving transfer capabilities..."); + + if (argc > 1) { + group = shell_strtoul(argv[1], 0, &err); + } else { + group = BT_MESH_ADDR_UNASSIGNED; + } + + if (argc > 2) { + blob_cli_xfer.inputs.timeout_base = shell_strtoul(argv[2], 0, &err); + } else { + blob_cli_xfer.inputs.timeout_base = 0; + } + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + if (!blob_cli_xfer.target_count) { + shell_print(sh, "Failed: No targets"); + return 0; + } + + blob_cli_inputs_prepare(group); + + err = bt_mesh_blob_cli_caps_get((struct bt_mesh_blob_cli *)mod_cli->user_data, + &blob_cli_xfer.inputs); + if (err) { + shell_print(sh, "Boundary check start failed (err: %d)", err); + } + + return 0; +} + +static int cmd_tx_cancel(const struct shell *sh, size_t argc, + char *argv[]) +{ + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { + return -ENODEV; + } + + shell_print(sh, "Cancelling transfer"); + bt_mesh_blob_cli_cancel((struct bt_mesh_blob_cli *)mod_cli->user_data); + + return 0; +} + +static int cmd_tx_get(const struct shell *sh, size_t argc, char *argv[]) +{ + uint16_t group; + int err; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { + return -ENODEV; + } + + if (argc > 1) { + group = shell_strtoul(argv[1], 0, &err); + } else { + group = BT_MESH_ADDR_UNASSIGNED; + } + + if (!blob_cli_xfer.target_count) { + shell_print(sh, "Failed: No targets"); + return -EINVAL; + } + + blob_cli_inputs_prepare(group); + + err = bt_mesh_blob_cli_xfer_progress_get((struct bt_mesh_blob_cli *)mod_cli->user_data, + &blob_cli_xfer.inputs); + if (err) { + shell_print(sh, "ERR %d", err); + return err; + } + return 0; +} + +static int cmd_tx_suspend(const struct shell *sh, size_t argc, + char *argv[]) +{ + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { + return -ENODEV; + } + + shell_print(sh, "Suspending transfer"); + bt_mesh_blob_cli_suspend((struct bt_mesh_blob_cli *)mod_cli->user_data); + + return 0; +} + +static int cmd_tx_resume(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_CLI, &mod_cli)) { + return -ENODEV; + } + + shell_print(sh, "Resuming transfer"); + bt_mesh_blob_cli_resume((struct bt_mesh_blob_cli *)mod_cli->user_data); + + return 0; +} + +#endif /* CONFIG_BT_MESH_SHELL_BLOB_CLI */ + +#if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) + +static struct bt_mesh_model *mod_srv; + +static int cmd_rx(const struct shell *sh, size_t argc, char *argv[]) +{ + uint16_t timeout_base; + uint32_t id; + int err = 0; + + if (!mod_srv && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_SRV, &mod_srv)) { + return -ENODEV; + } + + id = shell_strtoul(argv[1], 0, &err); + blob_rx_sum = 0; + + if (argc > 2) { + timeout_base = shell_strtoul(argv[2], 0, &err); + } else { + timeout_base = 0U; + } + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + shell_print(sh, "Receive BLOB 0x%x", id); + err = bt_mesh_blob_srv_recv((struct bt_mesh_blob_srv *)mod_srv->user_data, + id, bt_mesh_shell_blob_io, BT_MESH_TTL_MAX, timeout_base); + if (err) { + shell_print(sh, "BLOB RX setup failed (%d)", err); + } + + return 0; +} + +static int cmd_rx_cancel(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (!mod_srv && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_BLOB_SRV, &mod_srv)) { + return -ENODEV; + } + + shell_print(sh, "Cancelling BLOB rx"); + err = bt_mesh_blob_srv_cancel((struct bt_mesh_blob_srv *)mod_srv->user_data); + if (err) { + shell_print(sh, "BLOB cancel failed (%d)", err); + } + + return 0; +} + +#endif /* CONFIG_BT_MESH_SHELL_BLOB_SRV */ + +#if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) +BT_MESH_SHELL_MDL_INSTANCE_CMDS(cli_instance_cmds, BT_MESH_MODEL_ID_BLOB_CLI, mod_cli); +#endif +#if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) +BT_MESH_SHELL_MDL_INSTANCE_CMDS(srv_instance_cmds, BT_MESH_MODEL_ID_BLOB_SRV, mod_srv); +#endif + +#if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) +SHELL_STATIC_SUBCMD_SET_CREATE( + blob_cli_cmds, + /* BLOB Client Model Operations */ + SHELL_CMD_ARG(target, NULL, "", cmd_target, 2, 0), + SHELL_CMD_ARG(caps, NULL, "[ []]", cmd_caps, 1, 2), + SHELL_CMD_ARG(tx, NULL, " " + " [ [ " + "[]]]", cmd_tx, 5, 3), + SHELL_CMD_ARG(tx-cancel, NULL, NULL, cmd_tx_cancel, 1, 0), + SHELL_CMD_ARG(tx-get, NULL, "[group]", cmd_tx_get, 1, 1), + SHELL_CMD_ARG(tx-suspend, NULL, NULL, cmd_tx_suspend, 1, 0), + SHELL_CMD_ARG(tx-resume, NULL, NULL, cmd_tx_resume, 1, 0), + SHELL_CMD(instance, &cli_instance_cmds, "Instance commands", bt_mesh_shell_mdl_cmds_help), + SHELL_SUBCMD_SET_END); +#endif + +#if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) +SHELL_STATIC_SUBCMD_SET_CREATE( + blob_srv_cmds, + /* BLOB Server Model Operations */ + SHELL_CMD_ARG(rx, NULL, " []", cmd_rx, 2, 1), + SHELL_CMD_ARG(rx-cancel, NULL, NULL, cmd_rx_cancel, 1, 0), + SHELL_CMD(instance, &srv_instance_cmds, "Instance commands", bt_mesh_shell_mdl_cmds_help), + SHELL_SUBCMD_SET_END); +#endif + +SHELL_STATIC_SUBCMD_SET_CREATE( + blob_cmds, +#if defined(CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH) + SHELL_CMD_ARG(flash-stream-set, NULL, " []", + cmd_flash_stream_set, 2, 1), + SHELL_CMD_ARG(flash-stream-unset, NULL, NULL, cmd_flash_stream_unset, 1, 0), +#endif +#if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) + SHELL_CMD(cli, &blob_cli_cmds, "BLOB Cli commands", bt_mesh_shell_mdl_cmds_help), +#endif +#if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) + SHELL_CMD(srv, &blob_srv_cmds, "BLOB Srv commands", bt_mesh_shell_mdl_cmds_help), +#endif + SHELL_SUBCMD_SET_END); + +SHELL_SUBCMD_ADD((mesh, models), blob, &blob_cmds, "BLOB models commands", + bt_mesh_shell_mdl_cmds_help, 1, 1); diff --git a/subsys/bluetooth/mesh/shell/blob.h b/subsys/bluetooth/mesh/shell/blob.h new file mode 100644 index 00000000000..4edeaac7247 --- /dev/null +++ b/subsys/bluetooth/mesh/shell/blob.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +extern const struct bt_mesh_blob_io *bt_mesh_shell_blob_io; +extern bool bt_mesh_shell_blob_valid; + +void bt_mesh_shell_blob_cmds_init(void); diff --git a/subsys/bluetooth/mesh/shell/dfd.c b/subsys/bluetooth/mesh/shell/dfd.c new file mode 100644 index 00000000000..3f697f92d6b --- /dev/null +++ b/subsys/bluetooth/mesh/shell/dfd.c @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "utils.h" + +#include "../dfu_slot.h" +#include "../dfd_srv_internal.h" +#include "../access.h" + +static struct bt_mesh_model *mod; + +static void print_receivers_status(const struct shell *sh, struct bt_mesh_dfd_srv *srv, + enum bt_mesh_dfd_status status) +{ + shell_print(sh, "{\"status\": %d, \"target_cnt\": %d}", status, srv->target_cnt); +} + +static void print_dfd_status(const struct shell *sh, struct bt_mesh_dfd_srv *srv, + enum bt_mesh_dfd_status status) +{ + shell_fprintf(sh, SHELL_NORMAL, "{ \"status\": %d, \"phase\": %d", status, + srv->phase); + + if (srv->phase != BT_MESH_DFD_PHASE_IDLE && srv->dfu.xfer.slot) { + shell_fprintf(sh, SHELL_NORMAL, ", \"group\": 0x%04x, \"app_idx\": %d, " + "\"ttl\": %d, \"timeout_base\": %d, \"xfer_mode\": %d, " + "\"apply\": %d, \"slot_idx\": %d", srv->inputs.group, + srv->inputs.app_idx, srv->inputs.ttl, srv->inputs.timeout_base, + srv->dfu.xfer.blob.mode, srv->apply, srv->slot_idx); + } + + shell_print(sh, " }"); +} + +static void print_fw_status(const struct shell *sh, enum bt_mesh_dfd_status status, + uint16_t idx, const uint8_t *fwid, size_t fwid_len) +{ + shell_fprintf(sh, SHELL_NORMAL, "{ \"status\": %d, \"slot_cnt\": %d, \"idx\": %d", + status, bt_mesh_dfu_slot_foreach(NULL, NULL), idx); + if (fwid) { + shell_fprintf(sh, SHELL_NORMAL, ", \"fwid\": \""); + for (size_t i = 0; i < fwid_len; i++) { + shell_fprintf(sh, SHELL_NORMAL, "%02x", fwid[i]); + } + shell_fprintf(sh, SHELL_NORMAL, "\""); + } + shell_print(sh, " }"); +} + +static enum bt_mesh_dfu_iter slot_space_cb(const struct bt_mesh_dfu_slot *slot, + void *user_data) +{ + size_t *total = user_data; + + *total += slot->size; + + return BT_MESH_DFU_ITER_CONTINUE; +} + +static int cmd_dfd_receivers_add(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + + if (bt_mesh_dfu_cli_is_busy(&dfd_srv->dfu)) { + print_receivers_status(sh, dfd_srv, + BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION); + return -EBUSY; + } + + char *outer_state, *inner_state; + + char *token = strtok_r(argv[1], ";", &outer_state); + + while (token) { + char *addr_str = strtok_r(token, ",", &inner_state); + char *img_idx_str = strtok_r(NULL, ",", &inner_state); + int err = 0; + + if (addr_str == NULL || img_idx_str == NULL) { + return -EINVAL; + } + + uint16_t addr = shell_strtoul(addr_str, 0, &err); + uint8_t img_idx = shell_strtoul(img_idx_str, 0, &err); + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_receiver_add( + dfd_srv, addr, img_idx); + + if (status != BT_MESH_DFD_SUCCESS) { + print_receivers_status(sh, dfd_srv, status); + return status == BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES ? + -ENOSPC : -EINVAL; + } + + token = strtok_r(NULL, ";", &outer_state); + } + + print_receivers_status(sh, dfd_srv, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static int cmd_dfd_receivers_delete_all(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_receivers_delete_all( + dfd_srv); + + print_receivers_status(sh, dfd_srv, status); + + if (status != BT_MESH_DFD_SUCCESS) { + return status == BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION ? -EBUSY : -EINVAL; + } + + return 0; +} + +static int cmd_dfd_receivers_get(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + int err = 0; + + uint16_t first = shell_strtoul(argv[1], 0, &err); + uint16_t cnt = shell_strtoul(argv[2], 0, &err); + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + if (cnt == 0 || dfd_srv->target_cnt <= first) { + return -EINVAL; + } + + cnt = MIN(cnt, dfd_srv->target_cnt - first); + uint8_t progress = bt_mesh_dfu_cli_progress(&dfd_srv->dfu) / 2; + + shell_print(sh, "{\n\t\"target_cnt\": %d,\n\t\"targets\": {", + dfd_srv->target_cnt); + for (int i = 0; i < cnt; i++) { + const struct bt_mesh_dfu_target *t = &dfd_srv->targets[i + first]; + + shell_print(sh, "\t\t\"%d\": { \"blob_addr\": 0x%04x, \"phase\": %d, " + "\"status\": %d, \"blob_status\": %d, \"progress\": %d, " + "\"img_idx\": %d }%s", i + first, t->blob.addr, t->phase, t->status, + t->blob.status, progress, t->img_idx, (i == cnt - 1) ? "" : ","); + } + shell_print(sh, "\t}\n}"); + + return 0; +} + +static int cmd_dfd_capabilities_get(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t size = 0; + /* Remaining size */ + (void)bt_mesh_dfu_slot_foreach(slot_space_cb, &size); + size = MIN(size, CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE); + + shell_print(sh, "{ \"targets_max\": %d, \"slot_cnt\": %d, \"slot_max_size\": %d, " + "\"slot_space\": %d, \"remaining_space\": %d, \"oob_supported\": false }", + CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX, CONFIG_BT_MESH_DFU_SLOT_CNT, + CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE, CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE, + CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE - size); + + return 0; +} + +static int cmd_dfd_get(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + + print_dfd_status(sh, dfd_srv, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static int cmd_dfd_start(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + struct bt_mesh_dfd_start_params params; + int err = 0; + + params.app_idx = shell_strtoul(argv[1], 0, &err); + params.slot_idx = shell_strtoul(argv[2], 0, &err); + if (argc > 3) { + params.group = shell_strtoul(argv[3], 0, &err); + } else { + params.group = BT_MESH_ADDR_UNASSIGNED; + } + + if (argc > 4) { + params.apply = strcmp(argv[4], "true") ? false : true; + } else { + params.apply = true; + } + + if (argc > 5) { + params.ttl = shell_strtoul(argv[5], 0, &err); + } else { + params.ttl = BT_MESH_TTL_DEFAULT; + } + + if (argc > 6) { + params.timeout_base = shell_strtoul(argv[6], 0, &err); + } else { + params.timeout_base = 0U; + } + + if (argc > 7) { + params.xfer_mode = (enum bt_mesh_blob_xfer_mode)shell_strtoul(argv[7], 0, &err); + } else { + params.xfer_mode = BT_MESH_BLOB_XFER_MODE_PUSH; + } + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_start(dfd_srv, ¶ms); + + print_dfd_status(sh, dfd_srv, status); + if (status != BT_MESH_DFD_SUCCESS) { + return -EINVAL; + } + + return 0; +} + +static int cmd_dfd_suspend(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_suspend(dfd_srv); + + print_dfd_status(sh, dfd_srv, status); + if (status != BT_MESH_DFD_SUCCESS) { + return -EINVAL; + } + + return 0; +} + +static int cmd_dfd_cancel(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_cancel(dfd_srv, NULL); + + print_dfd_status(sh, dfd_srv, status); + if (status != BT_MESH_DFD_SUCCESS) { + return -EINVAL; + } + + return 0; +} + +static int cmd_dfd_apply(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_apply(dfd_srv); + + print_dfd_status(sh, dfd_srv, status); + if (status != BT_MESH_DFD_SUCCESS) { + return -EINVAL; + } + + return 0; +} + +static int cmd_dfd_fw_get(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t fwid[CONFIG_BT_MESH_DFU_FWID_MAXLEN]; + size_t hexlen = strlen(argv[1]); + size_t fwid_len = hex2bin(argv[1], hexlen, fwid, CONFIG_BT_MESH_DFU_FWID_MAXLEN); + + if (fwid_len != ((hexlen + 1) / 2)) { + return -EINVAL; + } + + const struct bt_mesh_dfu_slot *slot; + int idx = bt_mesh_dfu_slot_get(fwid, fwid_len, &slot); + + if (idx >= 0 && bt_mesh_dfu_slot_is_valid(slot)) { + print_fw_status(sh, BT_MESH_DFD_SUCCESS, idx, fwid, fwid_len); + } else { + print_fw_status(sh, BT_MESH_DFD_ERR_FW_NOT_FOUND, 0xffff, fwid, fwid_len); + return -ENOENT; + } + + return 0; +} + +static int cmd_dfd_fw_get_by_idx(const struct shell *sh, size_t argc, char *argv[]) +{ + int err = 0; + uint16_t idx = shell_strtoul(argv[1], 0, &err); + const struct bt_mesh_dfu_slot *slot = bt_mesh_dfu_slot_at(idx); + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + if (slot && bt_mesh_dfu_slot_is_valid(slot)) { + print_fw_status(sh, BT_MESH_DFD_SUCCESS, idx, slot->fwid, slot->fwid_len); + } else { + print_fw_status(sh, BT_MESH_DFD_ERR_FW_NOT_FOUND, idx, NULL, 0); + return -ENOENT; + } + + return 0; +} + +static int cmd_dfd_fw_delete(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + + uint8_t fwid_buf[CONFIG_BT_MESH_DFU_FWID_MAXLEN]; + size_t hexlen = strlen(argv[1]); + size_t fwid_len = hex2bin(argv[1], hexlen, fwid_buf, CONFIG_BT_MESH_DFU_FWID_MAXLEN); + + if (fwid_len != ((hexlen + 1) / 2)) { + return -EINVAL; + } + + const uint8_t *fwid = &fwid_buf[0]; + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_fw_delete(dfd_srv, + &fwid_len, &fwid); + + print_fw_status(sh, status, 0xffff, fwid, fwid_len); + + if (status != BT_MESH_DFD_SUCCESS) { + return -EINVAL; + } + + return 0; +} + +static int cmd_dfd_fw_delete_all(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFD_SRV, &mod)) { + return -ENODEV; + } + + struct bt_mesh_dfd_srv *dfd_srv = mod->user_data; + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_fw_delete_all(dfd_srv); + + print_fw_status(sh, status, 0xffff, NULL, 0); + + if (status != BT_MESH_DFD_SUCCESS) { + return -EINVAL; + } + + return 0; +} + +BT_MESH_SHELL_MDL_INSTANCE_CMDS(instance_cmds, BT_MESH_MODEL_ID_DFD_SRV, mod); + +SHELL_STATIC_SUBCMD_SET_CREATE( + dfd_cmds, + SHELL_CMD_ARG(receivers-add, NULL, ",[;,]...", + cmd_dfd_receivers_add, 2, 0), + SHELL_CMD_ARG(receivers-delete-all, NULL, NULL, cmd_dfd_receivers_delete_all, 1, 0), + SHELL_CMD_ARG(receivers-get, NULL, " ", cmd_dfd_receivers_get, 3, 0), + SHELL_CMD_ARG(capabilities-get, NULL, NULL, cmd_dfd_capabilities_get, 1, 0), + SHELL_CMD_ARG(get, NULL, NULL, cmd_dfd_get, 1, 0), + SHELL_CMD_ARG(start, NULL, + " [ [ [ " + "[ []]]]]", + cmd_dfd_start, 3, 5), + SHELL_CMD_ARG(suspend, NULL, NULL, cmd_dfd_suspend, 1, 0), + SHELL_CMD_ARG(cancel, NULL, NULL, cmd_dfd_cancel, 1, 0), + SHELL_CMD_ARG(apply, NULL, NULL, cmd_dfd_apply, 1, 0), + SHELL_CMD_ARG(fw-get, NULL, "", cmd_dfd_fw_get, 2, 0), + SHELL_CMD_ARG(fw-get-by-idx, NULL, "", cmd_dfd_fw_get_by_idx, 2, 0), + SHELL_CMD_ARG(fw-delete, NULL, "", cmd_dfd_fw_delete, 2, 0), + SHELL_CMD_ARG(fw-delete-all, NULL, NULL, cmd_dfd_fw_delete_all, 1, 0), + SHELL_CMD(instance, &instance_cmds, "Instance commands", bt_mesh_shell_mdl_cmds_help), + SHELL_SUBCMD_SET_END); + +SHELL_SUBCMD_ADD((mesh, models), dfd, &dfd_cmds, "Distributor commands", + bt_mesh_shell_mdl_cmds_help, 1, 1); diff --git a/subsys/bluetooth/mesh/shell/dfu.c b/subsys/bluetooth/mesh/shell/dfu.c new file mode 100644 index 00000000000..b4268e89c52 --- /dev/null +++ b/subsys/bluetooth/mesh/shell/dfu.c @@ -0,0 +1,1032 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "dfu.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "blob.h" +#include "../dfu_slot.h" + +/*************************************************************************************************** + * Implementation of models' instances + **************************************************************************************************/ + +extern const struct shell *bt_mesh_shell_ctx_shell; + +#if defined(CONFIG_BT_MESH_SHELL_DFU_CLI) + +static void dfu_cli_ended(struct bt_mesh_dfu_cli *cli, + enum bt_mesh_dfu_status reason) +{ + shell_print(bt_mesh_shell_ctx_shell, "DFU ended: %u", reason); +} + +static void dfu_cli_applied(struct bt_mesh_dfu_cli *cli) +{ + shell_print(bt_mesh_shell_ctx_shell, "DFU applied."); +} + +static void dfu_cli_lost_target(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_dfu_target *target) +{ + shell_print(bt_mesh_shell_ctx_shell, "DFU target lost: 0x%04x", target->blob.addr); +} + +static void dfu_cli_confirmed(struct bt_mesh_dfu_cli *cli) +{ + shell_print(bt_mesh_shell_ctx_shell, "DFU confirmed"); +} + +const struct bt_mesh_dfu_cli_cb dfu_cli_cb = { + .ended = dfu_cli_ended, + .applied = dfu_cli_applied, + .lost_target = dfu_cli_lost_target, + .confirmed = dfu_cli_confirmed, +}; + +struct bt_mesh_dfu_cli bt_mesh_shell_dfu_cli = BT_MESH_DFU_CLI_INIT(&dfu_cli_cb); + +#endif /* CONFIG_BT_MESH_SHELL_DFU_CLI */ + +#if defined(CONFIG_BT_MESH_SHELL_DFU_SRV) + +struct shell_dfu_fwid { + uint8_t type; + struct mcuboot_img_sem_ver ver; +}; + +static struct bt_mesh_dfu_img dfu_imgs[] = { { + .fwid = &((struct shell_dfu_fwid){ 0x01, { 1, 0, 0, 0 } }), + .fwid_len = sizeof(struct shell_dfu_fwid), +} }; + +static int dfu_meta_check(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, + struct net_buf_simple *metadata, + enum bt_mesh_dfu_effect *effect) +{ + return 0; +} + +static int dfu_start(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, + struct net_buf_simple *metadata, + const struct bt_mesh_blob_io **io) +{ + shell_print(bt_mesh_shell_ctx_shell, "DFU setup"); + + *io = bt_mesh_shell_blob_io; + + return 0; +} + +static void dfu_end(struct bt_mesh_dfu_srv *srv, const struct bt_mesh_dfu_img *img, bool success) +{ + if (!success) { + shell_print(bt_mesh_shell_ctx_shell, "DFU failed"); + return; + } + + if (!bt_mesh_shell_blob_valid) { + bt_mesh_dfu_srv_rejected(srv); + return; + } + + bt_mesh_dfu_srv_verified(srv); +} + +static int dfu_apply(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img) +{ + if (!bt_mesh_shell_blob_valid) { + return -EINVAL; + } + + shell_print(bt_mesh_shell_ctx_shell, "Applying DFU transfer..."); + + return 0; +} + +static const struct bt_mesh_dfu_srv_cb dfu_handlers = { + .check = dfu_meta_check, + .start = dfu_start, + .end = dfu_end, + .apply = dfu_apply, +}; + +struct bt_mesh_dfu_srv bt_mesh_shell_dfu_srv = + BT_MESH_DFU_SRV_INIT(&dfu_handlers, dfu_imgs, ARRAY_SIZE(dfu_imgs)); + +#endif /* CONFIG_BT_MESH_SHELL_DFU_SRV */ + +void bt_mesh_shell_dfu_cmds_init(void) +{ +#if defined(CONFIG_BT_MESH_SHELL_DFU_SRV) && defined(CONFIG_BOOTLOADER_MCUBOOT) + struct mcuboot_img_header img_header; + + int err = boot_read_bank_header(FIXED_PARTITION_ID(slot0_partition), + &img_header, sizeof(img_header)); + if (!err) { + struct shell_dfu_fwid *fwid = + (struct shell_dfu_fwid *)dfu_imgs[0].fwid; + + fwid->ver = img_header.h.v1.sem_ver; + + boot_write_img_confirmed(); + } +#endif +} + +/*************************************************************************************************** + * Shell Commands + **************************************************************************************************/ + +#if defined(CONFIG_BT_MESH_SHELL_DFU_METADATA) + +NET_BUF_SIMPLE_DEFINE_STATIC(dfu_comp_data, BT_MESH_TX_SDU_MAX); + +static int cmd_dfu_comp_clear(const struct shell *sh, size_t argc, char *argv[]) +{ + net_buf_simple_reset(&dfu_comp_data); + return 0; +} + +static int cmd_dfu_comp_add(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_buf_simple_state state; + int err = 0; + + if (argc < 6) { + return -EINVAL; + } + + if (net_buf_simple_tailroom(&dfu_comp_data) < 10) { + shell_print(sh, "Buffer is too small: %u", + net_buf_simple_tailroom(&dfu_comp_data)); + return -EMSGSIZE; + } + + net_buf_simple_save(&dfu_comp_data, &state); + + for (size_t i = 1; i <= 5; i++) { + net_buf_simple_add_le16(&dfu_comp_data, shell_strtoul(argv[i], 0, &err)); + } + + if (err) { + net_buf_simple_restore(&dfu_comp_data, &state); + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + return 0; +} + +static int cmd_dfu_comp_elem_add(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t sig_model_count; + uint8_t vnd_model_count; + struct net_buf_simple_state state; + int err = 0; + + if (argc < 5) { + return -EINVAL; + } + + net_buf_simple_save(&dfu_comp_data, &state); + + sig_model_count = shell_strtoul(argv[2], 0, &err); + vnd_model_count = shell_strtoul(argv[3], 0, &err); + + if (argc < 4 + sig_model_count + vnd_model_count * 2) { + return -EINVAL; + } + + if (net_buf_simple_tailroom(&dfu_comp_data) < 4 + sig_model_count * 2 + + vnd_model_count * 4) { + shell_print(sh, "Buffer is too small: %u", + net_buf_simple_tailroom(&dfu_comp_data)); + return -EMSGSIZE; + } + + net_buf_simple_add_le16(&dfu_comp_data, shell_strtoul(argv[1], 0, &err)); + net_buf_simple_add_u8(&dfu_comp_data, sig_model_count); + net_buf_simple_add_u8(&dfu_comp_data, vnd_model_count); + + for (size_t i = 0; i < sig_model_count; i++) { + net_buf_simple_add_le16(&dfu_comp_data, shell_strtoul(argv[4 + i], 0, &err)); + } + + for (size_t i = 0; i < vnd_model_count; i++) { + size_t arg_i = 4 + sig_model_count + i * 2; + + net_buf_simple_add_le16(&dfu_comp_data, shell_strtoul(argv[arg_i], 0, &err)); + net_buf_simple_add_le16(&dfu_comp_data, shell_strtoul(argv[arg_i + 1], 0, &err)); + } + + if (err) { + net_buf_simple_restore(&dfu_comp_data, &state); + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + return 0; +} + +static int cmd_dfu_comp_hash_get(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t key[16] = {}; + uint32_t hash; + int err; + + if (dfu_comp_data.len < 14) { + shell_print(sh, "Composition data is not set"); + return -EINVAL; + } + + if (argc > 1) { + hex2bin(argv[1], strlen(argv[1]), key, sizeof(key)); + } + + shell_print(sh, "Composition data to be hashed:"); + shell_print(sh, "\tCID: 0x%04x", sys_get_le16(&dfu_comp_data.data[0])); + shell_print(sh, "\tPID: 0x%04x", sys_get_le16(&dfu_comp_data.data[2])); + shell_print(sh, "\tVID: 0x%04x", sys_get_le16(&dfu_comp_data.data[4])); + shell_print(sh, "\tCPRL: %u", sys_get_le16(&dfu_comp_data.data[6])); + shell_print(sh, "\tFeatures: 0x%x", sys_get_le16(&dfu_comp_data.data[8])); + + for (size_t i = 10; i < dfu_comp_data.len - 4;) { + uint8_t sig_model_count = dfu_comp_data.data[i + 2]; + uint8_t vnd_model_count = dfu_comp_data.data[i + 3]; + + shell_print(sh, "\tElem: %u", sys_get_le16(&dfu_comp_data.data[i])); + shell_print(sh, "\t\tNumS: %u", sig_model_count); + shell_print(sh, "\t\tNumV: %u", vnd_model_count); + + for (size_t j = 0; j < sig_model_count; j++) { + shell_print(sh, "\t\tSIG Model ID: 0x%04x", + sys_get_le16(&dfu_comp_data.data[i + 4 + j * 2])); + } + + for (size_t j = 0; j < vnd_model_count; j++) { + size_t arg_i = i + 4 + sig_model_count * 2 + j * 4; + + shell_print(sh, "\t\tVnd Company ID: 0x%04x, Model ID: 0x%04x", + sys_get_le16(&dfu_comp_data.data[arg_i]), + sys_get_le16(&dfu_comp_data.data[arg_i + 2])); + } + + i += 4 + sig_model_count * 2 + vnd_model_count * 4; + } + + err = bt_mesh_dfu_metadata_comp_hash_get(&dfu_comp_data, key, &hash); + if (err) { + shell_print(sh, "Failed to compute composition data hash: %d\n", err); + return err; + } + + shell_print(sh, "Composition data hash: 0x%04x", hash); + + return 0; +} + +static int cmd_dfu_metadata_encode(const struct shell *sh, size_t argc, char *argv[]) +{ + char md_str[2 * CONFIG_BT_MESH_DFU_METADATA_MAXLEN + 1]; + uint8_t user_data[CONFIG_BT_MESH_DFU_METADATA_MAXLEN - 18]; + struct bt_mesh_dfu_metadata md; + size_t len; + int err = 0; + + NET_BUF_SIMPLE_DEFINE(buf, CONFIG_BT_MESH_DFU_METADATA_MAXLEN); + + if (argc < 9) { + return -EINVAL; + } + + md.fw_ver.major = shell_strtoul(argv[1], 0, &err); + md.fw_ver.minor = shell_strtoul(argv[2], 0, &err); + md.fw_ver.revision = shell_strtoul(argv[3], 0, &err); + md.fw_ver.build_num = shell_strtoul(argv[4], 0, &err); + md.fw_size = shell_strtoul(argv[5], 0, &err); + md.fw_core_type = shell_strtoul(argv[6], 0, &err); + md.comp_hash = shell_strtoul(argv[7], 0, &err); + md.elems = shell_strtoul(argv[8], 0, &err); + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + if (argc > 9) { + if (sizeof(user_data) < strlen(argv[9]) / 2) { + shell_print(sh, "User data is too big."); + return -EINVAL; + } + + md.user_data_len = hex2bin(argv[9], strlen(argv[9]), user_data, sizeof(user_data)); + md.user_data = user_data; + } else { + md.user_data_len = 0; + } + + shell_print(sh, "Metadata to be encoded:"); + shell_print(sh, "\tVersion: %u.%u.%u+%u", md.fw_ver.major, md.fw_ver.minor, + md.fw_ver.revision, md.fw_ver.build_num); + shell_print(sh, "\tSize: %u", md.fw_size); + shell_print(sh, "\tCore Type: 0x%x", md.fw_core_type); + shell_print(sh, "\tComposition data hash: 0x%x", md.comp_hash); + shell_print(sh, "\tElements: %u", md.elems); + + if (argc > 9) { + shell_print(sh, "\tUser data: %s", argv[10]); + } + + shell_print(sh, "\tUser data length: %u", md.user_data_len); + + err = bt_mesh_dfu_metadata_encode(&md, &buf); + if (err) { + shell_print(sh, "Failed to encode metadata: %d", err); + return err; + } + + len = bin2hex(buf.data, buf.len, md_str, sizeof(md_str)); + md_str[len] = '\0'; + shell_print(sh, "Encoded metadata: %s", md_str); + + return 0; +} + +#endif /* CONFIG_BT_MESH_SHELL_DFU_METADATA */ + +#if defined(CONFIG_BT_MESH_SHELL_DFU_SLOT) + +static int cmd_dfu_slot_add(const struct shell *sh, size_t argc, char *argv[]) +{ + const struct bt_mesh_dfu_slot *slot; + size_t size; + uint8_t fwid[CONFIG_BT_MESH_DFU_FWID_MAXLEN]; + size_t fwid_len = 0; + uint8_t metadata[CONFIG_BT_MESH_DFU_METADATA_MAXLEN]; + size_t metadata_len = 0; + const char *uri = ""; + int err = 0; + + size = shell_strtoul(argv[1], 0, &err); + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + if (argc > 2) { + fwid_len = hex2bin(argv[2], strlen(argv[2]), fwid, + sizeof(fwid)); + } + + if (argc > 3) { + metadata_len = hex2bin(argv[3], strlen(argv[3]), metadata, + sizeof(metadata)); + } + + if (argc > 4) { + uri = argv[4]; + } + + shell_print(sh, "Adding slot (size: %u)", size); + + slot = bt_mesh_dfu_slot_add(size, fwid, fwid_len, metadata, + metadata_len, uri, strlen(uri)); + if (!slot) { + shell_print(sh, "Failed."); + return 0; + } + + bt_mesh_dfu_slot_valid_set(slot, true); + + shell_print(sh, "Slot added. ID: %u", bt_mesh_dfu_slot_idx_get(slot)); + + return 0; +} + +static int cmd_dfu_slot_del(const struct shell *sh, size_t argc, char *argv[]) +{ + const struct bt_mesh_dfu_slot *slot; + uint8_t idx; + int err = 0; + + idx = shell_strtoul(argv[1], 0, &err); + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + slot = bt_mesh_dfu_slot_at(idx); + if (!slot) { + shell_print(sh, "No slot at %u", idx); + return 0; + } + + err = bt_mesh_dfu_slot_del(slot); + if (err) { + shell_print(sh, "Failed deleting slot %u (err: %d)", idx, + err); + return 0; + } + + shell_print(sh, "Slot %u deleted.", idx); + return 0; +} + +static int cmd_dfu_slot_del_all(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + err = bt_mesh_dfu_slot_del_all(); + if (err) { + shell_print(sh, "Failed deleting all slots (err: %d)", err); + return 0; + } + + shell_print(sh, "All slots deleted."); + return 0; +} + +static void slot_info_print(const struct shell *sh, const struct bt_mesh_dfu_slot *slot, + const uint8_t *idx) +{ + char fwid[2 * CONFIG_BT_MESH_DFU_FWID_MAXLEN + 1]; + char metadata[2 * CONFIG_BT_MESH_DFU_METADATA_MAXLEN + 1]; + char uri[CONFIG_BT_MESH_DFU_URI_MAXLEN + 1]; + size_t len; + + len = bin2hex(slot->fwid, slot->fwid_len, fwid, sizeof(fwid)); + fwid[len] = '\0'; + len = bin2hex(slot->metadata, slot->metadata_len, metadata, + sizeof(metadata)); + metadata[len] = '\0'; + memcpy(uri, slot->uri, slot->uri_len); + uri[slot->uri_len] = '\0'; + + if (idx != NULL) { + shell_print(sh, "Slot %u:", *idx); + } else { + shell_print(sh, "Slot:"); + } + shell_print(sh, "\tSize: %u bytes", slot->size); + shell_print(sh, "\tFWID: %s", fwid); + shell_print(sh, "\tMetadata: %s", metadata); + shell_print(sh, "\tURI: %s", uri); +} + +static int cmd_dfu_slot_get(const struct shell *sh, size_t argc, char *argv[]) +{ + const struct bt_mesh_dfu_slot *slot; + uint8_t idx; + int err = 0; + + idx = shell_strtoul(argv[1], 0, &err); + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + slot = bt_mesh_dfu_slot_at(idx); + if (!slot) { + shell_print(sh, "No slot at %u", idx); + return 0; + } + + slot_info_print(sh, slot, &idx); + return 0; +} + +#endif /* defined(CONFIG_BT_MESH_SHELL_DFU_SLOT) */ + +#if defined(CONFIG_BT_MESH_SHELL_DFU_CLI) + +static struct bt_mesh_model *mod_cli; + +static struct { + struct bt_mesh_dfu_target targets[32]; + struct bt_mesh_blob_target_pull pull[32]; + size_t target_cnt; + struct bt_mesh_blob_cli_inputs inputs; +} dfu_tx; + +static void dfu_tx_prepare(void) +{ + sys_slist_init(&dfu_tx.inputs.targets); + + for (size_t i = 0; i < dfu_tx.target_cnt; i++) { + /* Reset target context. */ + uint16_t addr = dfu_tx.targets[i].blob.addr; + + memset(&dfu_tx.targets[i].blob, 0, sizeof(struct bt_mesh_blob_target)); + memset(&dfu_tx.pull[i], 0, sizeof(struct bt_mesh_blob_target_pull)); + dfu_tx.targets[i].blob.addr = addr; + dfu_tx.targets[i].blob.pull = &dfu_tx.pull[i]; + + sys_slist_append(&dfu_tx.inputs.targets, &dfu_tx.targets[i].blob.n); + } +} + +static int cmd_dfu_target(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t img_idx; + uint16_t addr; + int err = 0; + + addr = shell_strtoul(argv[1], 0, &err); + img_idx = shell_strtoul(argv[2], 0, &err); + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + if (dfu_tx.target_cnt == ARRAY_SIZE(dfu_tx.targets)) { + shell_print(sh, "No room."); + return 0; + } + + for (size_t i = 0; i < dfu_tx.target_cnt; i++) { + if (dfu_tx.targets[i].blob.addr == addr) { + shell_print(sh, "Target 0x%04x already exists", addr); + return 0; + } + } + + dfu_tx.targets[dfu_tx.target_cnt].blob.addr = addr; + dfu_tx.targets[dfu_tx.target_cnt].img_idx = img_idx; + sys_slist_append(&dfu_tx.inputs.targets, &dfu_tx.targets[dfu_tx.target_cnt].blob.n); + dfu_tx.target_cnt++; + + shell_print(sh, "Added target 0x%04x", addr); + return 0; +} + +static int cmd_dfu_targets_reset(const struct shell *sh, size_t argc, char *argv[]) +{ + dfu_tx_prepare(); + return 0; +} + +static int cmd_dfu_target_state(const struct shell *sh, size_t argc, char *argv[]) +{ + struct bt_mesh_dfu_target_status rsp; + struct bt_mesh_msg_ctx ctx = { + .send_ttl = BT_MESH_TTL_DEFAULT, + .net_idx = bt_mesh_shell_target_ctx.net_idx, + .addr = bt_mesh_shell_target_ctx.dst, + .app_idx = bt_mesh_shell_target_ctx.app_idx, + }; + int err; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + err = bt_mesh_dfu_cli_status_get((struct bt_mesh_dfu_cli *)mod_cli->user_data, + &ctx, &rsp); + if (err) { + shell_print(sh, "Failed getting target status (err: %d)", + err); + return 0; + } + + shell_print(sh, "Target 0x%04x:", bt_mesh_shell_target_ctx.dst); + shell_print(sh, "\tStatus: %u", rsp.status); + shell_print(sh, "\tPhase: %u", rsp.phase); + if (rsp.phase != BT_MESH_DFU_PHASE_IDLE) { + shell_print(sh, "\tEffect: %u", rsp.effect); + shell_print(sh, "\tImg Idx: %u", rsp.img_idx); + shell_print(sh, "\tTTL: %u", rsp.ttl); + shell_print(sh, "\tTimeout base: %u", rsp.timeout_base); + } + + return 0; +} + +static enum bt_mesh_dfu_iter dfu_img_cb(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t idx, uint8_t total, + const struct bt_mesh_dfu_img *img, + void *cb_data) +{ + char fwid[2 * CONFIG_BT_MESH_DFU_FWID_MAXLEN + 1]; + size_t len; + + len = bin2hex(img->fwid, img->fwid_len, fwid, sizeof(fwid)); + fwid[len] = '\0'; + + shell_print(bt_mesh_shell_ctx_shell, "Image %u:", idx); + shell_print(bt_mesh_shell_ctx_shell, "\tFWID: %s", fwid); + if (img->uri) { + shell_print(bt_mesh_shell_ctx_shell, "\tURI: %s", img->uri); + } + + return BT_MESH_DFU_ITER_CONTINUE; +} + +static int cmd_dfu_target_imgs(const struct shell *sh, size_t argc, char *argv[]) +{ + struct bt_mesh_msg_ctx ctx = { + .send_ttl = BT_MESH_TTL_DEFAULT, + .net_idx = bt_mesh_shell_target_ctx.net_idx, + .addr = bt_mesh_shell_target_ctx.dst, + .app_idx = bt_mesh_shell_target_ctx.app_idx, + }; + uint8_t img_cnt = 0xff; + int err = 0; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + if (argc == 2) { + img_cnt = shell_strtoul(argv[1], 0, &err); + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + } + + shell_print(sh, "Requesting DFU images in 0x%04x", bt_mesh_shell_target_ctx.dst); + + err = bt_mesh_dfu_cli_imgs_get((struct bt_mesh_dfu_cli *)mod_cli->user_data, + &ctx, dfu_img_cb, NULL, img_cnt); + if (err) { + shell_print(sh, "Request failed (err: %d)", err); + } + + return 0; +} + +static int cmd_dfu_target_check(const struct shell *sh, size_t argc, char *argv[]) +{ + struct bt_mesh_dfu_metadata_status rsp; + const struct bt_mesh_dfu_slot *slot; + struct bt_mesh_msg_ctx ctx = { + .send_ttl = BT_MESH_TTL_DEFAULT, + .net_idx = bt_mesh_shell_target_ctx.net_idx, + .addr = bt_mesh_shell_target_ctx.dst, + .app_idx = bt_mesh_shell_target_ctx.app_idx, + }; + uint8_t slot_idx, img_idx; + int err = 0; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + slot_idx = shell_strtoul(argv[1], 0, &err); + img_idx = shell_strtoul(argv[2], 0, &err); + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + slot = bt_mesh_dfu_slot_at(slot_idx); + if (!slot) { + shell_print(sh, "No image in slot %u", slot_idx); + return 0; + } + + err = bt_mesh_dfu_cli_metadata_check((struct bt_mesh_dfu_cli *)mod_cli->user_data, + &ctx, img_idx, slot, &rsp); + if (err) { + shell_print(sh, "Metadata check failed. err: %d", err); + return 0; + } + + shell_print(sh, "Slot %u check for 0x%04x image %u:", slot_idx, + bt_mesh_shell_target_ctx.dst, img_idx); + shell_print(sh, "\tStatus: %u", rsp.status); + shell_print(sh, "\tEffect: 0x%x", rsp.effect); + + return 0; +} + +static int cmd_dfu_send(const struct shell *sh, size_t argc, char *argv[]) +{ + struct bt_mesh_dfu_cli_xfer_blob_params blob_params; + struct bt_mesh_dfu_cli_xfer xfer; + uint8_t slot_idx; + uint16_t group; + int err = 0; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + slot_idx = shell_strtoul(argv[1], 0, &err); + if (argc > 2) { + group = shell_strtoul(argv[2], 0, &err); + } else { + group = BT_MESH_ADDR_UNASSIGNED; + } + + if (argc > 3) { + xfer.mode = shell_strtoul(argv[3], 0, &err); + } else { + xfer.mode = BT_MESH_BLOB_XFER_MODE_PUSH; + } + + if (argc > 5) { + blob_params.block_size_log = shell_strtoul(argv[4], 0, &err); + blob_params.chunk_size = shell_strtoul(argv[5], 0, &err); + xfer.blob_params = &blob_params; + } else { + xfer.blob_params = NULL; + } + + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + if (!dfu_tx.target_cnt) { + shell_print(sh, "No targets."); + return 0; + } + + xfer.slot = bt_mesh_dfu_slot_at(slot_idx); + if (!xfer.slot) { + shell_print(sh, "No image in slot %u", slot_idx); + return 0; + } + + shell_print(sh, "Starting DFU from slot %u (%u targets)", slot_idx, + dfu_tx.target_cnt); + + dfu_tx.inputs.group = group; + dfu_tx.inputs.app_idx = bt_mesh_shell_target_ctx.app_idx; + dfu_tx.inputs.ttl = BT_MESH_TTL_DEFAULT; + + err = bt_mesh_dfu_cli_send((struct bt_mesh_dfu_cli *)mod_cli->user_data, + &dfu_tx.inputs, bt_mesh_shell_blob_io, &xfer); + if (err) { + shell_print(sh, "Failed (err: %d)", err); + return 0; + } + return 0; +} + +static int cmd_dfu_tx_cancel(const struct shell *sh, size_t argc, char *argv[]) +{ + struct bt_mesh_msg_ctx ctx = { + .send_ttl = BT_MESH_TTL_DEFAULT, + .net_idx = bt_mesh_shell_target_ctx.net_idx, + .addr = bt_mesh_shell_target_ctx.dst, + .app_idx = bt_mesh_shell_target_ctx.app_idx, + }; + int err = 0; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + if (argc == 2) { + ctx.addr = shell_strtoul(argv[1], 0, &err); + if (err) { + shell_warn(sh, "Unable to parse input string argument"); + return err; + } + + shell_print(sh, "Cancelling DFU for 0x%04x", ctx.addr); + } else { + shell_print(sh, "Cancelling DFU"); + } + + err = bt_mesh_dfu_cli_cancel((struct bt_mesh_dfu_cli *)mod_cli->user_data, + (argc == 2) ? &ctx : NULL); + if (err) { + shell_print(sh, "Failed (err: %d)", err); + } + + return 0; +} + +static int cmd_dfu_apply(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + shell_print(sh, "Applying DFU"); + + err = bt_mesh_dfu_cli_apply((struct bt_mesh_dfu_cli *)mod_cli->user_data); + if (err) { + shell_print(sh, "Failed (err: %d)", err); + } + + return 0; +} + +static int cmd_dfu_confirm(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + shell_print(sh, "Confirming DFU"); + + err = bt_mesh_dfu_cli_confirm((struct bt_mesh_dfu_cli *)mod_cli->user_data); + if (err) { + shell_print(sh, "Failed (err: %d)", err); + } + + return 0; +} + +static int cmd_dfu_suspend(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + shell_print(sh, "Suspending DFU"); + + err = bt_mesh_dfu_cli_suspend((struct bt_mesh_dfu_cli *)mod_cli->user_data); + if (err) { + shell_print(sh, "Failed (err: %d)", err); + } + + return 0; +} + +static int cmd_dfu_resume(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + shell_print(sh, "Resuming DFU"); + + err = bt_mesh_dfu_cli_resume((struct bt_mesh_dfu_cli *)mod_cli->user_data); + if (err) { + shell_print(sh, "Failed (err: %d)", err); + } + + return 0; +} + +static int cmd_dfu_tx_progress(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod_cli && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_CLI, &mod_cli)) { + return -ENODEV; + } + + shell_print(sh, "DFU progress: %u %%", + bt_mesh_dfu_cli_progress((struct bt_mesh_dfu_cli *)mod_cli->user_data)); + return 0; +} + +#endif /* CONFIG_BT_MESH_SHELL_DFU_CLI */ + +#if defined(CONFIG_BT_MESH_SHELL_DFU_SRV) + +static struct bt_mesh_model *mod_srv; + +static int cmd_dfu_applied(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod_srv && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_SRV, &mod_srv)) { + return -ENODEV; + } + + bt_mesh_dfu_srv_applied((struct bt_mesh_dfu_srv *)mod_srv->user_data); + return 0; +} + +static int cmd_dfu_rx_cancel(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod_srv && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_SRV, &mod_srv)) { + return -ENODEV; + } + + bt_mesh_dfu_srv_cancel((struct bt_mesh_dfu_srv *)mod_srv->user_data); + return 0; +} + +static int cmd_dfu_rx_progress(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!mod_srv && !bt_mesh_shell_mdl_first_get(BT_MESH_MODEL_ID_DFU_SRV, &mod_srv)) { + return -ENODEV; + } + + shell_print(sh, "DFU progress: %u %%", + bt_mesh_dfu_srv_progress((struct bt_mesh_dfu_srv *)mod_srv->user_data)); + return 0; +} + +#endif /* CONFIG_BT_MESH_SHELL_DFU_SRV */ + +#if defined(CONFIG_BT_MESH_SHELL_DFU_CLI) +BT_MESH_SHELL_MDL_INSTANCE_CMDS(cli_instance_cmds, BT_MESH_MODEL_ID_DFU_CLI, mod_cli); +#endif + +#if defined(CONFIG_BT_MESH_SHELL_DFU_SRV) +BT_MESH_SHELL_MDL_INSTANCE_CMDS(srv_instance_cmds, BT_MESH_MODEL_ID_DFU_SRV, mod_srv); +#endif + +#if defined(CONFIG_BT_MESH_SHELL_DFU_METADATA) +SHELL_STATIC_SUBCMD_SET_CREATE( + dfu_metadata_cmds, + SHELL_CMD_ARG(comp-clear, NULL, NULL, cmd_dfu_comp_clear, 1, 0), + SHELL_CMD_ARG(comp-add, NULL, " ", + cmd_dfu_comp_add, 6, 0), + SHELL_CMD_ARG(comp-elem-add, NULL, " " + "{| }...", + cmd_dfu_comp_elem_add, 5, 10), + SHELL_CMD_ARG(comp-hash-get, NULL, "[<128-bit key>]", cmd_dfu_comp_hash_get, 1, 1), + SHELL_CMD_ARG(metadata-encode, NULL, " " + " []", + cmd_dfu_metadata_encode, 9, 1), + SHELL_SUBCMD_SET_END); +#endif + +#if defined(CONFIG_BT_MESH_SHELL_DFU_SLOT) +SHELL_STATIC_SUBCMD_SET_CREATE( + dfu_slot_cmds, + SHELL_CMD_ARG(add, NULL, + " [ [ []]]", + cmd_dfu_slot_add, 2, 3), + SHELL_CMD_ARG(del, NULL, "", cmd_dfu_slot_del, 2, 0), + SHELL_CMD_ARG(del-all, NULL, NULL, cmd_dfu_slot_del_all, 1, 0), + SHELL_CMD_ARG(get, NULL, "", cmd_dfu_slot_get, 2, 0), + SHELL_SUBCMD_SET_END); +#endif + +#if defined(CONFIG_BT_MESH_SHELL_DFU_CLI) +SHELL_STATIC_SUBCMD_SET_CREATE( + dfu_cli_cmds, + /* DFU Client Model Operations */ + SHELL_CMD_ARG(target, NULL, " ", cmd_dfu_target, 3, + 0), + SHELL_CMD_ARG(targets-reset, NULL, NULL, cmd_dfu_targets_reset, 1, 0), + SHELL_CMD_ARG(target-state, NULL, NULL, cmd_dfu_target_state, 1, 0), + SHELL_CMD_ARG(target-imgs, NULL, "[]", + cmd_dfu_target_imgs, 1, 1), + SHELL_CMD_ARG(target-check, NULL, " ", + cmd_dfu_target_check, 3, 0), + SHELL_CMD_ARG(send, NULL, " [ " + "[ [ ]]]", cmd_dfu_send, 2, 4), + SHELL_CMD_ARG(cancel, NULL, "[]", cmd_dfu_tx_cancel, 1, 1), + SHELL_CMD_ARG(apply, NULL, NULL, cmd_dfu_apply, 0, 0), + SHELL_CMD_ARG(confirm, NULL, NULL, cmd_dfu_confirm, 0, 0), + SHELL_CMD_ARG(suspend, NULL, NULL, cmd_dfu_suspend, 0, 0), + SHELL_CMD_ARG(resume, NULL, NULL, cmd_dfu_resume, 0, 0), + SHELL_CMD_ARG(progress, NULL, NULL, cmd_dfu_tx_progress, 1, 0), + SHELL_CMD(instance, &cli_instance_cmds, "Instance commands", bt_mesh_shell_mdl_cmds_help), + SHELL_SUBCMD_SET_END); +#endif + +#if defined(CONFIG_BT_MESH_SHELL_DFU_SRV) +SHELL_STATIC_SUBCMD_SET_CREATE( + dfu_srv_cmds, + SHELL_CMD_ARG(applied, NULL, NULL, cmd_dfu_applied, 1, 0), + SHELL_CMD_ARG(rx-cancel, NULL, NULL, cmd_dfu_rx_cancel, 1, 0), + SHELL_CMD_ARG(progress, NULL, NULL, cmd_dfu_rx_progress, 1, 0), + SHELL_CMD(instance, &srv_instance_cmds, "Instance commands", bt_mesh_shell_mdl_cmds_help), + SHELL_SUBCMD_SET_END); +#endif + +SHELL_STATIC_SUBCMD_SET_CREATE( + dfu_cmds, +#if defined(CONFIG_BT_MESH_SHELL_DFU_METADATA) + SHELL_CMD(metadata, &dfu_metadata_cmds, "Metadata commands", bt_mesh_shell_mdl_cmds_help), +#endif +#if defined(CONFIG_BT_MESH_SHELL_DFU_SLOT) + SHELL_CMD(slot, &dfu_slot_cmds, "Slot commands", bt_mesh_shell_mdl_cmds_help), +#endif +#if defined(CONFIG_BT_MESH_SHELL_DFU_CLI) + SHELL_CMD(cli, &dfu_cli_cmds, "DFU Cli commands", bt_mesh_shell_mdl_cmds_help), +#endif +#if defined(CONFIG_BT_MESH_SHELL_DFU_SRV) + SHELL_CMD(srv, &dfu_srv_cmds, "DFU Srv commands", bt_mesh_shell_mdl_cmds_help), +#endif + SHELL_SUBCMD_SET_END); + +SHELL_SUBCMD_ADD((mesh, models), dfu, &dfu_cmds, "DFU models commands", + bt_mesh_shell_mdl_cmds_help, 1, 1); diff --git a/subsys/bluetooth/mesh/shell/dfu.h b/subsys/bluetooth/mesh/shell/dfu.h new file mode 100644 index 00000000000..0acc2b192db --- /dev/null +++ b/subsys/bluetooth/mesh/shell/dfu.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +void bt_mesh_shell_dfu_cmds_init(void); diff --git a/subsys/bluetooth/mesh/shell/shell.c b/subsys/bluetooth/mesh/shell/shell.c index 86aee3fbfd9..87241c47fcc 100644 --- a/subsys/bluetooth/mesh/shell/shell.c +++ b/subsys/bluetooth/mesh/shell/shell.c @@ -26,6 +26,8 @@ #include "mesh/settings.h" #include "mesh/access.h" #include "utils.h" +#include "dfu.h" +#include "blob.h" #define CID_NVAL 0xffff @@ -207,6 +209,14 @@ static int cmd_init(const struct shell *sh, size_t argc, char *argv[]) bt_mesh_shell_ctx_shell = sh; shell_print(sh, "Mesh shell initialized"); +#if defined(CONFIG_BT_MESH_SHELL_DFU_CLI) || defined(CONFIG_BT_MESH_SHELL_DFU_SRV) + bt_mesh_shell_dfu_cmds_init(); +#endif +#if defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) || defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) || \ + defined(CONFIG_BT_MESH_SHELL_BLOB_IO_FLASH) + bt_mesh_shell_blob_cmds_init(); +#endif + return 0; } diff --git a/tests/bluetooth/mesh_shell/src/main.c b/tests/bluetooth/mesh_shell/src/main.c index b76673fd566..a74de8ac09d 100644 --- a/tests/bluetooth/mesh_shell/src/main.c +++ b/tests/bluetooth/mesh_shell/src/main.c @@ -16,6 +16,10 @@ static struct bt_mesh_cfg_cli cfg_cli; +#if defined(CONFIG_BT_MESH_DFD_SRV) +static struct bt_mesh_dfd_srv dfd_srv; +#endif + BT_MESH_SHELL_HEALTH_PUB_DEFINE(health_pub); static struct bt_mesh_model root_models[] = { @@ -23,6 +27,20 @@ static struct bt_mesh_model root_models[] = { BT_MESH_MODEL_CFG_CLI(&cfg_cli), BT_MESH_MODEL_HEALTH_SRV(&bt_mesh_shell_health_srv, &health_pub), BT_MESH_MODEL_HEALTH_CLI(&bt_mesh_shell_health_cli), +#if defined(CONFIG_BT_MESH_DFD_SRV) + BT_MESH_MODEL_DFD_SRV(&dfd_srv), +#else +#if defined(CONFIG_BT_MESH_SHELL_DFU_SRV) + BT_MESH_MODEL_DFU_SRV(&bt_mesh_shell_dfu_srv), +#elif defined(CONFIG_BT_MESH_SHELL_BLOB_SRV) + BT_MESH_MODEL_BLOB_SRV(&bt_mesh_shell_blob_srv), +#endif +#if defined(CONFIG_BT_MESH_SHELL_DFU_CLI) + BT_MESH_MODEL_DFU_CLI(&bt_mesh_shell_dfu_cli), +#elif defined(CONFIG_BT_MESH_SHELL_BLOB_CLI) + BT_MESH_MODEL_BLOB_CLI(&bt_mesh_shell_blob_cli), +#endif +#endif /* CONFIG_BT_MESH_DFD_SRV */ }; static struct bt_mesh_elem elements[] = {