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:
Pavel Vasilyev 2023-12-11 23:29:36 +01:00 committed by Carles Cufí
commit fe70e50d41
7 changed files with 311 additions and 16 deletions

View file

@ -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"

View file

@ -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));
}
}