This commit is contained in:
2026-05-19 07:58:11 +02:00
parent 32fe244fcf
commit 5557c78179
20 changed files with 422 additions and 272 deletions

View File

@@ -3,85 +3,38 @@ menuconfig BLE_MGMT
default n
select BT
select BT_PERIPHERAL
select BT_LOG_LEVEL_WARN
select BT_DEVICE_NAME_DYNAMIC
help
Library for initializing and managing Bluetooth functionality.
Minimal BLE transport for the buzzer firmware.
if BLE_MGMT
config BLE_MGMT_TX_QUEUE_DEPTH
int "BLE TX queue depth"
default 32
help
Number of notification payloads that can be queued in the BLE transport.
config BLE_MGMT_DEFAULT_DEVICE_NAME
string "Default Bluetooth Device Name"
string "Default Bluetooth device name"
default "Edis Buzzer"
config BLE_MGMT_ADV_INT_MIN
int "Minimum Advertising Interval (in 0.625 ms units)"
default 160
help
Minimal advertising interval. 160 equals to 100ms.
config BLE_MGMT_ADV_INT_MAX
int "Maximum Advertising Interval (ms)"
default 160
help
Maximal advertising interval. 160 equals to 100ms.
# Airtime
config BT_CTLR_SDC_MAX_CONN_EVENT_LEN_DEFAULT
default 4000000
# MTU Setup
config BT_BUF_ACL_RX_SIZE
default 502
config BT_BUF_ACL_TX_SIZE
default 502
config BT_L2CAP_TX_MTU
default 498
config BT_CTLR_DATA_LENGTH_MAX
default 251
help
Device name used when ble_mgmt_init() is called with a NULL name.
# Buffers
config BT_BUF_ACL_TX_COUNT
default 15
config BT_L2CAP_TX_BUF_COUNT
default 15
config BT_CONN_TX_MAX
default 15
config BT_CTLR_SDC_TX_PACKET_COUNT
default 15
config BT_CTLR_SDC_RX_PACKET_COUNT
default 15
config BT_BUF_EVT_RX_COUNT
default 16
# Callbacks
config BT_USER_PHY_UPDATE
default y
config BT_USER_DATA_LEN_UPDATE
default y
# Automatic updates
config BT_AUTO_DATA_LEN_UPDATE
default y
config BT_GAP_AUTO_UPDATE_CONN_PARAMS
default y
# Preferred defaults
config BT_PERIPHERAL_PREF_MIN_INT
default 6
config BT_PERIPHERAL_PREF_MAX_INT
default 40
config BT_PERIPHERAL_PREF_LATENCY
default 0
config BT_PERIPHERAL_PREF_TIMEOUT
default 400
# Connections
config BT_MAX_CONN
default 2
# BLE control/data handling runs in BT RX workqueue. Enforce a larger
# stack while BLE_MGMT is enabled to avoid overflows on connect/file ops.
config BT_RX_STACK_SIZE
range 3072 8192
default 3072
# Use larger BLE data path buffers for file transfer use-cases.
config BT_L2CAP_TX_MTU
default 498
config BT_BUF_ACL_RX_SIZE
default 502
config BT_BUF_ACL_TX_SIZE
default 502
config BT_CTLR_DATA_LENGTH_MAX
default 251
module = BLE_MGMT
module-str = ble_mgmt

View File

@@ -17,7 +17,8 @@ int ble_mgmt_init(ble_mgmt_rx_cb_t rx_cb, const char *device_name);
* Sends data to the connected central device via a GATT characteristic.
* @param data Pointer to the data buffer to send.
* @param len Length of the data in bytes.
* @return 0 on success, -EACCES if notifications are not enabled, or a negative error code on failure.
* @return 0 on success, -EAGAIN if no ATT link is ready yet, -EACCES if notifications are not enabled,
* or a negative error code on failure.
*/
int ble_mgmt_send(const uint8_t *data, uint16_t len);

