Bluetooth: Mesh: Added support for randomly delaying publications
The section 3.7.3.1 of the mesh specification recommends to delay a message publication in certain cases: - at power-up or upon state change for a time between 20 to 500 ms - for periodic publications for a time between 20 to 50 ms This change implements this recommendation by adding the `CONFIG_BT_MESH_DELAYABLE_PUBLICATION` Kconfig option which enables the randomization code and by adding the `bt_mesh_model_pub.delayable` bit field which allows each model decide whether the publications should be delayed for this model or not. Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
This commit is contained in:
parent
640a493c1d
commit
fe70e50d41
7 changed files with 311 additions and 16 deletions
|
@ -255,6 +255,21 @@ message, it will send messages with delay close to expiration to free memory.
|
|||
When the mesh stack is suspended or reset, messages not yet sent are removed and
|
||||
the :c:member:`bt_mesh_send_cb.start` callback is raised with an error code.
|
||||
|
||||
Delayable publications
|
||||
======================
|
||||
|
||||
The delayable publication functionality implements the specification recommendations for message
|
||||
publication delays in the following cases:
|
||||
|
||||
* Between 20 to 500 milliseconds when the Bluetooth Mesh stack starts or when the publication is
|
||||
triggered by the :c:func:`bt_mesh_model_publish` function
|
||||
* Between 20 to 50 milliseconds for periodically published messages
|
||||
|
||||
This feature is optional and enabled with the :kconfig:option:`CONFIG_BT_MESH_DELAYABLE_PUBLICATION`
|
||||
Kconfig option. When enabled, each model can enable or disable the delayable publication by setting
|
||||
the :c:member:`bt_mesh_model_pub.delayable` bit field to ``1`` or ``0`` correspondingly. This bit
|
||||
field can be changed at any time.
|
||||
|
||||
API reference
|
||||
*************
|
||||
|
||||
|
|
|
@ -713,6 +713,8 @@ struct bt_mesh_model_pub {
|
|||
uint8_t period_div:4, /**< Divisor for the Period. */
|
||||
count:4; /**< Transmissions left. */
|
||||
|
||||
uint8_t delayable:1; /**< Use random delay for publishing. */
|
||||
|
||||
uint32_t period_start; /**< Start of the current period. */
|
||||
|
||||
/** @brief Publication buffer, containing the publication message.
|
||||
|
|
|
@ -672,6 +672,15 @@ config BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_COUNT
|
|||
|
||||
endif # BT_MESH_ACCESS_DELAYABLE_MSG
|
||||
|
||||
config BT_MESH_DELAYABLE_PUBLICATION
|
||||
bool "Delayable publication"
|
||||
default y
|
||||
help
|
||||
When enabled, the periodic publications are randomly delayed by 20 to 50ms. Publications
|
||||
triggered at the start of the stack or by the bt_mesh_model_publish() call are delayed by
|
||||
20 to 500ms. This option reduces the probability of collisions when multiple nodes publish
|
||||
at the same time.
|
||||
|
||||
endmenu # Access layer
|
||||
|
||||
menu "Models"
|
||||
|
|
|
@ -33,6 +33,11 @@
|
|||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(bt_mesh_access);
|
||||
|
||||
/* 20 - 50ms */
|
||||
#define RANDOM_DELAY_SHORT 30
|
||||
/* 20 - 500ms */
|
||||
#define RANDOM_DELAY_LONG 480
|
||||
|
||||
/* Model publication information for persistent storage. */
|
||||
struct mod_pub_val {
|
||||
struct {
|
||||
|
@ -761,8 +766,16 @@ static int32_t next_period(const struct bt_mesh_model *mod)
|
|||
|
||||
if (period && elapsed >= period) {
|
||||
LOG_WRN("Retransmission interval is too short");
|
||||
/* Return smallest positive number since 0 means disabled */
|
||||
return 1;
|
||||
|
||||
if (!!pub->delayable) {
|
||||
LOG_WRN("Publication period is too short for"
|
||||
" retransmissions");
|
||||
}
|
||||
|
||||
/* Keep retransmitting the message with the interval sacrificing the
|
||||
* next publication period start.
|
||||
*/
|
||||
return BT_MESH_PUB_TRANSMIT_INT(mod->pub->retransmit);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -775,6 +788,11 @@ static int32_t next_period(const struct bt_mesh_model *mod)
|
|||
|
||||
if (elapsed >= period) {
|
||||
LOG_WRN("Publication sending took longer than the period");
|
||||
|
||||
if (!!pub->delayable) {
|
||||
LOG_WRN("Publication period is too short to be delayable");
|
||||
}
|
||||
|
||||
/* Return smallest positive number since 0 means disabled */
|
||||
return 1;
|
||||
}
|
||||
|
@ -855,6 +873,39 @@ static int pub_period_start(struct bt_mesh_model_pub *pub)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static uint16_t pub_delay_get(int random_delay_window)
|
||||
{
|
||||
if (!IS_ENABLED(CONFIG_BT_MESH_DELAYABLE_PUBLICATION)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t num = 0;
|
||||
|
||||
(void)bt_rand(&num, sizeof(num));
|
||||
|
||||
return 20 + (num % random_delay_window);
|
||||
}
|
||||
|
||||
static int pub_delay_schedule(struct bt_mesh_model_pub *pub, int delay)
|
||||
{
|
||||
uint16_t random;
|
||||
int err;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_BT_MESH_DELAYABLE_PUBLICATION)) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
random = pub_delay_get(delay);
|
||||
err = k_work_reschedule(&pub->timer, K_MSEC(random));
|
||||
if (err < 0) {
|
||||
LOG_ERR("Unable to delay publication (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
LOG_DBG("Publication delayed by %dms", random);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mod_publish(struct k_work *work)
|
||||
{
|
||||
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||
|
@ -890,6 +941,13 @@ static void mod_publish(struct k_work *work)
|
|||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Delay the first publication in a period. */
|
||||
if (!!pub->delayable && !pub_delay_schedule(pub, RANDOM_DELAY_SHORT)) {
|
||||
/* Increment count as it would do BT_MESH_PUB_MSG_TOTAL */
|
||||
pub->count++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
err = publish_transmit(pub->mod);
|
||||
|
@ -1564,6 +1622,18 @@ int bt_mesh_model_publish(const struct bt_mesh_model *model)
|
|||
LOG_DBG("Publish Retransmit Count %u Interval %ums", pub->count,
|
||||
BT_MESH_PUB_TRANSMIT_INT(pub->retransmit));
|
||||
|
||||
/* Delay the publication for longer time when the publication is triggered manually (section
|
||||
* 3.7.3.1):
|
||||
*
|
||||
* When the publication of a message is the result of a power-up, a state transition
|
||||
* progress update, or completion of a state transition, multiple nodes may be reporting the
|
||||
* state change at the same time. To reduce the probability of a message collision, these
|
||||
* messages should be sent with a random delay between 20 and 500 milliseconds.
|
||||
*/
|
||||
if (!!pub->delayable && !pub_delay_schedule(pub, RANDOM_DELAY_LONG)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
k_work_reschedule(&pub->timer, K_NO_WAIT);
|
||||
|
||||
return 0;
|
||||
|
@ -2568,8 +2638,21 @@ static void commit_mod(const struct bt_mesh_model *mod, const struct bt_mesh_ele
|
|||
int32_t ms = bt_mesh_model_pub_period_get(mod);
|
||||
|
||||
if (ms > 0) {
|
||||
LOG_DBG("Starting publish timer (period %u ms)", ms);
|
||||
k_work_schedule(&mod->pub->timer, K_MSEC(ms));
|
||||
/* Delay the first publication after power-up for longer time (section
|
||||
* 3.7.3.1):
|
||||
*
|
||||
* When the publication of a message is the result of a power-up, a state
|
||||
* transition progress update, or completion of a state transition, multiple
|
||||
* nodes may be reporting the state change at the same time. To reduce the
|
||||
* probability of a message collision, these messages should be sent with a
|
||||
* random delay between 20 and 500 milliseconds.
|
||||
*/
|
||||
uint16_t random;
|
||||
|
||||
random = !!mod->pub->delayable ? pub_delay_get(RANDOM_DELAY_LONG) : 0;
|
||||
|
||||
LOG_DBG("Starting publish timer (period %u ms, delay %u ms)", ms, random);
|
||||
k_work_schedule(&mod->pub->timer, K_MSEC(ms + random));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ static const struct {
|
|||
uint8_t div;
|
||||
int32_t period_ms;
|
||||
} test_period[] = {
|
||||
{ BT_MESH_PUB_PERIOD_100MS(1), 0, 100 },
|
||||
{ BT_MESH_PUB_PERIOD_100MS(5), 0, 500 },
|
||||
{ BT_MESH_PUB_PERIOD_SEC(2), 0, 2000 },
|
||||
{ BT_MESH_PUB_PERIOD_10SEC(1), 0, 10000 },
|
||||
|
@ -522,6 +523,91 @@ static void msgf_publish(void)
|
|||
bt_mesh_model_publish(model);
|
||||
}
|
||||
|
||||
static void pub_delayable_check(int32_t interval, uint8_t count)
|
||||
{
|
||||
int64_t timestamp = k_uptime_get();
|
||||
int err;
|
||||
|
||||
for (size_t j = 0; j < count; j++) {
|
||||
/* Every new publication will release semaphore in the update handler and the time
|
||||
* between two consecutive publications will be measured.
|
||||
*/
|
||||
err = k_sem_take(&publish_sem, K_SECONDS(20));
|
||||
if (err) {
|
||||
FAIL("Send timed out");
|
||||
}
|
||||
|
||||
int32_t time_delta = k_uptime_delta(×tamp);
|
||||
int32_t pub_delta = time_delta - interval;
|
||||
|
||||
LOG_DBG("Send time: %d delta: %d pub_delta: %d", (int32_t)timestamp, time_delta,
|
||||
pub_delta);
|
||||
|
||||
if (j == 0) {
|
||||
/* The first delta will be between the messages published manually and next
|
||||
* publication (or retransmission). So the time difference should not be
|
||||
* longer than 500 - 20 + 10 (margin):
|
||||
*
|
||||
* |---|-------|--------|-------|---->
|
||||
* M1 20ms tx(M1) 500ms
|
||||
* update()
|
||||
*/
|
||||
ASSERT_IN_RANGE(pub_delta, 0, 510);
|
||||
} else {
|
||||
/* Time difference between the consequtive update callback calls should be
|
||||
* within a small margin like without random delay as the callbacks should
|
||||
* be called at the regular interval or immediately (if it passed the next
|
||||
* period time).
|
||||
*/
|
||||
ASSERT_IN_RANGE(pub_delta, 0, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void recv_delayable_check(int32_t interval, uint8_t count)
|
||||
{
|
||||
int64_t timestamp;
|
||||
int err;
|
||||
|
||||
/* The measurement starts by the first received message. */
|
||||
err = k_sem_take(&publish_sem, K_SECONDS(20));
|
||||
if (err) {
|
||||
FAIL("Recv timed out");
|
||||
}
|
||||
|
||||
timestamp = k_uptime_get();
|
||||
|
||||
for (size_t j = 0; j < count; j++) {
|
||||
/* Every new received message will release semaphore in the message handler and
|
||||
* the time between two consecutive publications will be measured.
|
||||
*/
|
||||
err = k_sem_take(&publish_sem, K_SECONDS(20));
|
||||
if (err) {
|
||||
FAIL("Recv timed out");
|
||||
}
|
||||
|
||||
int32_t time_delta = k_uptime_delta(×tamp);
|
||||
/* First message can be delayed up to 500ms, others for up to 50ms. */
|
||||
int32_t upper_delay = j == 0 ? 500 : 50;
|
||||
|
||||
/*
|
||||
* Lower boundary: tx2 - tx1 + interval
|
||||
* |---|-------|---------------|-------|----->
|
||||
* M1 tx1(50ms/500ms) M2 tx2(20ms)
|
||||
*
|
||||
* Upper boundary: tx2 - tx1 + interval
|
||||
* |---|-------|--------|-----------|----->
|
||||
* M1 tx1(20ms) M2 tx2(50ms/500ms)
|
||||
*/
|
||||
int32_t lower_boundary = 20 - upper_delay + interval;
|
||||
int32_t upper_boundary = upper_delay - 20 + interval;
|
||||
|
||||
LOG_DBG("Recv time: %d delta: %d boundaries: %d/%d", (int32_t)timestamp, time_delta,
|
||||
lower_boundary, upper_boundary);
|
||||
ASSERT_IN_RANGE(time_delta, lower_boundary, upper_boundary + RX_JITTER_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
static void pub_jitter_check(int32_t interval, uint8_t count)
|
||||
{
|
||||
int64_t timestamp = k_uptime_get();
|
||||
|
@ -578,8 +664,8 @@ static void recv_jitter_check(int32_t interval, uint8_t count)
|
|||
|
||||
jitter = MAX(pub_delta, jitter);
|
||||
|
||||
LOG_DBG("Recv time: %d delta: %d jitter: %d", (int32_t)timestamp, time_delta,
|
||||
jitter);
|
||||
LOG_DBG("Recv time: %d delta: %d jitter: %d, j: %d", (int32_t)timestamp, time_delta,
|
||||
jitter, j);
|
||||
}
|
||||
|
||||
LOG_INF("Recv jitter: %d", jitter);
|
||||
|
@ -589,17 +675,19 @@ static void recv_jitter_check(int32_t interval, uint8_t count)
|
|||
/* Test publish period states by publishing a message and checking interval between update handler
|
||||
* calls.
|
||||
*/
|
||||
static void test_tx_period(void)
|
||||
static void tx_period(bool delayable)
|
||||
{
|
||||
const struct bt_mesh_model *model = &models[2];
|
||||
|
||||
bt_mesh_test_cfg_set(NULL, 60);
|
||||
bt_mesh_test_cfg_set(NULL, 70);
|
||||
bt_mesh_device_setup(&prov, &local_comp);
|
||||
provision(UNICAST_ADDR1);
|
||||
common_configure(UNICAST_ADDR1);
|
||||
|
||||
k_sem_init(&publish_sem, 0, 1);
|
||||
|
||||
model->pub->delayable = delayable;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(test_period); i++) {
|
||||
pub_param_set(test_period[i].period, 0);
|
||||
|
||||
|
@ -611,7 +699,11 @@ static void test_tx_period(void)
|
|||
/* Start publishing messages and measure jitter. */
|
||||
msgf_publish();
|
||||
publish_allow = true;
|
||||
pub_jitter_check(test_period[i].period_ms, PUB_PERIOD_COUNT);
|
||||
if (delayable) {
|
||||
pub_delayable_check(test_period[i].period_ms, PUB_PERIOD_COUNT);
|
||||
} else {
|
||||
pub_jitter_check(test_period[i].period_ms, PUB_PERIOD_COUNT);
|
||||
}
|
||||
|
||||
/* Disable periodic publication before the next test iteration. */
|
||||
publish_allow = false;
|
||||
|
@ -626,9 +718,9 @@ static void test_tx_period(void)
|
|||
/* Receive a periodically published message and check publication period by measuring interval
|
||||
* between message handler calls.
|
||||
*/
|
||||
static void test_rx_period(void)
|
||||
static void rx_period(bool delayable)
|
||||
{
|
||||
bt_mesh_test_cfg_set(NULL, 60);
|
||||
bt_mesh_test_cfg_set(NULL, 70);
|
||||
bt_mesh_device_setup(&prov, &local_comp);
|
||||
provision(UNICAST_ADDR2);
|
||||
common_configure(UNICAST_ADDR2);
|
||||
|
@ -636,16 +728,40 @@ static void test_rx_period(void)
|
|||
k_sem_init(&publish_sem, 0, 1);
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(test_period); i++) {
|
||||
recv_jitter_check(test_period[i].period_ms, PUB_PERIOD_COUNT);
|
||||
if (delayable) {
|
||||
recv_delayable_check(test_period[i].period_ms, PUB_PERIOD_COUNT);
|
||||
} else {
|
||||
recv_jitter_check(test_period[i].period_ms, PUB_PERIOD_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
static void test_tx_period(void)
|
||||
{
|
||||
tx_period(false);
|
||||
}
|
||||
|
||||
static void test_rx_period(void)
|
||||
{
|
||||
rx_period(false);
|
||||
}
|
||||
|
||||
static void test_tx_period_delayable(void)
|
||||
{
|
||||
tx_period(true);
|
||||
}
|
||||
|
||||
static void test_rx_period_delayable(void)
|
||||
{
|
||||
rx_period(true);
|
||||
}
|
||||
|
||||
/* Test publish retransmit interval and count states by publishing a message and checking interval
|
||||
* between update handler calls.
|
||||
*/
|
||||
static void test_tx_transmit(void)
|
||||
static void tx_transmit(bool delayable)
|
||||
{
|
||||
const struct bt_mesh_model *model = &models[2];
|
||||
uint8_t status;
|
||||
|
@ -672,6 +788,7 @@ static void test_tx_transmit(void)
|
|||
|
||||
publish_allow = true;
|
||||
model->pub->retr_update = true;
|
||||
model->pub->delayable = delayable;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(test_transmit); i++) {
|
||||
pub_param_set(0, test_transmit[i]);
|
||||
|
@ -683,7 +800,11 @@ static void test_tx_transmit(void)
|
|||
|
||||
/* Start publishing messages and measure jitter. */
|
||||
msgf_publish();
|
||||
pub_jitter_check(interval, count);
|
||||
if (delayable) {
|
||||
pub_delayable_check(interval, count);
|
||||
} else {
|
||||
pub_jitter_check(interval, count);
|
||||
}
|
||||
|
||||
/* Let the receiver hit the first semaphore. */
|
||||
k_sleep(K_SECONDS(1));
|
||||
|
@ -695,7 +816,7 @@ static void test_tx_transmit(void)
|
|||
/* Receive a published message and check retransmission interval by measuring interval between
|
||||
* message handler calls.
|
||||
*/
|
||||
static void test_rx_transmit(void)
|
||||
static void rx_transmit(bool delayable)
|
||||
{
|
||||
bt_mesh_test_cfg_set(NULL, 60);
|
||||
bt_mesh_device_setup(&prov, &local_comp);
|
||||
|
@ -708,12 +829,36 @@ static void test_rx_transmit(void)
|
|||
int32_t interval = BT_MESH_PUB_TRANSMIT_INT(test_transmit[i]);
|
||||
int count = BT_MESH_PUB_TRANSMIT_COUNT(test_transmit[i]);
|
||||
|
||||
recv_jitter_check(interval, count);
|
||||
if (delayable) {
|
||||
recv_delayable_check(interval, count);
|
||||
} else {
|
||||
recv_jitter_check(interval, count);
|
||||
}
|
||||
}
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
static void test_tx_transmit(void)
|
||||
{
|
||||
tx_transmit(false);
|
||||
}
|
||||
|
||||
static void test_rx_transmit(void)
|
||||
{
|
||||
rx_transmit(false);
|
||||
}
|
||||
|
||||
static void test_tx_transmit_delayable(void)
|
||||
{
|
||||
tx_transmit(true);
|
||||
}
|
||||
|
||||
static void test_rx_transmit_delayable(void)
|
||||
{
|
||||
rx_transmit(true);
|
||||
}
|
||||
|
||||
/* Cancel one of messages to be published and check that the next one is published when next period
|
||||
* starts.
|
||||
*/
|
||||
|
@ -841,6 +986,13 @@ static const struct bst_test_instance test_access[] = {
|
|||
TEST_CASE(tx, cancel, "Access: Cancel a message during publication"),
|
||||
TEST_CASE(rx, cancel, "Access: Receive published messages except cancelled"),
|
||||
|
||||
TEST_CASE(tx, period_delayable, "Access: Test delayable periodic publication"),
|
||||
TEST_CASE(rx, period_delayable, "Access: Receive delayable periodic publication"),
|
||||
|
||||
TEST_CASE(tx, transmit_delayable, "Access: Test delayable publication with retransmission"),
|
||||
TEST_CASE(rx, transmit_delayable, "Access: Receive delayable publication with"
|
||||
" retransmissions"),
|
||||
|
||||
BSTEST_END_MARKER
|
||||
};
|
||||
|
||||
|
|
17
tests/bsim/bluetooth/mesh/tests_scripts/access/access_period_delayable.sh
Executable file
17
tests/bsim/bluetooth/mesh/tests_scripts/access/access_period_delayable.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright 2023 Nordic Semiconductor
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
source $(dirname "${BASH_SOURCE[0]}")/../../_mesh_test.sh
|
||||
|
||||
RunTest mesh_access_pub_period_delayable_retr \
|
||||
access_tx_period_delayable access_rx_period_delayable
|
||||
|
||||
conf=prj_mesh1d1_conf
|
||||
RunTest mesh_access_pub_period_delayable_retr_1d1 \
|
||||
access_tx_period_delayable access_rx_period_delayable
|
||||
|
||||
conf=prj_mesh1d1_conf
|
||||
overlay=overlay_psa_conf
|
||||
RunTest mesh_access_pub_period_delayable_retr_psa \
|
||||
access_tx_period_delayable access_rx_period_delayable
|
17
tests/bsim/bluetooth/mesh/tests_scripts/access/access_transmit_delayable.sh
Executable file
17
tests/bsim/bluetooth/mesh/tests_scripts/access/access_transmit_delayable.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright 2023 Nordic Semiconductor
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
source $(dirname "${BASH_SOURCE[0]}")/../../_mesh_test.sh
|
||||
|
||||
RunTest mesh_access_pub_transmit_delayable_retr \
|
||||
access_tx_transmit_delayable access_rx_transmit_delayable
|
||||
|
||||
conf=prj_mesh1d1_conf
|
||||
RunTest mesh_access_pub_transmit_delayable_retr_1d1 \
|
||||
access_tx_transmit_delayable access_rx_transmit_delayable
|
||||
|
||||
conf=prj_mesh1d1_conf
|
||||
overlay=overlay_psa_conf
|
||||
RunTest mesh_access_pub_transmit_delayable_retr_psa \
|
||||
access_tx_period_delayable access_rx_period_delayable
|
Loading…
Add table
Add a link
Reference in a new issue