This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Two models in one device: combination of light / light_switch sample applications

I have used two sample applications light / light_switch from bluetooth mesh to get a device with 2 elements:

- generic on/off clent

- generic on/off server

I want to be able to configure it to make the client element talk to the server element via loopback within the device.

The board have single led and single button. Source code (model_handler.c) attached below.

/*
 * Copyright (c) 2019 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic
 */

#include <bluetooth/bluetooth.h>
#include <bluetooth/mesh/models.h>
#include <dk_buttons_and_leds.h>
#include "model_handler.h"

static void led_set(struct bt_mesh_onoff_srv *srv, struct bt_mesh_msg_ctx *ctx,
		    const struct bt_mesh_onoff_set *set,
		    struct bt_mesh_onoff_status *rsp);

static void led_get(struct bt_mesh_onoff_srv *srv, struct bt_mesh_msg_ctx *ctx,
		    struct bt_mesh_onoff_status *rsp);

static const struct bt_mesh_onoff_srv_handlers onoff_handlers = {
	.set = led_set,
	.get = led_get,
};

struct led_ctx {
	struct bt_mesh_onoff_srv srv;
	struct k_delayed_work work;
	uint32_t remaining;
	bool value;
};

static struct led_ctx led_ctx[3] = {
	[0 ... 2] = {
		.srv = BT_MESH_ONOFF_SRV_INIT(&onoff_handlers),
	}
};

static void led_transition_start(struct led_ctx *led)
{
	int led_idx = led - &led_ctx[0];

	/* As long as the transition is in progress, the onoff
	 * state is "on":
	 */
	dk_set_led(led_idx, true);
	k_delayed_work_submit(&led->work, K_MSEC(led->remaining));
	led->remaining = 0;
}

static void led_status(struct led_ctx *led, struct bt_mesh_onoff_status *status)
{
	status->remaining_time =
		k_delayed_work_remaining_get(&led->work) + led->remaining;
	status->target_on_off = led->value;
	/* As long as the transition is in progress, the onoff state is "on": */
	status->present_on_off = led->value || status->remaining_time;
}

static void led_set(struct bt_mesh_onoff_srv *srv, struct bt_mesh_msg_ctx *ctx,
		    const struct bt_mesh_onoff_set *set,
		    struct bt_mesh_onoff_status *rsp)
{
	struct led_ctx *led = CONTAINER_OF(srv, struct led_ctx, srv);
	int led_idx = led - &led_ctx[0];

	if (set->on_off == led->value) {
		goto respond;
	}

	led->value = set->on_off;
	led->remaining = set->transition->time;

	if (set->transition->delay > 0) {
		k_delayed_work_submit(&led->work,
				      K_MSEC(set->transition->delay));
	} else if (set->transition->time > 0) {
		led_transition_start(led);
	} else {
		dk_set_led(led_idx, set->on_off);
	}

respond:
	if (rsp) {
		led_status(led, rsp);
	}
}

static void led_get(struct bt_mesh_onoff_srv *srv, struct bt_mesh_msg_ctx *ctx,
		    struct bt_mesh_onoff_status *rsp)
{
	struct led_ctx *led = CONTAINER_OF(srv, struct led_ctx, srv);

	led_status(led, rsp);
}

static void led_work(struct k_work *work)
{
	struct led_ctx *led = CONTAINER_OF(work, struct led_ctx, work.work);
	int led_idx = led - &led_ctx[0];

	if (led->remaining) {
		led_transition_start(led);
	} else {
		dk_set_led(led_idx, led->value);

		/* Publish the new value at the end of the transition */
		struct bt_mesh_onoff_status status;

		led_status(led, &status);
		bt_mesh_onoff_srv_pub(&led->srv, NULL, &status);
	}
}

/* Light switch behavior */

/** Context for a single light switch. */
struct button {
	/** Current light status of the corresponding server. */
	bool status;
	/** Generic OnOff client instance for this switch. */
	struct bt_mesh_onoff_cli client;
};