View File

@@ -1,14 +1,14 @@
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/conn.h>
#include <errno.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/logging/log.h>
#include "ble_mgmt.h"
LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL);
@@ -20,18 +20,24 @@ LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL);
#define BUZZ_TX_UUID_VAL \
BT_UUID_128_ENCODE(0xe517d988, 0xbab5, 0x4574, 0x8479, 0x97c6cb115ca2)
#define DEFAULT_ATT_MTU 23U
#define GATT_NOTIFY_OVERHEAD 3U
#define MAX_ADV_NAME_LEN 29
struct ble_peer {
struct bt_conn *conn;
};
static struct bt_uuid_128 buzz_service_uuid = BT_UUID_INIT_128(BUZZ_SERVICE_UUID_VAL);
static struct bt_uuid_128 buzz_rx_uuid = BT_UUID_INIT_128(BUZZ_RX_UUID_VAL);
static struct bt_uuid_128 buzz_tx_uuid = BT_UUID_INIT_128(BUZZ_TX_UUID_VAL);
static ble_mgmt_rx_cb_t app_rx_cb = NULL;
static bool notify_enabled = false;
static uint16_t current_tx_mtu = 23;
static uint16_t current_rx_mtu = 23;
#define MAX_ADV_NAME_LEN 29
static ble_mgmt_rx_cb_t app_rx_cb;
static bool ble_ready;
static bool advertising_active;
static uint16_t current_att_mtu = DEFAULT_ATT_MTU;
static char current_device_name[MAX_ADV_NAME_LEN + 1];
static struct ble_peer peers[CONFIG_BT_MAX_CONN];
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
@@ -44,219 +50,322 @@ static struct bt_data sd[] = {
static struct bt_le_adv_param adv_param = {
.id = BT_ID_DEFAULT,
.sid = 0,
.secondary_max_skip = 0,
.options = BT_LE_ADV_OPT_CONN,
.interval_min = CONFIG_BLE_MGMT_ADV_INT_MIN,
.interval_max = CONFIG_BLE_MGMT_ADV_INT_MAX,
.peer = NULL,
.interval_min = BT_GAP_ADV_FAST_INT_MIN_2,
.interval_max = BT_GAP_ADV_FAST_INT_MAX_2,
};
static void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx)
{
LOG_INF("MTU exchanged: TX %u bytes, RX %u bytes", tx, rx);
current_tx_mtu = tx;
current_rx_mtu = rx;
ARG_UNUSED(conn);
current_att_mtu = MIN(tx, rx);
LOG_INF("ATT MTU updated to %u", current_att_mtu);
}
static size_t peer_count(void)
{
size_t count = 0;
for (size_t index = 0; index < ARRAY_SIZE(peers); ++index) {
if (peers[index].conn != NULL) {
++count;
}
}
return count;
}
static struct ble_peer *find_peer(struct bt_conn *conn)
{
for (size_t index = 0; index < ARRAY_SIZE(peers); ++index) {
if (peers[index].conn == conn) {
return &peers[index];
}
}
return NULL;
}
static struct ble_peer *reserve_peer(struct bt_conn *conn)
{
struct ble_peer *peer = find_peer(conn);
if (peer != NULL) {
return peer;
}
for (size_t index = 0; index < ARRAY_SIZE(peers); ++index) {
if (peers[index].conn == NULL) {
peers[index].conn = bt_conn_ref(conn);
return &peers[index];
}
}
return NULL;
}
static void release_peer(struct bt_conn *conn)
{
struct ble_peer *peer = find_peer(conn);
if (peer == NULL) {
return;
}
bt_conn_unref(peer->conn);
peer->conn = NULL;
}
static void set_device_name(const char *name)
{
const char *name_to_use = name != NULL ? name : CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME;
strncpy(current_device_name, name_to_use, MAX_ADV_NAME_LEN);
current_device_name[MAX_ADV_NAME_LEN] = '\0';
sd[0].data_len = strlen(current_device_name);
#ifdef CONFIG_BT_DEVICE_NAME_DYNAMIC
if (ble_ready) {
(void)bt_set_name(current_device_name);
}
#endif
}
static int start_advertising(void)
{
int rc;
if (!ble_ready || peer_count() >= CONFIG_BT_MAX_CONN || advertising_active) {
return 0;
}
rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (rc == -EALREADY) {
advertising_active = true;
return 0;
}
if (rc != 0) {
LOG_ERR("Failed to start advertising (err %d)", rc);
return rc;
}
advertising_active = true;
LOG_INF("Advertising as %s", current_device_name);
return 0;
}
static int restart_advertising(void)
{
int rc;
if (!ble_ready) {
return -EAGAIN;
}
if (advertising_active) {
rc = bt_le_adv_stop();
if ((rc != 0) && (rc != -EALREADY)) {
LOG_ERR("Failed to stop advertising (err %d)", rc);
return rc;
}
advertising_active = false;
}
return start_advertising();
}
static ssize_t rx_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
{
LOG_DBG("Received %u bytes", len);
LOG_HEXDUMP_DBG(buf, len, "Data:");
ARG_UNUSED(conn);
ARG_UNUSED(attr);
ARG_UNUSED(flags);
if (app_rx_cb)
{
if (offset != 0U) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (app_rx_cb != NULL) {
app_rx_cb((const uint8_t *)buf, len);
}
return len;
}
static void tx_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
notify_enabled = (value == BT_GATT_CCC_NOTIFY);
LOG_INF("Notifications %s", notify_enabled ? "enabled" : "disabled");
ARG_UNUSED(attr);
LOG_INF("Notification state changed: 0x%04x", value);
}
BT_GATT_SERVICE_DEFINE(ble_mgmt_svc,
BT_GATT_PRIMARY_SERVICE(&buzz_service_uuid),
BT_GATT_CHARACTERISTIC(&buzz_rx_uuid.uuid, BT_GATT_CHRC_WRITE_WITHOUT_RESP,
BT_GATT_PERM_WRITE, NULL, rx_cb, NULL),
BT_GATT_CHARACTERISTIC(&buzz_tx_uuid.uuid, BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_NONE, NULL, NULL, NULL),
BT_GATT_CCC(tx_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE));
uint16_t ble_mgmt_get_max_payload(void)
{
/* Kappe die verhandelte MTU auf die hart konfigurierte Zephyr-Puffergrenze */
uint16_t effective_mtu = MIN(current_tx_mtu, current_rx_mtu);
#ifdef CONFIG_BT_L2CAP_TX_MTU
if (effective_mtu > CONFIG_BT_L2CAP_TX_MTU)
{
effective_mtu = CONFIG_BT_L2CAP_TX_MTU;
}
#endif
/* 3 Bytes abziehen für den GATT Notification Overhead */
return (effective_mtu > 3) ? (effective_mtu - 3) : 20;
}
int ble_mgmt_send(const uint8_t *data, uint16_t len)
{
if (!notify_enabled)
{
return -EACCES;
}
int rc;
do
{
rc = bt_gatt_notify(NULL, &ble_mgmt_svc.attrs[4], data, len);
if (rc == -ENOMEM)
{
k_sleep(K_MSEC(5)); // Thread pausieren, bis TX-Buffer frei wird
}
} while (rc == -ENOMEM);
if (rc)
{
LOG_ERR("Failed to send notification (err %d)", rc);
return rc;
}
return rc;
}
/* Interne Hilfsfunktion zur Zuweisung des Namens */
static void set_device_name(const char *name)
{
if (!name)
{
return;
}
strncpy(current_device_name, name, MAX_ADV_NAME_LEN);
current_device_name[MAX_ADV_NAME_LEN] = '\0';
/* Längen-Update im Scan-Response Array */
sd[0].data_len = strlen(current_device_name);
#ifdef CONFIG_BT_DEVICE_NAME_DYNAMIC
/* Setzt den Namen parallel im Zephyr GAP-Service (wichtig für macOS) */
bt_set_name(current_device_name);
#endif
}
int ble_mgmt_update_adv_name(const char *new_name)
{
int rc;
bt_le_adv_stop();
set_device_name(new_name);
rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (rc)
{
LOG_ERR("Advertising failed to restart after name update (err %d)", rc);
return rc;
}
LOG_INF("Advertising updated. New Name: %s", current_device_name);
return 0;
}
BT_GATT_PRIMARY_SERVICE(&buzz_service_uuid),
BT_GATT_CHARACTERISTIC(&buzz_rx_uuid.uuid, BT_GATT_CHRC_WRITE_WITHOUT_RESP,
BT_GATT_PERM_WRITE, NULL, rx_cb, NULL),
BT_GATT_CHARACTERISTIC(&buzz_tx_uuid.uuid, BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_NONE, NULL, NULL, NULL),
BT_GATT_CCC(tx_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE));
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err)
{
char addr_str[BT_ADDR_LE_STR_LEN];
struct bt_conn_info info;
int rc;
if (err != 0U) {
LOG_ERR("Connection failed (err 0x%02x)", err);
return;
}
char addr_str[BT_ADDR_LE_STR_LEN];
struct bt_conn_info info;
advertising_active = false;
int rc = bt_conn_get_info(conn, &info);
if (rc == 0)
{
bt_addr_le_to_str(info.le.dst, addr_str, sizeof(addr_str));
LOG_INF("Connected to %s", addr_str);
LOG_INF("Role: %s", info.role == BT_CONN_ROLE_CENTRAL ? "Central" : "Peripheral");
if (reserve_peer(conn) == NULL) {
LOG_ERR("No free BLE peer slot available");
return;
}
else
{
LOG_INF("Connected (info retrieval failed)");
rc = bt_conn_get_info(conn, &info);
if ((rc == 0) && (info.type == BT_CONN_TYPE_LE)) {
bt_addr_le_to_str(info.le.dst, addr_str, sizeof(addr_str));
LOG_INF("Connected to %s (%u/%u)", addr_str, (unsigned int)peer_count(),
CONFIG_BT_MAX_CONN);
} else {
LOG_INF("Connected (%u/%u)", (unsigned int)peer_count(), CONFIG_BT_MAX_CONN);
}
if (peer_count() < CONFIG_BT_MAX_CONN) {
(void)start_advertising();
}
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
LOG_INF("Disconnected (reason 0x%02x)", reason);
release_peer(conn);
advertising_active = false;
/* Startet Advertising mit dem global definierten Setup neu */
int rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (rc)
{
LOG_ERR("Advertising failed to restart (err %d)", rc);
LOG_INF("Disconnected (reason 0x%02x, %u/%u active)", reason,
(unsigned int)peer_count(), CONFIG_BT_MAX_CONN);
if (peer_count() < CONFIG_BT_MAX_CONN) {
(void)start_advertising();
}
else
{
LOG_INF("Advertising successfully restarted");
}
}
static void le_phy_updated(struct bt_conn *conn, struct bt_conn_le_phy_info *param)
{
const char *tx_phy_str = (param->tx_phy == BT_GAP_LE_PHY_2M) ? "2M" : (param->tx_phy == BT_GAP_LE_PHY_1M) ? "1M" : "Coded/Unknown";
const char *rx_phy_str = (param->rx_phy == BT_GAP_LE_PHY_2M) ? "2M" : (param->rx_phy == BT_GAP_LE_PHY_1M) ? "1M" : "Coded/Unknown";
LOG_INF("LE PHY updated: TX PHY %s, RX PHY %s", tx_phy_str, rx_phy_str);
}
static void le_param_updated(struct bt_conn *conn, uint16_t interval,
uint16_t latency, uint16_t timeout)
{
LOG_INF("Connection parameters updated: Interval: %u, Latency: %u, Timeout: %u",
interval, latency, timeout);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.le_param_updated = le_param_updated,
.le_phy_updated = le_phy_updated,
};
uint16_t ble_mgmt_get_max_payload(void)
{
uint16_t mtu = current_att_mtu;
#ifdef CONFIG_BT_L2CAP_TX_MTU
if (mtu > CONFIG_BT_L2CAP_TX_MTU) {
mtu = CONFIG_BT_L2CAP_TX_MTU;
}
#endif
return (mtu > GATT_NOTIFY_OVERHEAD) ? (mtu - GATT_NOTIFY_OVERHEAD) : 20U;
}
int ble_mgmt_send(const uint8_t *data, uint16_t len)
{
bool any_subscriber = false;
bool any_link_present = false;
int last_error = 0;
ARG_UNUSED(data);
ARG_UNUSED(len);
for (size_t index = 0; index < ARRAY_SIZE(peers); ++index) {
int rc;
if (peers[index].conn == NULL) {
continue;
}
any_link_present = true;
if (!bt_gatt_is_subscribed(peers[index].conn, &ble_mgmt_svc.attrs[4],
BT_GATT_CCC_NOTIFY)) {
continue;
}
any_subscriber = true;
do {
rc = bt_gatt_notify(peers[index].conn, &ble_mgmt_svc.attrs[4], data, len);
if (rc == -ENOMEM) {
k_sleep(K_MSEC(5));
}
} while (rc == -ENOMEM);
if (rc != 0) {
LOG_ERR("Failed to send notification (err %d)", rc);
last_error = rc;
}
}
if (!any_link_present || current_att_mtu <= DEFAULT_ATT_MTU) {
LOG_DBG("TX deferred: ATT not ready (links=%u, mtu=%u)",
(unsigned int)peer_count(), current_att_mtu);
return -EAGAIN;
}
if (!any_subscriber) {
LOG_DBG("TX blocked: no peer subscribed for notifications");
return -EACCES;
}
return last_error;
}
int ble_mgmt_update_adv_name(const char *new_name)
{
set_device_name(new_name);
if (peer_count() >= CONFIG_BT_MAX_CONN && !advertising_active) {
return 0;
}
return restart_advertising();
}
int ble_mgmt_init(ble_mgmt_rx_cb_t rx_cb, const char *device_name)
{
int rc;
app_rx_cb = rx_cb;
static struct bt_gatt_cb gatt_callbacks = {
.att_mtu_updated = att_mtu_updated,
};
bt_gatt_cb_register(&gatt_callbacks);
LOG_INF("ble_mgmt_init: starting");
app_rx_cb = rx_cb;
current_att_mtu = DEFAULT_ATT_MTU;
LOG_INF("ble_mgmt_init: calling bt_enable");
rc = bt_enable(NULL);
if (rc)
{
if (rc != 0) {
LOG_ERR("Bluetooth init failed (err %d)", rc);
return rc;
}
const char *name_to_use = (device_name != NULL) ? device_name : CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME;
LOG_INF("BLE init: set_device_name");
set_device_name(name_to_use);
LOG_INF("ble_mgmt_init: bt_enable done, marking ready");
ble_ready = true;
bt_gatt_cb_register(&gatt_callbacks);
advertising_active = false;
set_device_name(device_name);
LOG_INF("BLE init: bt_le_adv_start");
rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (rc)
{
LOG_ERR("Advertising failed to start (err %d)", rc);
LOG_INF("ble_mgmt_init: calling start_advertising");
rc = start_advertising();
if (rc != 0) {
LOG_ERR("start_advertising failed (err %d)", rc);
return rc;
}
LOG_INF("Bluetooth initialized. Adv-Name: %s", current_device_name);
LOG_INF("ble_mgmt_init: complete");
return 0;
}