blob: a6888da427041379eb65f70a9fff6552b8706ec9 [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "blob.h"
#include <stdlib.h>
#include <zephyr/shell/shell.h>
#include <zephyr/bluetooth/mesh.h>
#include <zephyr/bluetooth/mesh/shell.h>
#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 = DIV_ROUND_UP(info->size, 1U << info->block_size_log);
blocks_not_rxed_size = DIV_ROUND_UP(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 const 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->rt->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->rt->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->rt->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->rt->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->rt->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->rt->user_data);
return 0;
}
#endif /* CONFIG_BT_MESH_SHELL_BLOB_CLI */
#if defined(CONFIG_BT_MESH_SHELL_BLOB_SRV)
static const 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->rt->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->rt->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, "<Addr>", cmd_target, 2, 0),
SHELL_CMD_ARG(caps, NULL, "[<Group> [<TimeoutBase>]]", cmd_caps, 1, 2),
SHELL_CMD_ARG(tx, NULL, "<ID> <Size> <BlockSizeLog> "
"<ChunkSize> [<Group> [<Mode(push, pull)> "
"[<TimeoutBase>]]]", 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, "<ID> [<TimeoutBase(10s steps)>]", 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, "<AreaID> [<Offset>]",
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);