static void status_handler(struct bt_mesh_onoff_cli *cli,
			   struct bt_mesh_msg_ctx *ctx,
			   const struct bt_mesh_onoff_status *status);

static struct button buttons[4] = {
	[0 ... 3] = { .client = BT_MESH_ONOFF_CLI_INIT(&status_handler) },
};

static void status_handler(struct bt_mesh_onoff_cli *cli,
			   struct bt_mesh_msg_ctx *ctx,
			   const struct bt_mesh_onoff_status *status)
{
	struct button *button =
		CONTAINER_OF(cli, struct button, client);
	int index = button - &buttons[0];

	button->status = status->present_on_off;
	dk_set_led(index, status->present_on_off);

	printk("Button %d: Received response: %s\n", index + 1,
	       status->present_on_off ? "on" : "off");
}

static void button_handler_cb(uint32_t pressed, uint32_t changed)
{
	if (!bt_mesh_is_provisioned()) {
		return;
	}

	for (int i = 0; i < 4; ++i) {
		if (!(pressed & changed & BIT(i))) {
			continue;
		}

		struct bt_mesh_onoff_set set = {
			.on_off = !buttons[i].status,
		};
		int err;

		/* As we can't know how many nodes are in a group, it doesn't
		 * make sense to send acknowledged messages to group addresses -
		 * we won't be able to make use of the responses anyway.
		 */
		if (bt_mesh_model_pub_is_unicast(buttons[i].client.model)) {
			err = bt_mesh_onoff_cli_set(&buttons[i].client, NULL,
						    &set, NULL);
		} else {
			err = bt_mesh_onoff_cli_set_unack(&buttons[i].client,
							  NULL, &set);
			if (!err) {
				/* There'll be no response status for the
				 * unacked message. Set the state immediately.
				 */
				buttons[i].status = set.on_off;
				//dk_set_led(i, set.on_off);
			}
		}

		if (err) {
			printk("OnOff %d set failed: %d\n", i + 1, err);
		}
	}
}

/** Configuration server definition */
static struct bt_mesh_cfg_srv cfg_srv = {
	.relay = IS_ENABLED(CONFIG_BT_MESH_RELAY),
	.beacon = BT_MESH_BEACON_ENABLED,
	.frnd = IS_ENABLED(CONFIG_BT_MESH_FRIEND),
	.gatt_proxy = IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY),
	.default_ttl = 7,

	/* 3 transmissions with 20ms interval */
	.net_transmit = BT_MESH_TRANSMIT(2, 20),
	.relay_retransmit = BT_MESH_TRANSMIT(2, 20),
};

/* Set up a repeating delayed work to blink the DK's LEDs when attention is
 * requested.
 */
static struct k_delayed_work attention_blink_work;

static void attention_blink(struct k_work *work)
{
	static int idx;
	const uint8_t pattern[] = {
		BIT(0) | BIT(1),
		BIT(1) | BIT(2),
		BIT(2) | BIT(3),
		BIT(3) | BIT(0),
	};
	
	dk_set_leds(pattern[idx++ % ARRAY_SIZE(pattern)]);
	k_delayed_work_submit(&attention_blink_work, K_MSEC(30));
}

static void attention_on(struct bt_mesh_model *mod)
{
	k_delayed_work_submit(&attention_blink_work, K_NO_WAIT);
}

static void attention_off(struct bt_mesh_model *mod)
{
	k_delayed_work_cancel(&attention_blink_work);
	dk_set_leds(DK_NO_LEDS_MSK);
}

static const struct bt_mesh_health_srv_cb health_srv_cb = {
	.attn_on = attention_on,
	.attn_off = attention_off,
};

static struct bt_mesh_health_srv health_srv = {
	.cb = &health_srv_cb,
};

BT_MESH_HEALTH_PUB_DEFINE(health_pub, 0);

