Bluetooth: Mesh: Improve Low Power establishment procedure

Add some automated policies for starting LPN establishment and make it
possible to perform the establishment in a "low power" way, i.e.
switching to low duty-cycle already when starting to send Friend
Requests.

Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
This commit is contained in:
Johan Hedberg 2017-11-09 10:28:44 +02:00 committed by Johan Hedberg
commit b95d70c3f0
5 changed files with 164 additions and 35 deletions

View file

@ -186,6 +186,42 @@ config BT_MESH_LOW_POWER
Enable this option to be able to act as a Low Power Node. Enable this option to be able to act as a Low Power Node.
if BT_MESH_LOW_POWER if BT_MESH_LOW_POWER
config BT_MESH_LPN_ESTABLISHMENT
bool "Perform Friendship establishment using low power"
default y
help
Perform the Friendship establishment using low power, with
the help of a reduced scan duty cycle. The downside of this
is that the node may miss out on messages intended for it
until it has successfully set up Friendship with a Friend
node.
config BT_MESH_LPN_AUTO
bool "Automatically start looking for Friend nodes once provisioned"
default y
help
Automatically enable LPN functionality once provisioned and start
looking for Friend nodes. If this option is disabled LPN mode
needs to be manually enabled by calling bt_mesh_lpn_set(true).
config BT_MESH_LPN_AUTO_TIMEOUT
int "Time from last received message before going to LPN mode"
default 15
range 0 3600
depends on BT_MESH_LPN_AUTO
help
Time in seconds from the last received message, that the node
will wait before starting to look for Friend nodes.
config BT_MESH_LPN_RETRY_TIMEOUT
int "Retry timeout for Friend requests"
default 8
range 1 3600
help
Time in seconds between Friend Requests, if a previous Friend
Request did not receive any acceptable Friend Offers.
config BT_MESH_LPN_RSSI_FACTOR config BT_MESH_LPN_RSSI_FACTOR
int "RSSIFactor, used in the Friend Offer Delay calculation" int "RSSIFactor, used in the Friend Offer Delay calculation"
range 0 3 range 0 3

View file

