blob: ada785b9f2e8699cfe2709b77bb21589f579d93c [file]
/*
*
* Copyright (c) 2023 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BLEAdvertisingArbiter.h"
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/CHIPDeviceLayer.h>
#include <system/SystemError.h>
#include <zephyr/bluetooth/conn.h>
#if defined(CONFIG_ZEPHYR_VERSION_3_3)
#include <version.h>
#else
#include <zephyr/random/random.h>
#include <zephyr/version.h>
#endif
namespace chip {
namespace DeviceLayer {
namespace BLEAdvertisingArbiter {
namespace {
// List of advertising requests ordered by priority
sys_slist_t sRequests;
bool sIsInitialized = false;
uint8_t sBtId = 0;
#if KERNEL_VERSION_MAJOR >= 4
bool sWasDisconnection = false;
#endif // KERNEL_VERSION_MAJOR >= 4
// Cast an intrusive list node to the containing request object
const BLEAdvertisingArbiter::Request & ToRequest(const sys_snode_t * node)
{
return *static_cast<const BLEAdvertisingArbiter::Request *>(node);
}
// Notify application about stopped advertising if the callback has been provided
void NotifyAdvertisingStopped(const sys_snode_t * node)
{
VerifyOrReturn(node);
const Request & request = ToRequest(node);
if (request.onStopped != nullptr)
{
request.onStopped();
}
}
// Restart advertising using the top-priority request
CHIP_ERROR RestartAdvertising()
{
// Note: bt_le_adv_stop() returns success when the advertising was not started
ReturnErrorOnFailure(System::MapErrorZephyr(bt_le_adv_stop()));
VerifyOrReturnError(!sys_slist_is_empty(&sRequests), CHIP_NO_ERROR);
const Request & top = ToRequest(sys_slist_peek_head(&sRequests));
bt_le_adv_param params = BT_LE_ADV_PARAM_INIT(top.options, top.minInterval, top.maxInterval, nullptr);
params.id = sBtId;
const int result = bt_le_adv_start(&params, top.advertisingData.data(), top.advertisingData.size(), top.scanResponseData.data(),
top.scanResponseData.size());
if (result == -ENOMEM)
{
ChipLogProgress(DeviceLayer, "Advertising start failed, will retry once connection is released");
}
if (top.onStarted != nullptr)
{
top.onStarted(result);
}
return System::MapErrorZephyr(result);
}
#if KERNEL_VERSION_MAJOR >= 4
BT_CONN_CB_DEFINE(conn_callbacks) = {
.disconnected =
[](struct bt_conn * conn, uint8_t reason) {
(void) conn;
(void) reason;
sWasDisconnection = true;
},
.recycled =
[]() {
// In this callback the connection object was returned to the pool and we can try to re-start connectable
// advertising, but only if the disconnection was detected.
if (sWasDisconnection)
{
TEMPORARY_RETURN_IGNORED SystemLayer().ScheduleLambda([] {
if (!sys_slist_is_empty(&sRequests))
{
// Starting from Zephyr 4.0 Automatic advertiser resumption is deprecated,
// so the BLE Advertising Arbiter has to take over the responsibility of restarting the advertiser.
// Restart advertising in this callback if there are pending requests after the connection is released.
TEMPORARY_RETURN_IGNORED RestartAdvertising();
}
});
// Reset the disconnection flag to avoid restarting advertising multiple times
sWasDisconnection = false;
}
},
};
#endif // KERNEL_VERSION_MAJOR >= 4
} // namespace
CHIP_ERROR Init(uint8_t btId)
{
if (sIsInitialized)
{
return CHIP_ERROR_INCORRECT_STATE;
}
sBtId = btId;
sIsInitialized = true;
return CHIP_NO_ERROR;
}
CHIP_ERROR InsertRequest(Request & request)
{
if (!sIsInitialized)
{
return CHIP_ERROR_INCORRECT_STATE;
}
CancelRequest(request);
sys_snode_t * prev = nullptr;
sys_snode_t * node = nullptr;
// Find position of the request in the list that preserves ordering by priority
SYS_SLIST_FOR_EACH_NODE(&sRequests, node)
{
if (request.priority < ToRequest(node).priority)
{
break;
}
prev = node;
}
if (prev == nullptr)
{
NotifyAdvertisingStopped(sys_slist_peek_head(&sRequests));
sys_slist_prepend(&sRequests, &request);
}
else
{
sys_slist_insert(&sRequests, prev, &request);
}
// If the request is top-priority, restart the advertising
if (sys_slist_peek_head(&sRequests) == &request)
{
return RestartAdvertising();
}
return CHIP_NO_ERROR;
}
void CancelRequest(Request & request)
{
if (!sIsInitialized)
{
return;
}
const bool isTopPriority = (sys_slist_peek_head(&sRequests) == &request);
VerifyOrReturn(sys_slist_find_and_remove(&sRequests, &request));
// If cancelled request was top-priority, restart the advertising.
if (isTopPriority)
{
TEMPORARY_RETURN_IGNORED RestartAdvertising();
}
}
} // namespace BLEAdvertisingArbiter
} // namespace DeviceLayer
} // namespace chip