static struct bt_mesh_elem elements[] = {
	BT_MESH_ELEM(1,
		     BT_MESH_MODEL_LIST(
			     BT_MESH_MODEL_CFG_SRV(&cfg_srv),
			     BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
			     BT_MESH_MODEL_ONOFF_CLI(&buttons[0].client)),
		     BT_MESH_MODEL_NONE),
	//BT_MESH_ELEM(2,
	//	     BT_MESH_MODEL_LIST(
	//		     BT_MESH_MODEL_ONOFF_CLI(&buttons[1].client)),
	//	     BT_MESH_MODEL_NONE),
	//BT_MESH_ELEM(3,
	//	     BT_MESH_MODEL_LIST(
	//		     BT_MESH_MODEL_ONOFF_CLI(&buttons[2].client)),
	//	     BT_MESH_MODEL_NONE),
	//BT_MESH_ELEM(4,
	//	     BT_MESH_MODEL_LIST(
	//		     BT_MESH_MODEL_ONOFF_CLI(&buttons[3].client)),
	//	     BT_MESH_MODEL_NONE),
	//BT_MESH_ELEM(5,
	//	     BT_MESH_MODEL_LIST(
	//		     BT_MESH_MODEL_ONOFF_CLI(&buttons[4].client)),
	//	     BT_MESH_MODEL_NONE),
	//BT_MESH_ELEM(6,
	//	     BT_MESH_MODEL_LIST(
	//		     BT_MESH_MODEL_ONOFF_CLI(&buttons[5].client)),
	//	     BT_MESH_MODEL_NONE),	 
	BT_MESH_ELEM(
		7, BT_MESH_MODEL_LIST(BT_MESH_MODEL_ONOFF_SRV(&led_ctx[0].srv)),
		BT_MESH_MODEL_NONE),
	//BT_MESH_ELEM(
	//	8, BT_MESH_MODEL_LIST(BT_MESH_MODEL_ONOFF_SRV(&led_ctx[1].srv)),
	//	BT_MESH_MODEL_NONE),
	//BT_MESH_ELEM(
	//	9, BT_MESH_MODEL_LIST(BT_MESH_MODEL_ONOFF_SRV(&led_ctx[2].srv)),
	//	BT_MESH_MODEL_NONE),
};

static const struct bt_mesh_comp comp = {
	.cid = CONFIG_BT_COMPANY_ID,
	.elem = elements,
	.elem_count = ARRAY_SIZE(elements),
};

const struct bt_mesh_comp *model_handler_init(void)
{
	static struct button_handler button_handler = {
		.cb = button_handler_cb,
	};

	dk_button_handler_add(&button_handler);

	k_delayed_work_init(&attention_blink_work, attention_blink);

	for (int i = 0; i < ARRAY_SIZE(led_ctx); ++i) {
		k_delayed_work_init(&led_ctx[i].work, led_work);
	}

	return &comp;
}

Is there anything in solution / project configuration to enable loopback?

Is there anything in mesh configuration to make it work?

Regards

Jarek

Parents
  • Hi,

    The loopback should work out-of-the-box, as long as the models are implemented correctly.

    You need to configure the publication and/or subscription of the models, using either group addresses or the element addresses of the server and client models. Just as you would if the models were on two different nodes.

    I recommend that you use the nRF Mesh smartphone app for configuration, and not the provisioner example, as the provisioner example expects the client and server to be on different nodes and will probably not work (without changes) for configuring your device.

    Regards,
    Terje

Reply
  • Hi,

    The loopback should work out-of-the-box, as long as the models are implemented correctly.

    You need to configure the publication and/or subscription of the models, using either group addresses or the element addresses of the server and client models. Just as you would if the models were on two different nodes.

    I recommend that you use the nRF Mesh smartphone app for configuration, and not the provisioner example, as the provisioner example expects the client and server to be on different nodes and will probably not work (without changes) for configuring your device.

    Regards,
    Terje

Children
No Data
Related