@ -26,12 +26,21 @@
#include "beacon.h" #include "beacon.h"
#include "lpn.h" #include "lpn.h"
#if defined(CONFIG_BT_MESH_LPN_AUTO)
#define LPN_AUTO_TIMEOUT K_SECONDS(CONFIG_BT_MESH_LPN_AUTO_TIMEOUT)
#else
#define LPN_AUTO_TIMEOUT 0
#endif
#define LPN_RECV_DELAY CONFIG_BT_MESH_LPN_RECV_DELAY #define LPN_RECV_DELAY CONFIG_BT_MESH_LPN_RECV_DELAY
#define SCAN_LATENCY min(CONFIG_BT_MESH_LPN_SCAN_LATENCY, \ #define SCAN_LATENCY min(CONFIG_BT_MESH_LPN_SCAN_LATENCY, \
LPN_RECV_DELAY) LPN_RECV_DELAY)
#define FRIEND_REQ_RETRY_TIMEOUT K_SECONDS(5) #define FRIEND_REQ_RETRY_TIMEOUT K_SECONDS(CONFIG_BT_MESH_LPN_RETRY_TIMEOUT)
#define FRIEND_REQ_TIMEOUT (K_MSEC(100) + K_SECONDS(1))
#define FRIEND_REQ_WAIT K_MSEC(100)
#define FRIEND_REQ_SCAN K_SECONDS(1)
#define FRIEND_REQ_TIMEOUT (FRIEND_REQ_WAIT + FRIEND_REQ_SCAN)
#define POLL_RETRY_TIMEOUT K_MSEC(100) #define POLL_RETRY_TIMEOUT K_MSEC(100)
@ -59,12 +68,14 @@ static const char *state2str(int state)
return "disabled"; return "disabled";
case BT_MESH_LPN_CLEAR: case BT_MESH_LPN_CLEAR:
return "clear"; return "clear";
case BT_MESH_LPN_TIMER:
return "timer";
case BT_MESH_LPN_ENABLED: case BT_MESH_LPN_ENABLED:
return "enabled"; return "enabled";
case BT_MESH_LPN_REQ_WAIT:
return "req wait";
case BT_MESH_LPN_WAIT_OFFER: case BT_MESH_LPN_WAIT_OFFER:
return "wait offer"; return "wait offer";
case BT_MESH_LPN_ESTABLISHING:
return "establishing";
case BT_MESH_LPN_ESTABLISHED: case BT_MESH_LPN_ESTABLISHED:
return "established"; return "established";
case BT_MESH_LPN_RECV_DELAY: case BT_MESH_LPN_RECV_DELAY:
@ -133,7 +144,7 @@ static void clear_friendship(bool disable)
{ {
struct bt_mesh_lpn *lpn = &bt_mesh.lpn; struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
if (lpn->state >= BT_MESH_LPN_ESTABLISHED) { if (lpn->established) {
send_friend_clear(); send_friend_clear();
lpn->disable = disable; lpn->disable = disable;
return; return;
@ -152,6 +163,7 @@ static void clear_friendship(bool disable)
lpn->queue_size = 0; lpn->queue_size = 0;
lpn->disable = 0; lpn->disable = 0;
lpn->sent_req = 0; lpn->sent_req = 0;
lpn->established = 0;
/* Set this to 1 to force group subscription when the next /* Set this to 1 to force group subscription when the next
* Friendship is created, in case lpn->groups doesn't get * Friendship is created, in case lpn->groups doesn't get
@ -170,13 +182,20 @@ static void clear_friendship(bool disable)
static void friend_req_sent(struct net_buf *buf, int err) static void friend_req_sent(struct net_buf *buf, int err)
{ {
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
if (err) { if (err) {
BT_ERR("Sending Friend Request failed (err %d)", err); BT_ERR("Sending Friend Request failed (err %d)", err);
return; return;
} }
lpn_set_state(BT_MESH_LPN_WAIT_OFFER); if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
k_delayed_work_submit(&bt_mesh.lpn.timer, FRIEND_REQ_TIMEOUT); k_delayed_work_submit(&lpn->timer, FRIEND_REQ_WAIT);
lpn_set_state(BT_MESH_LPN_REQ_WAIT);
} else {
k_delayed_work_submit(&lpn->timer, FRIEND_REQ_TIMEOUT);
lpn_set_state(BT_MESH_LPN_WAIT_OFFER);
}
} }
static int send_friend_req(void) static int send_friend_req(void)
@ -264,16 +283,16 @@ static void req_sent(struct net_buf *buf, int err)
lpn->req_attempts++; lpn->req_attempts++;
if (lpn->state == BT_MESH_LPN_ESTABLISHING) { if (lpn->established || IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
k_delayed_work_submit(&lpn->timer,
LPN_RECV_DELAY + lpn->recv_win);
} else { /* Established Friendship */
lpn_set_state(BT_MESH_LPN_RECV_DELAY); lpn_set_state(BT_MESH_LPN_RECV_DELAY);
/* We start scanning a bit early to elimitate risk of missing /* We start scanning a bit early to elimitate risk of missing
* response data due to HCI and other latencies. * response data due to HCI and other latencies.
*/ */
k_delayed_work_submit(&lpn->timer, k_delayed_work_submit(&lpn->timer,
LPN_RECV_DELAY - SCAN_LATENCY); LPN_RECV_DELAY - SCAN_LATENCY);
} else {
k_delayed_work_submit(&lpn->timer,
LPN_RECV_DELAY + lpn->recv_win);
} }
} }
@ -326,12 +345,14 @@ void bt_mesh_lpn_disable(void)
int bt_mesh_lpn_set(bool enable) int bt_mesh_lpn_set(bool enable)
{ {
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
if (enable) { if (enable) {
if (bt_mesh.lpn.state != BT_MESH_LPN_DISABLED) { if (lpn->state != BT_MESH_LPN_DISABLED) {
return 0; return 0;
} }
} else { } else {
if (bt_mesh.lpn.state == BT_MESH_LPN_DISABLED) { if (lpn->state == BT_MESH_LPN_DISABLED) {
return 0; return 0;
} }
} }
@ -347,9 +368,21 @@ int bt_mesh_lpn_set(bool enable)
} }
if (enable) { if (enable) {
lpn_set_state(BT_MESH_LPN_ENABLED);
if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
bt_mesh_scan_disable();
}
send_friend_req(); send_friend_req();
} else { } else {
bt_mesh_lpn_disable(); if (IS_ENABLED(CONFIG_BT_MESH_LPN_AUTO) &&
lpn->state == BT_MESH_LPN_TIMER) {
k_delayed_work_cancel(&lpn->timer);
lpn_set_state(BT_MESH_LPN_DISABLED);
} else {
bt_mesh_lpn_disable();
}
} }
return 0; return 0;
@ -374,6 +407,12 @@ void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx)
{ {
struct bt_mesh_lpn *lpn = &bt_mesh.lpn; struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
if (lpn->state == BT_MESH_LPN_TIMER) {
BT_DBG("Restarting establishment timer");
k_delayed_work_submit(&lpn->timer, LPN_AUTO_TIMEOUT);
return;
}
if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) { if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) {
BT_WARN("Unexpected message withouth a preceding Poll"); BT_WARN("Unexpected message withouth a preceding Poll");
return; return;
@ -452,7 +491,6 @@ int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx,
} }
lpn->counter++; lpn->counter++;
lpn_set_state(BT_MESH_LPN_ESTABLISHING);
return 0; return 0;
} }
@ -614,6 +652,32 @@ static bool sub_update(u8_t op)
return true; return true;
} }
static void update_timeout(struct bt_mesh_lpn *lpn)
{
lpn->sent_req = 0;
if (lpn->established) {
BT_WARN("No response from Friend during ReceiveWindow");
bt_mesh_scan_disable();
lpn_set_state(BT_MESH_LPN_ESTABLISHED);
k_delayed_work_submit(&lpn->timer, POLL_RETRY_TIMEOUT);
} else {
if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
bt_mesh_scan_disable();
}
if (lpn->req_attempts < 6) {
BT_WARN("Retrying first Friend Poll");
if (send_friend_poll() == 0) {
return;
}
}
BT_ERR("Timed out waiting for first Friend Update");
clear_friendship(false);
}
}
static void lpn_timeout(struct k_work *work) static void lpn_timeout(struct k_work *work)
{ {
struct bt_mesh_lpn *lpn = &bt_mesh.lpn; struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
@ -628,26 +692,30 @@ static void lpn_timeout(struct k_work *work)
case BT_MESH_LPN_CLEAR: case BT_MESH_LPN_CLEAR:
clear_friendship(bt_mesh.lpn.disable); clear_friendship(bt_mesh.lpn.disable);
break; break;
case BT_MESH_LPN_TIMER:
BT_DBG("Starting to look for Friend nodes");
lpn_set_state(BT_MESH_LPN_ENABLED);
if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
bt_mesh_scan_disable();
}
/* fall through */
case BT_MESH_LPN_ENABLED: case BT_MESH_LPN_ENABLED:
send_friend_req(); send_friend_req();
break; break;
case BT_MESH_LPN_REQ_WAIT:
bt_mesh_scan_enable();
k_delayed_work_submit(&lpn->timer, FRIEND_REQ_SCAN);
lpn_set_state(BT_MESH_LPN_WAIT_OFFER);
break;
case BT_MESH_LPN_WAIT_OFFER: case BT_MESH_LPN_WAIT_OFFER:
BT_WARN("No acceptable Friend Offers received"); BT_WARN("No acceptable Friend Offers received");
if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
bt_mesh_scan_disable();
}
lpn->counter++; lpn->counter++;
lpn_set_state(BT_MESH_LPN_ENABLED); lpn_set_state(BT_MESH_LPN_ENABLED);
k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT); k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT);
break; break;
case BT_MESH_LPN_ESTABLISHING:
if (lpn->req_attempts < 6) {
BT_WARN("Retrying first Friend Poll");
if (send_friend_poll() == 0) {
break;
}
}
BT_ERR("Timed out waiting for first Friend Update");
clear_friendship(false);
break;
case BT_MESH_LPN_ESTABLISHED: case BT_MESH_LPN_ESTABLISHED:
if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) { if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) {
u8_t req = lpn->sent_req; u8_t req = lpn->sent_req;
@ -674,10 +742,7 @@ static void lpn_timeout(struct k_work *work)
lpn->recv_win + SCAN_LATENCY); lpn->recv_win + SCAN_LATENCY);
break; break;
case BT_MESH_LPN_WAIT_UPDATE: case BT_MESH_LPN_WAIT_UPDATE:
BT_WARN("No response from Friend during ReceiveWindow"); update_timeout(lpn);
bt_mesh_scan_disable();
lpn_set_state(BT_MESH_LPN_ESTABLISHED);
k_delayed_work_submit(&lpn->timer, POLL_RETRY_TIMEOUT);
break; break;
default: default:
__ASSERT(0, "Unhandled LPN state"); __ASSERT(0, "Unhandled LPN state");
@ -826,7 +891,7 @@ int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx,
bt_mesh_beacon_ivu_initiator(false); bt_mesh_beacon_ivu_initiator(false);
} }
if (lpn->state == BT_MESH_LPN_ESTABLISHING) { if (!lpn->established) {
/* This is normally checked on the transport layer, however /* This is normally checked on the transport layer, however
* in this state we're also still accepting master * in this state we're also still accepting master
* credentials so we need to ensure the right ones (Friend * credentials so we need to ensure the right ones (Friend
@ -837,6 +902,8 @@ int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx,
return -EINVAL; return -EINVAL;
} }
lpn->established = 1;
BT_INFO("Friendship established with 0x%04x", lpn->frnd); BT_INFO("Friendship established with 0x%04x", lpn->frnd);
/* Set initial poll timeout */ /* Set initial poll timeout */
@ -888,10 +955,20 @@ int bt_mesh_lpn_init(void)
{ {
struct bt_mesh_lpn *lpn = &bt_mesh.lpn; struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
BT_DBG("");
k_delayed_work_init(&lpn->timer, lpn_timeout); k_delayed_work_init(&lpn->timer, lpn_timeout);
if (lpn->state == BT_MESH_LPN_ENABLED) { if (lpn->state == BT_MESH_LPN_ENABLED) {
if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
bt_mesh_scan_disable();
}
send_friend_req(); send_friend_req();
} else if (IS_ENABLED(CONFIG_BT_MESH_LPN_AUTO)) {
BT_DBG("Waiting %u ms for messages", LPN_AUTO_TIMEOUT);
lpn_set_state(BT_MESH_LPN_TIMER);
k_delayed_work_submit(&lpn->timer, LPN_AUTO_TIMEOUT);
} }
return 0; return 0;

View file

@ -18,7 +18,7 @@ int bt_mesh_lpn_friend_sub_cfm(struct bt_mesh_net_rx *rx,
static inline bool bt_mesh_lpn_established(void) static inline bool bt_mesh_lpn_established(void)
{ {
#if defined(CONFIG_BT_MESH_LOW_POWER) #if defined(CONFIG_BT_MESH_LOW_POWER)
return (bt_mesh.lpn.state >= BT_MESH_LPN_ESTABLISHED); return bt_mesh.lpn.established;
#else #else
return false; return false;
#endif #endif
@ -43,6 +43,15 @@ static inline bool bt_mesh_lpn_waiting_update(void)
#endif #endif
} }
static inline bool bt_mesh_lpn_timer(void)
{
#if defined(CONFIG_BT_MESH_LPN_AUTO)
return (bt_mesh.lpn.state == BT_MESH_LPN_TIMER);
#else
return false;
#endif
}
void bt_mesh_lpn_friend_poll(void); void bt_mesh_lpn_friend_poll(void);
void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx); void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx);

View file

@ -126,9 +126,10 @@ struct bt_mesh_lpn {
enum __packed { enum __packed {
BT_MESH_LPN_DISABLED, /* LPN feature is disabled */ BT_MESH_LPN_DISABLED, /* LPN feature is disabled */
BT_MESH_LPN_CLEAR, /* Clear in progress */ BT_MESH_LPN_CLEAR, /* Clear in progress */
BT_MESH_LPN_TIMER, /* Waiting for auto timer expiry */
BT_MESH_LPN_ENABLED, /* LPN enabled, but no Friend */ BT_MESH_LPN_ENABLED, /* LPN enabled, but no Friend */
BT_MESH_LPN_REQ_WAIT, /* Wait before scanning for offers */
BT_MESH_LPN_WAIT_OFFER, /* Friend Req sent */ BT_MESH_LPN_WAIT_OFFER, /* Friend Req sent */
BT_MESH_LPN_ESTABLISHING, /* First Friend Poll sent */
BT_MESH_LPN_ESTABLISHED, /* Friendship established */ BT_MESH_LPN_ESTABLISHED, /* Friendship established */
BT_MESH_LPN_RECV_DELAY, /* Poll sent, waiting ReceiveDelay */ BT_MESH_LPN_RECV_DELAY, /* Poll sent, waiting ReceiveDelay */
BT_MESH_LPN_WAIT_UPDATE, /* Waiting for Update or message */ BT_MESH_LPN_WAIT_UPDATE, /* Waiting for Update or message */
@ -154,7 +155,8 @@ struct bt_mesh_lpn {
u8_t groups_changed:1, /* Friend Subscription List needs updating */ u8_t groups_changed:1, /* Friend Subscription List needs updating */
pending_poll:1, /* Poll to be sent after subscription */ pending_poll:1, /* Poll to be sent after subscription */
disable:1, /* Disable LPN after clearing */ disable:1, /* Disable LPN after clearing */
fsn:1; /* Friend Sequence Number */ fsn:1, /* Friend Sequence Number */
established:1; /* Friendship established */
/* Friend Queue Size */ /* Friend Queue Size */
u8_t queue_size; u8_t queue_size;

View file

@ -1289,12 +1289,17 @@ int bt_mesh_trans_recv(struct net_buf_simple *buf, struct bt_mesh_net_rx *rx)
* we still need to go through the actual sending to the bearer and * we still need to go through the actual sending to the bearer and
* wait for ReceiveDelay before transitioning to WAIT_UPDATE state. * wait for ReceiveDelay before transitioning to WAIT_UPDATE state.
* *
* Another situation where we want to notify the LPN state machine
* is if it's configured to use an automatic Friendship establishment
* timer, in which case we want to reset the timer at this point.
*
* ENOENT is a special condition that's only used to indicate that * ENOENT is a special condition that's only used to indicate that
* the Transport OpCode was invalid, in which case we should ignore * the Transport OpCode was invalid, in which case we should ignore
* the PDU completely, as per MESH/NODE/FRND/LPN/BI-02-C. * the PDU completely, as per MESH/NODE/FRND/LPN/BI-02-C.
*/ */
if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && err != -ENOENT && if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && err != -ENOENT &&
bt_mesh_lpn_established() && bt_mesh_lpn_waiting_update()) { (bt_mesh_lpn_timer() ||
(bt_mesh_lpn_established() && bt_mesh_lpn_waiting_update()))) {
bt_mesh_lpn_msg_received(rx); bt_mesh_lpn_msg_received(rx);
} }