bluetooth: BAS: add battery level status char to bas service

Added the battery level status char to bas service
as per bas_1.1 spec. Added BSIM test for BAS service
to test the NTF/INDs of BAS characteristics.

Signed-off-by: Nithin Ramesh Myliattil <niym@demant.com>
This commit is contained in:
Nithin Ramesh Myliattil 2024-08-08 17:49:46 +02:00 committed by Anas Nashif
commit baa5683e59
19 changed files with 1643 additions and 110 deletions

View file

@ -1,4 +1,5 @@
/* /*
* Copyright (c) 2024 Demant A/S
* Copyright (c) 2018 Nordic Semiconductor ASA * Copyright (c) 2018 Nordic Semiconductor ASA
* Copyright (c) 2016 Intel Corporation * Copyright (c) 2016 Intel Corporation
* *
@ -19,11 +20,173 @@
*/ */
#include <stdint.h> #include <stdint.h>
#include <zephyr/sys/util.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/**
* @brief Battery Level Status Characteristic flags.
*
* Enumeration for the flags indicating the presence
* of various fields in the Battery Level Status characteristic.
*/
enum bt_bas_bls_flags {
/** Bit indicating that the Battery Level Status identifier is present. */
BT_BAS_BLS_FLAG_IDENTIFIER_PRESENT = BIT(0),
/** Bit indicating that the Battery Level is present. */
BT_BAS_BLS_FLAG_BATTERY_LEVEL_PRESENT = BIT(1),
/** Bit indicating that additional status information is present. */
BT_BAS_BLS_FLAG_ADDITIONAL_STATUS_PRESENT = BIT(2),
};
/** @brief Battery Present Status
*
* Enumeration for the presence of the battery.
*/
enum bt_bas_bls_battery_present {
/** Battery is not present. */
BT_BAS_BLS_BATTERY_NOT_PRESENT = 0,
/** Battery is present. */
BT_BAS_BLS_BATTERY_PRESENT = 1
};
/** @brief Wired External Power Source Status
*
* Enumeration for the status of the wired external power source.
*/
enum bt_bas_bls_wired_power_source {
/** Wired external power source is not connected. */
BT_BAS_BLS_WIRED_POWER_NOT_CONNECTED = 0,
/** Wired external power source is connected. */
BT_BAS_BLS_WIRED_POWER_CONNECTED = 1,
/** Wired external power source status is unknown. */
BT_BAS_BLS_WIRED_POWER_UNKNOWN = 2
};
/** @brief Wireless External Power Source Status
*
* Enumeration for the status of the wireless external power source.
*/
enum bt_bas_bls_wireless_power_source {
/** Wireless external power source is not connected. */
BT_BAS_BLS_WIRELESS_POWER_NOT_CONNECTED = 0,
/** Wireless external power source is connected. */
BT_BAS_BLS_WIRELESS_POWER_CONNECTED = 1,
/** Wireless external power source status is unknown. */
BT_BAS_BLS_WIRELESS_POWER_UNKNOWN = 2
};
/** @brief Battery Charge State
*
* Enumeration for the charge state of the battery.
*/
enum bt_bas_bls_battery_charge_state {
/** Battery charge state is unknown. */
BT_BAS_BLS_CHARGE_STATE_UNKNOWN = 0,
/** Battery is currently charging. */
BT_BAS_BLS_CHARGE_STATE_CHARGING = 1,
/** Battery is discharging actively. */
BT_BAS_BLS_CHARGE_STATE_DISCHARGING_ACTIVE = 2,
/** Battery is discharging but inactive. */
BT_BAS_BLS_CHARGE_STATE_DISCHARGING_INACTIVE = 3
};
/** @brief Battery Charge Level
*
* Enumeration for the level of charge in the battery.
*/
enum bt_bas_bls_battery_charge_level {
/** Battery charge level is unknown. */
BT_BAS_BLS_CHARGE_LEVEL_UNKNOWN = 0,
/** Battery charge level is good. */
BT_BAS_BLS_CHARGE_LEVEL_GOOD = 1,
/** Battery charge level is low. */
BT_BAS_BLS_CHARGE_LEVEL_LOW = 2,
/** Battery charge level is critical. */
BT_BAS_BLS_CHARGE_LEVEL_CRITICAL = 3
};
/** @brief Battery Charge Type
*
* Enumeration for the type of charging applied to the battery.
*/
enum bt_bas_bls_battery_charge_type {
/** Battery charge type is unknown or not charging. */
BT_BAS_BLS_CHARGE_TYPE_UNKNOWN = 0,
/** Battery is charged using constant current. */
BT_BAS_BLS_CHARGE_TYPE_CONSTANT_CURRENT = 1,
/** Battery is charged using constant voltage. */
BT_BAS_BLS_CHARGE_TYPE_CONSTANT_VOLTAGE = 2,
/** Battery is charged using trickle charge. */
BT_BAS_BLS_CHARGE_TYPE_TRICKLE = 3,
/** Battery is charged using float charge. */
BT_BAS_BLS_CHARGE_TYPE_FLOAT = 4
};
/** @brief Charging Fault Reason
*
* Enumeration for the reasons of charging faults.
*/
enum bt_bas_bls_charging_fault_reason {
/** No charging fault. */
BT_BAS_BLS_FAULT_REASON_NONE = 0,
/** Charging fault due to battery issue. */
BT_BAS_BLS_FAULT_REASON_BATTERY = BIT(0),
/** Charging fault due to external power source issue. */
BT_BAS_BLS_FAULT_REASON_EXTERNAL_POWER = BIT(1),
/** Charging fault for other reasons. */
BT_BAS_BLS_FAULT_REASON_OTHER = BIT(2)
};
/** @brief Service Required Status
*
* Enumeration for whether the service is required.
*/
enum bt_bas_bls_service_required {
/** Service is not required. */
BT_BAS_BLS_SERVICE_REQUIRED_FALSE = 0,
/** Service is required. */
BT_BAS_BLS_SERVICE_REQUIRED_TRUE = 1,
/** Service requirement is unknown. */
BT_BAS_BLS_SERVICE_REQUIRED_UNKNOWN = 2
};
/** @brief Battery Fault Status
*
* Enumeration for the fault status of the battery.
*/
enum bt_bas_bls_battery_fault {
/** No battery fault. */
BT_BAS_BLS_BATTERY_FAULT_NO = 0,
/** Battery fault present. */
BT_BAS_BLS_BATTERY_FAULT_YES = 1
};
/** @brief Read battery level value. /** @brief Read battery level value.
* *
* Read the characteristic value of the battery level * Read the characteristic value of the battery level
@ -43,6 +206,81 @@ uint8_t bt_bas_get_battery_level(void);
*/ */
int bt_bas_set_battery_level(uint8_t level); int bt_bas_set_battery_level(uint8_t level);
/**
* @brief Set the battery present status.
*
* @param present The battery present status to set.
*/
void bt_bas_bls_set_battery_present(enum bt_bas_bls_battery_present present);
/**
* @brief Set the wired external power source status.
*
* @param source The wired external power source status to set.
*/
void bt_bas_bls_set_wired_external_power_source(enum bt_bas_bls_wired_power_source source);
/**
* @brief Set the wireless external power source status.
*
* @param source The wireless external power source status to set.
*/
void bt_bas_bls_set_wireless_external_power_source(enum bt_bas_bls_wireless_power_source source);
/**
* @brief Set the battery charge state.
*
* @param state The battery charge state to set.
*/
void bt_bas_bls_set_battery_charge_state(enum bt_bas_bls_battery_charge_state state);
/**
* @brief Set the battery charge level.
*
* @param level The battery charge level to set.
*/
void bt_bas_bls_set_battery_charge_level(enum bt_bas_bls_battery_charge_level level);
/**
* @brief Set the battery charge type.
*
* @param type The battery charge type to set.
*/
void bt_bas_bls_set_battery_charge_type(enum bt_bas_bls_battery_charge_type type);
/**
* @brief Set the charging fault reason.
*
* @param reason The charging fault reason to set.
*/
void bt_bas_bls_set_charging_fault_reason(enum bt_bas_bls_charging_fault_reason reason);
/**
* @brief Set the identifier of the battery.
*
* kconfig_dep{CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT}
*
* @param identifier Identifier to set.
*/
void bt_bas_bls_set_identifier(uint16_t identifier);
/**
* @brief Set the service required status.
*
* kconfig_dep{CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT}
*
* @param value Service required status to set.
*/
void bt_bas_bls_set_service_required(enum bt_bas_bls_service_required value);
/**
* @brief Set the battery fault status.
*
* kconfig_dep{CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT}
*
* @param value Battery fault status to set.
*/
void bt_bas_bls_set_battery_fault(enum bt_bas_bls_battery_fault value);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -56,4 +56,9 @@ CONFIG_BT_TBS_CLIENT_GTBS=y
CONFIG_BT_TBS_CLIENT_CCID=y CONFIG_BT_TBS_CLIENT_CCID=y
CONFIG_BT_TBS_CLIENT_STATUS_FLAGS=y CONFIG_BT_TBS_CLIENT_STATUS_FLAGS=y
CONFIG_BT_BAS_BLS=y
CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT=y
CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT=y
CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT=y
CONFIG_LOG=y CONFIG_LOG=y

View file

@ -3,12 +3,14 @@
zephyr_sources_ifdef(CONFIG_BT_DIS dis.c) zephyr_sources_ifdef(CONFIG_BT_DIS dis.c)
zephyr_sources_ifdef(CONFIG_BT_BAS bas.c)
zephyr_sources_ifdef(CONFIG_BT_HRS hrs.c) zephyr_sources_ifdef(CONFIG_BT_HRS hrs.c)
zephyr_sources_ifdef(CONFIG_BT_TPS tps.c) zephyr_sources_ifdef(CONFIG_BT_TPS tps.c)
if(CONFIG_BT_BAS)
add_subdirectory(bas)
endif()
if(CONFIG_BT_OTS OR CONFIG_BT_OTS_CLIENT) if(CONFIG_BT_OTS OR CONFIG_BT_OTS_CLIENT)
add_subdirectory(ots) add_subdirectory(ots)
endif() endif()

View file

@ -8,8 +8,6 @@ menu "GATT Services"
rsource "Kconfig.dis" rsource "Kconfig.dis"
rsource "Kconfig.bas"
rsource "Kconfig.hrs" rsource "Kconfig.hrs"
rsource "Kconfig.tps" rsource "Kconfig.tps"
@ -20,4 +18,6 @@ rsource "ias/Kconfig.ias"
rsource "ots/Kconfig" rsource "ots/Kconfig"
rsource "bas/Kconfig.bas"
endmenu endmenu

View file

@ -1,7 +0,0 @@
# Bluetooth GATT Battery service
# Copyright (c) 2018 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
config BT_BAS
bool "GATT Battery service"

View file

@ -1,98 +0,0 @@
/** @file
* @brief GATT Battery Service
*/
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/init.h>
#include <zephyr/sys/__assert.h>
#include <stdbool.h>
#include <zephyr/types.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/services/bas.h>
#define LOG_LEVEL CONFIG_BT_BAS_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bas);
static uint8_t battery_level = 100U;
static void blvl_ccc_cfg_changed(const struct bt_gatt_attr *attr,
uint16_t value)
{
ARG_UNUSED(attr);
bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
LOG_INF("BAS Notifications %s", notif_enabled ? "enabled" : "disabled");
}
static ssize_t read_blvl(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint8_t lvl8 = battery_level;
return bt_gatt_attr_read(conn, attr, buf, len, offset, &lvl8,
sizeof(lvl8));
}
/* Constant values from the Assigned Numbers specification:
* https://www.bluetooth.com/wp-content/uploads/Files/Specification/Assigned_Numbers.pdf?id=89
*/
static const struct bt_gatt_cpf level_cpf = {
.format = 0x04, /* uint8 */
.exponent = 0x0,
.unit = 0x27AD, /* Percentage */
.name_space = 0x01, /* Bluetooth SIG */
.description = 0x0106, /* "main" */
};
BT_GATT_SERVICE_DEFINE(bas,
BT_GATT_PRIMARY_SERVICE(BT_UUID_BAS),
BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_LEVEL,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ, read_blvl, NULL,
&battery_level),
BT_GATT_CCC(blvl_ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
BT_GATT_CPF(&level_cpf),
);
static int bas_init(void)
{
return 0;
}
uint8_t bt_bas_get_battery_level(void)
{
return battery_level;
}
int bt_bas_set_battery_level(uint8_t level)
{
int rc;
if (level > 100U) {
return -EINVAL;
}
battery_level = level;
rc = bt_gatt_notify(NULL, &bas.attrs[1], &level, sizeof(level));
return rc == -ENOTCONN ? 0 : rc;
}
SYS_INIT(bas_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

View file

@ -0,0 +1,3 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_sources_ifdef(CONFIG_BT_BAS bas.c)
zephyr_sources_ifdef(CONFIG_BT_BAS_BLS bas_bls.c)

View file

@ -0,0 +1,30 @@
# Bluetooth GATT Battery service
# Copyright (c) 2018 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
config BT_BAS
bool "GATT Battery service"
config BT_BAS_BLS
bool "Battery Level Status"
help
Enable this option to include Battery Level Status Characteristic.
if BT_BAS_BLS
config BT_BAS_BLS_IDENTIFIER_PRESENT
bool "Battery Level Identifier Present"
help
Enable this option if the Battery Level Identifier is present.
config BT_BAS_BLS_BATTERY_LEVEL_PRESENT
bool "Battery Level Present"
help
Enable this option if the Battery Level is present.
config BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT
bool "Additional Battery Status Present"
help
Enable this option if Additional Battery Status information is present.
endif

View file

@ -0,0 +1,134 @@
/** @file
* @brief GATT Battery Service
*/
/*
* Copyright (c) 2024 Demant A/S
* Copyright (c) 2018 Nordic Semiconductor ASA
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/init.h>
#include <zephyr/sys/__assert.h>
#include <stdbool.h>
#include <zephyr/types.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/services/bas.h>
#include "bas_internal.h"
#define LOG_LEVEL CONFIG_BT_BAS_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bas, CONFIG_BT_BAS_LOG_LEVEL);
static uint8_t battery_level = 100U;
static void blvl_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
ARG_UNUSED(attr);
bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
LOG_INF("BAS Notifications %s", notif_enabled ? "enabled" : "disabled");
}
#if defined(CONFIG_BT_BAS_BLS)
static void blvl_status_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
ARG_UNUSED(attr);
bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
bool ind_enabled = (value == BT_GATT_CCC_INDICATE);
LOG_INF("BAS Notifications %s", notif_enabled ? "enabled" : "disabled");
LOG_INF("BAS Indications %s", ind_enabled ? "enabled" : "disabled");
}
#endif
static ssize_t read_blvl(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint8_t lvl8 = battery_level;
return bt_gatt_attr_read(conn, attr, buf, len, offset, &lvl8, sizeof(lvl8));
}
/* Constant values from the Assigned Numbers specification:
* https://www.bluetooth.com/wp-content/uploads/Files/Specification/Assigned_Numbers.pdf?id=89
*/
static const struct bt_gatt_cpf level_cpf = {
.format = 0x04, /* uint8 */
.exponent = 0x0,
.unit = 0x27AD, /* Percentage */
.name_space = 0x01, /* Bluetooth SIG */
.description = 0x0106, /* "main" */
};
BT_GATT_SERVICE_DEFINE(
bas, BT_GATT_PRIMARY_SERVICE(BT_UUID_BAS),
BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_LEVEL, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ, read_blvl, NULL, NULL),
BT_GATT_CCC(blvl_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
BT_GATT_CPF(&level_cpf),
#if defined(CONFIG_BT_BAS_BLS)
BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_LEVEL_STATUS,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE,
BT_GATT_PERM_READ, bt_bas_bls_read_blvl_status, NULL, NULL),
BT_GATT_CCC(blvl_status_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
#endif
);
static int bas_init(void)
{
if (IS_ENABLED(CONFIG_BT_BAS_BLS)) {
/* Initialize the Battery Level Status Module */
bt_bas_bls_init();
if (IS_ENABLED(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT)) {
/* Set the identifier only if BT_BAS_BLS_IDENTIFIER_PRESENT is defined */
bt_bas_bls_set_identifier(level_cpf.description);
}
}
return 0;
}
uint8_t bt_bas_get_battery_level(void)
{
return battery_level;
}
int bt_bas_set_battery_level(uint8_t level)
{
int rc;
if (level > 100U) {
return -EINVAL;
}
battery_level = level;
rc = bt_gatt_notify(NULL, &bas.attrs[1], &level, sizeof(level));
if (IS_ENABLED(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT)) {
bt_bas_bls_set_battery_level(level);
}
return rc == -ENOTCONN ? 0 : rc;
}
const struct bt_gatt_attr *bt_bas_get_bas_attr(uint16_t index)
{
if (index < bas.attr_count) {
return &bas.attrs[index];
}
return NULL;
}
SYS_INIT(bas_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

View file

@ -0,0 +1,236 @@
/*
* Copyright (c) 2024 Demant A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/services/bas.h>
#include <zephyr/bluetooth/gatt.h>
#include "bas_internal.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(bas, CONFIG_BT_BAS_LOG_LEVEL);
/* The battery level status of a battery. */
static struct bt_bas_bls bls;
#define BT_BAS_IDX_BATT_LVL_STATUS_CHAR_VAL 6
/* Notify/Indicate all connections */
static struct bt_gatt_indicate_params ind_params;
/*
* Bitfield structure: Power State
*
* - Bits 0: Battery Present
* - Bits 12: Wired External Power Source Connected
* - Bits 34: Wireless External Power Source Connected
* - Bits 56: Battery Charge State
* - Bits 78: Battery Charge Level
* - Bits 911: Charging Type
* - Bits 1214: Charging Fault Reason
* - Bit 15: RFU
*
* For detailed specification, refer to:
* https://bitbucket.org/bluetooth-SIG/public/src/main/gss/
* org.bluetooth.characteristic.battery_level_status.yaml
*/
#define BATTERY_SHIFT 0
#define WIRED_POWER_SHIFT 1
#define WIRELESS_POWER_SHIFT 3
#define BATTERY_CHARGE_STATE_SHIFT 5
#define BATTERY_CHARGE_LEVEL_SHIFT 7
#define BATTERY_CHARGE_TYPE_SHIFT 9
#define CHARGING_FAULT_SHIFT 12
#define BATTERY_MASK (BIT_MASK(1) << BATTERY_SHIFT)
#define WIRED_POWER_MASK (BIT_MASK(2) << WIRED_POWER_SHIFT)
#define WIRELESS_POWER_MASK (BIT_MASK(2) << WIRELESS_POWER_SHIFT)
#define BATTERY_CHARGE_STATE_MASK (BIT_MASK(2) << BATTERY_CHARGE_STATE_SHIFT)
#define BATTERY_CHARGE_LEVEL_MASK (BIT_MASK(2) << BATTERY_CHARGE_LEVEL_SHIFT)
#define BATTERY_CHARGE_TYPE_MASK (BIT_MASK(3) << BATTERY_CHARGE_TYPE_SHIFT)
#define CHARGING_FAULT_MASK (BIT_MASK(3) << CHARGING_FAULT_SHIFT)
/*
* Bitfield structure: Additional Status
*
* - Bits 01: Service Required
* - Bit 2: Battery Fault
* - Bits 37: Reserved
*/
#define SERVICE_REQUIRED_SHIFT 0
#define BATTERY_FAULT_SHIFT 2
#define SERVICE_REQUIRED_MASK (BIT_MASK(2) << SERVICE_REQUIRED_SHIFT)
#define BATTERY_FAULT_MASK (BIT_MASK(1) << BATTERY_FAULT_SHIFT)
void bt_bas_bls_init(void)
{
LOG_DBG("Initialise BAS Battery Level Status Module");
bls.flags = 0;
bls.power_state = 0;
#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT)
/* Set identifier flag */
bls.flags |= BT_BAS_BLS_FLAG_IDENTIFIER_PRESENT;
bls.identifier = 0;
#endif /* CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT */
#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT)
/* Set battery level flag */
bls.flags |= BT_BAS_BLS_FLAG_BATTERY_LEVEL_PRESENT;
bls.battery_level = bt_bas_get_battery_level();
#endif /* CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT */
#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT)
/* Set additional status flag */
bls.flags |= BT_BAS_BLS_FLAG_ADDITIONAL_STATUS_PRESENT;
bls.additional_status = 0;
#endif /* CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT */
}
static void indicate_cb(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err)
{
if (err != 0) {
LOG_DBG("Indication failed with error %d\n", err);
} else {
LOG_DBG("Indication sent successfully\n");
}
}
static void bt_bas_bls_update_battery_level_status(void)
{
int err;
const struct bt_gatt_attr *attr = bt_bas_get_bas_attr(BT_BAS_IDX_BATT_LVL_STATUS_CHAR_VAL);
if (attr) {
const struct bt_bas_bls le_battery_level_status = {
.flags = bls.flags,
.power_state = sys_cpu_to_le16(bls.power_state),
#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT)
.identifier = sys_cpu_to_le16(bls.identifier),
#endif
#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT)
.battery_level = bls.battery_level,
#endif
#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT)
.additional_status = bls.additional_status,
#endif
};
/* Notify/Indicate all connections */
ind_params.attr = attr;
ind_params.data = &le_battery_level_status;
ind_params.len = sizeof(le_battery_level_status);
ind_params.func = indicate_cb;
err = bt_gatt_indicate(NULL, &ind_params);
if (err) {
LOG_DBG("Failed to send ntf/ind to all connections (err %d)\n", err);
}
}
}
ssize_t bt_bas_bls_read_blvl_status(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const struct bt_bas_bls le_battery_level_status = {
.flags = bls.flags,
.power_state = sys_cpu_to_le16(bls.power_state),
#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT)
.identifier = sys_cpu_to_le16(bls.identifier),
#endif
#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT)
.battery_level = bls.battery_level,
#endif
#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT)
.additional_status = bls.additional_status,
#endif
};
return bt_gatt_attr_read(conn, attr, buf, len, offset, &le_battery_level_status,
sizeof(le_battery_level_status));
}
void bt_bas_bls_set_battery_present(enum bt_bas_bls_battery_present present)
{
bls.power_state &= ~BATTERY_MASK;
bls.power_state |= (present << BATTERY_SHIFT) & BATTERY_MASK;
bt_bas_bls_update_battery_level_status();
}
void bt_bas_bls_set_wired_external_power_source(enum bt_bas_bls_wired_power_source source)
{
bls.power_state &= ~WIRED_POWER_MASK;
bls.power_state |= (source << WIRED_POWER_SHIFT) & WIRED_POWER_MASK;
bt_bas_bls_update_battery_level_status();
}
void bt_bas_bls_set_wireless_external_power_source(enum bt_bas_bls_wireless_power_source source)
{
bls.power_state &= ~WIRELESS_POWER_MASK;
bls.power_state |= (source << WIRELESS_POWER_SHIFT) & WIRELESS_POWER_MASK;
bt_bas_bls_update_battery_level_status();
}
void bt_bas_bls_set_battery_charge_state(enum bt_bas_bls_battery_charge_state state)
{
bls.power_state &= ~BATTERY_CHARGE_STATE_MASK;
bls.power_state |= (state << BATTERY_CHARGE_STATE_SHIFT) & BATTERY_CHARGE_STATE_MASK;
bt_bas_bls_update_battery_level_status();
}
void bt_bas_bls_set_battery_charge_level(enum bt_bas_bls_battery_charge_level level)
{
bls.power_state &= ~BATTERY_CHARGE_LEVEL_MASK;
bls.power_state |= (level << BATTERY_CHARGE_LEVEL_SHIFT) & BATTERY_CHARGE_LEVEL_MASK;
bt_bas_bls_update_battery_level_status();
}
void bt_bas_bls_set_battery_charge_type(enum bt_bas_bls_battery_charge_type type)
{
bls.power_state &= ~BATTERY_CHARGE_TYPE_MASK;
bls.power_state |= (type << BATTERY_CHARGE_TYPE_SHIFT) & BATTERY_CHARGE_TYPE_MASK;
bt_bas_bls_update_battery_level_status();
}
void bt_bas_bls_set_charging_fault_reason(enum bt_bas_bls_charging_fault_reason reason)
{
bls.power_state &= ~CHARGING_FAULT_MASK;
bls.power_state |= (reason << CHARGING_FAULT_SHIFT) & CHARGING_FAULT_MASK;
bt_bas_bls_update_battery_level_status();
}
#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT)
void bt_bas_bls_set_battery_level(uint8_t level)
{
bls.battery_level = level;
bt_bas_bls_update_battery_level_status();
}
#endif /* CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT */
#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT)
void bt_bas_bls_set_identifier(uint16_t identifier)
{
bls.identifier = sys_cpu_to_le16(identifier);
bt_bas_bls_update_battery_level_status();
}
#endif /* CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT */
#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT)
void bt_bas_bls_set_service_required(enum bt_bas_bls_service_required value)
{
bls.additional_status &= ~SERVICE_REQUIRED_MASK;
bls.additional_status |= (value << SERVICE_REQUIRED_SHIFT) & SERVICE_REQUIRED_MASK;
bt_bas_bls_update_battery_level_status();
}
void bt_bas_bls_set_battery_fault(enum bt_bas_bls_battery_fault value)
{
bls.additional_status &= ~BATTERY_FAULT_MASK;
bls.additional_status |= (value << BATTERY_FAULT_SHIFT) & BATTERY_FAULT_MASK;
bt_bas_bls_update_battery_level_status();
}
#endif /* CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT */

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2024 Demant A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef BT_BAS_INTERNAL_H_
#define BT_BAS_INTERNAL_H_
#include <sys/types.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/types.h>
/**
* @brief Battery level status structure definition.
*/
struct bt_bas_bls {
/** @brief Flags Field
*
* The values of this field are defined below.
*
* - bit 0: Identifier Present
* - Indicates whether the identifier field is present.
* - bit 1: Battery Level Present
* - Indicates whether the battery level field is present.
* - bit 2: Additional Status Present
* - Indicates whether the additional status field is present.
* - bit 37: RFU (Reserved for Future Use)
* - Reserved bits for future use; should be set to zero.
*/
uint8_t flags;
/** @brief Power State
*
* The values of this field are defined below.
*
* - bit 0: Battery Present
* - 0 = No
* - 1 = Yes
* - bit 12: Wired External Power Source Connected
* - 0 = No
* - 1 = Yes
* - 2 = Unknown
* - 3 = RFU
* - bit 34: Wireless External Power Source Connected
* - 0 = No
* - 1 = Yes
* - 2 = Unknown
* - 3 = RFU
* - bit 56: Battery Charge State
* - 0 = Unknown
* - 1 = Charging
* - 2 = Discharging: Active
* - 3 = Discharging: Inactive
* - bit 78: Battery Charge Level
* - 0 = Unknown
* - 1 = Good
* - 2 = Low
* - 3 = Critical
* - bit 911: Charging Type
* - 0 = Unknown or Not Charging
* - 1 = Constant Current
* - 2 = Constant Voltage
* - 3 = Trickle
* - 4 = Float
* - 57 = RFU
* - bit 1214: Charging Fault Reason
* - Bit 12: Battery
* - Bit 13: External Power source
* - Bit 14: Other
* - bit 15: RFU
*/
uint16_t power_state;
#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT)
/** Identifier for the battery, range 0x0000 to 0xFFFF.*/
uint16_t identifier;
#endif /* CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT */
#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT)
/** Current battery level */
uint8_t battery_level;
#endif /* CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT */
#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT)
/** @brief Additional Status
*
* The values of this field are defined below.
*
* - bit 01: Service Required
* - 0 = False
* - 1 = True
* - 2 = Unknown
* - 3 = RFU
* - bit 2: Battery Fault
* - 0 = False or Unknown
* - 1 = Yes
* - bit 37: RFU
*/
uint8_t additional_status;
#endif /* CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT */
} __packed;
/**
* @brief Initialize the Battery Level Status Module.
*
*/
void bt_bas_bls_init(void);
/**
* @brief Set the battery level characteristic value.
*
* @param battery_level The new battery level value in percent (0-100).
*/
void bt_bas_bls_set_battery_level(uint8_t battery_level);
/**
* @brief Read the Battery Level Status characteristic.
*
* @param conn Pointer to the Bluetooth connection object representing the client requesting
* the characteristic.
* @param attr Pointer to the GATT attribute representing the Battery Level Status characteristic.
* @param buf Buffer to store the read value.
* @param len Length of the buffer.
* @param offset Offset within the characteristic value to start reading.
*
* @return The number of bytes read and sent to the client, or a negative error code on failure.
*/
ssize_t bt_bas_bls_read_blvl_status(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset);
/**
* @brief Retrieve the Bluetooth GATT attribute for the BAS service by index.
*
* @param index The index of the attribute within the BAS service.
*
* @return Pointer to the Bluetooth GATT attribute if the index is valid,
* otherwise NULL if the index is out of bounds.
*/
const struct bt_gatt_attr *bt_bas_get_bas_attr(uint16_t index);
#endif /* BT_BAS_INTERNAL_H_ */

View file

@ -0,0 +1,29 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(battery_service_test)
target_sources(app PRIVATE
src/main.c
src/central_test.c
src/peripheral_test.c
)
# This contains a variety of helper functions that abstract away common tasks,
# like scanning, setting up a connection, querying the peer for a given
# characteristic, etc..
add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/common/testlib testlib)
target_link_libraries(app PRIVATE testlib)
# This contains babblesim-specific helpers, e.g. device synchronization.
add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit)
target_link_libraries(app PRIVATE babblekit)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)
zephyr_include_directories(
${BSIM_COMPONENTS_PATH}/libUtilv1/src/
${BSIM_COMPONENTS_PATH}/libPhyComv1/src/
)

View file

@ -0,0 +1,14 @@
CONFIG_BT=y
CONFIG_LOG=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_SMP=y
CONFIG_BT_BAS=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_DEVICE_NAME="bsim_bas"
CONFIG_BT_ATT_TX_COUNT=5
CONFIG_BT_BAS_BLS=y
CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT=y
CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT=y
CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT=y

View file

@ -0,0 +1,562 @@
/*
* Copyright (c) 2024 Demant A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include "bs_types.h"
#include "bs_tracing.h"
#include "time_machine.h"
#include "bstests.h"
#include <argparse.h>
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/bluetooth/services/bas.h>
#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/sys/byteorder.h>
#include "testlib/conn.h"
#include "testlib/scan.h"
#include "testlib/log_utils.h"
#include "babblekit/flags.h"
#include "babblekit/sync.h"
#include "babblekit/testcase.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_bsim_bas, CONFIG_BT_BAS_LOG_LEVEL);
static struct bt_conn *default_conn;
static bt_addr_le_t peer = {};
static struct bt_uuid_16 uuid = BT_UUID_INIT_16(0);
static struct bt_gatt_discover_params discover_params;
static struct bt_gatt_subscribe_params battery_level_notify_params;
static struct bt_gatt_subscribe_params battery_level_status_sub_params;
/*
* Battery Service test:
* We expect to find a connectable peripheral to which we will
* connect and discover Battery Service
*
* Test the Read/Notify/Indicate Characteristics of BAS
*/
#define WAIT_TIME 10 /*seconds*/
#define BAS_BLS_IND_RECEIVED_COUNT 20
#define BAS_BLS_NTF_RECEIVED_COUNT 20
static DEFINE_FLAG(notification_count_reached);
static DEFINE_FLAG(indication_count_reached);
extern enum bst_result_t bst_result;
static void test_bas_central_init(void)
{
bst_ticker_set_next_tick_absolute(WAIT_TIME * 1e6);
bst_result = In_progress;
}
static void test_bas_central_tick(bs_time_t HW_device_time)
{
/*
* If in WAIT_TIME seconds the testcase did not already pass
* (and finish) we consider it failed
*/
if (bst_result != Passed) {
TEST_FAIL("test_bas_central failed (not passed after %i seconds)\n", WAIT_TIME);
}
}
/* Callback for handling Battery Level Notifications */
static uint8_t battery_level_notify_cb(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params, const void *data,
uint16_t length)
{
if (data) {
LOG_INF("[NOTIFICATION] BAS Battery Level: %d%%", *(const uint8_t *)data);
} else {
LOG_INF("Battery Level Notifications disabled");
}
return BT_GATT_ITER_CONTINUE;
}
/* Callback for handling Battery Level Read Response */
static uint8_t battery_level_read_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params, const void *data,
uint16_t length)
{
TEST_ASSERT(err == 0, "Failed to read Battery Level (err %d)", err);
if (data) {
LOG_DBG("[READ] BAS Battery Level: %d%%\n", *(const uint8_t *)data);
}
return BT_GATT_ITER_STOP;
}
static bool parse_battery_level_status(const uint8_t *data, uint16_t length)
{
/* Check minimum length for parsing flags and power state */
if (length < 3) {
TEST_FAIL("Invalid data length: %d", length);
return false;
}
/* Parse flags (first byte) */
uint8_t flags = data[0];
LOG_INF("Parsed Flags: 0x%02x", flags);
if (flags & BT_BAS_BLS_FLAG_IDENTIFIER_PRESENT) {
LOG_INF(" Identifier Present");
} else {
LOG_INF(" Identifier Not Present");
}
if (flags & BT_BAS_BLS_FLAG_BATTERY_LEVEL_PRESENT) {
LOG_INF(" Battery Level Present");
} else {
LOG_INF(" Battery Level Not Present");
}
if (flags & BT_BAS_BLS_FLAG_ADDITIONAL_STATUS_PRESENT) {
LOG_INF(" Additional Status Present");
} else {
LOG_INF(" Additional Status Not Present");
}
/* Parse power state (next 2 bytes) */
uint16_t power_state = sys_get_le16(&data[1]);
LOG_INF("Parsed Power State: 0x%04x", power_state);
/* Print out each power state value */
LOG_INF(" Battery Present: %s", (power_state & BIT(0)) ? "Yes" : "No");
uint8_t wired_power = (power_state >> 1) & 0x03;
switch (wired_power) {
case 0:
LOG_INF(" Wired Power Source: No");
break;
case 1:
LOG_INF(" Wired Power Source: Yes");
break;
case 2:
LOG_INF(" Wired Power Source: Unknown");
break;
default:
LOG_INF(" Wired Power Source: RFU");
break;
}
uint8_t wireless_power = (power_state >> 3) & 0x03;
switch (wireless_power) {
case 0:
LOG_INF(" Wireless Power Source: No");
break;
case 1:
LOG_INF(" Wireless Power Source: Yes");
break;
case 2:
LOG_INF(" Wireless Power Source: Unknown");
break;
default:
LOG_INF(" Wireless Power Source: RFU");
break;
}
uint8_t charge_state = (power_state >> 5) & 0x03;
switch (charge_state) {
case 0:
LOG_INF(" Battery Charge State: Unknown");
break;
case 1:
LOG_INF(" Battery Charge State: Charging");
break;
case 2:
LOG_INF(" Battery Charge State: Discharging (Active)");
break;
case 3:
LOG_INF(" Battery Charge State: Discharging (Inactive)");
break;
}
uint8_t charge_level = (power_state >> 7) & 0x03;
switch (charge_level) {
case 0:
LOG_INF(" Battery Charge Level: Unknown");
break;
case 1:
LOG_INF(" Battery Charge Level: Good");
break;
case 2:
LOG_INF(" Battery Charge Level: Low");
break;
case 3:
LOG_INF(" Battery Charge Level: Critical");
break;
}
uint8_t charging_type = (power_state >> 9) & 0x07;
switch (charging_type) {
case 0:
LOG_INF(" Charging Type: Unknown or Not Charging");
break;
case 1:
LOG_INF(" Charging Type: Constant Current");
break;
case 2:
LOG_INF(" Charging Type: Constant Voltage");
break;
case 3:
LOG_INF(" Charging Type: Trickle");
break;
case 4:
LOG_INF(" Charging Type: Float");
break;
default:
LOG_INF(" Charging Type: RFU");
break;
}
uint8_t charging_fault = (power_state >> 12) & 0x07;
if (charging_fault) {
LOG_INF(" Charging Fault Reason: %s%s%s",
(charging_fault & BIT(0)) ? "Battery " : "",
(charging_fault & BIT(1)) ? "External Power Source " : "",
(charging_fault & BIT(2)) ? "Other " : "");
} else {
LOG_INF(" Charging Fault Reason: None");
}
/* Optional: Check if identifier is present */
if (IS_ENABLED(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT)) {
/* Check if length is sufficient for identifier */
if (length < 5) {
TEST_FAIL("Invalid data length for identifier");
return false;
}
/* Parse identifier (next 2 bytes) */
uint16_t identifier = sys_get_le16(&data[3]);
LOG_INF("Parsed Identifier: 0x%04x", identifier);
}
/* Optional: Check if battery level is present */
if (IS_ENABLED(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT)) {
/* Check if length is sufficient for battery level */
if (length < 6) {
TEST_FAIL("Invalid data length for battery level");
return false;
}
/* Parse battery level (next byte) */
uint8_t battery_level = data[5];
LOG_INF("Parsed Battery Level: %d%%", battery_level);
}
/* Optional: Check if additional status is present */
if (IS_ENABLED(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT)) {
/* Check if length is sufficient for additional status */
if (length < 7) {
TEST_FAIL("Invalid data length for additional status");
return false;
}
/* Parse additional status (next byte) */
uint8_t additional_status = data[6];
LOG_INF("Parsed Additional Status: 0x%02x", additional_status);
/* Print out additional status values */
uint8_t service_required = additional_status & 0x03;
switch (service_required) {
case 0:
LOG_INF(" Service Required: False");
break;
case 1:
LOG_INF(" Service Required: True");
break;
case 2:
LOG_INF(" Service Required: Unknown");
break;
default:
LOG_INF(" Service Required: RFU");
break;
}
bool battery_fault = (additional_status & BIT(2)) ? true : false;
LOG_INF(" Battery Fault: %s", battery_fault ? "Yes" : "No");
}
return true;
}
static unsigned char battery_level_status_indicate_cb(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
if (!data) {
LOG_INF("bas level status indication disabled\n");
} else {
static int ind_received;
printk("[INDICATION] BAS Battery Level Status: ");
for (int i = 0; i < length; i++) {
printk("%02x ", ((uint8_t *)data)[i]);
}
printk("\n");
if (parse_battery_level_status(data, length)) {
LOG_INF("Notification parsed successfully");
} else {
LOG_ERR("Notification parsing failed");
}
if (ind_received++ > BAS_BLS_IND_RECEIVED_COUNT) {
SET_FLAG(indication_count_reached);
}
}
return BT_GATT_ITER_CONTINUE;
}
static uint8_t battery_level_status_notify_cb(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
if (!data) {
LOG_INF("bas level status notification disabled\n");
} else {
static int notify_count;
printk("[NOTIFICATION] BAS Battery Level Status: ");
for (int i = 0; i < length; i++) {
printk("%02x ", ((uint8_t *)data)[i]);
}
printk("\n");
if (parse_battery_level_status(data, length)) {
LOG_INF("Notification parsed successfully");
} else {
LOG_ERR("Notification parsing failed");
}
if (notify_count++ > BAS_BLS_NTF_RECEIVED_COUNT) {
SET_FLAG(notification_count_reached);
}
}
return BT_GATT_ITER_CONTINUE;
}
static void read_battery_level(const struct bt_gatt_attr *attr)
{
/* Read the battery level after subscribing */
static struct bt_gatt_read_params read_params;
read_params.func = battery_level_read_cb;
read_params.handle_count = 1;
read_params.single.handle = bt_gatt_attr_get_handle(attr);
read_params.single.offset = 0;
bt_gatt_read(default_conn, &read_params);
}
static void subscribe_battery_level(const struct bt_gatt_attr *attr)
{
int err;
battery_level_notify_params = (struct bt_gatt_subscribe_params){
/* In Zephyr, it is common practice for the CCC handle
* to be positioned two handles after the characteristic handle.
*/
.ccc_handle = bt_gatt_attr_get_handle(attr) + 2,
.value_handle = bt_gatt_attr_value_handle(attr),
.value = BT_GATT_CCC_NOTIFY,
.notify = battery_level_notify_cb,
};
err = bt_gatt_subscribe(default_conn, &battery_level_notify_params);
if (err && err != -EALREADY) {
TEST_FAIL("Subscribe failed (err %d)\n", err);
} else {
LOG_DBG("Battery level [SUBSCRIBED]\n");
}
read_battery_level(attr);
}
static void subscribe_battery_level_status(const struct bt_gatt_attr *attr)
{
int err;
if (get_device_nbr() == 1) { /* One device for Indication */
battery_level_status_sub_params = (struct bt_gatt_subscribe_params){
/* In Zephyr, it is common practice for the CCC handle
* to be positioned two handles after the characteristic handle.
*/
.ccc_handle = bt_gatt_attr_get_handle(attr) + 2,
.value_handle = bt_gatt_attr_value_handle(attr),
.value = BT_GATT_CCC_INDICATE,
.notify = battery_level_status_indicate_cb,
};
} else { /* Other device for Notification */
battery_level_status_sub_params = (struct bt_gatt_subscribe_params){
.ccc_handle = bt_gatt_attr_get_handle(attr) + 2,
.value_handle = bt_gatt_attr_value_handle(attr),
.value = BT_GATT_CCC_NOTIFY,
.notify = battery_level_status_notify_cb,
};
}
err = bt_gatt_subscribe(default_conn, &battery_level_status_sub_params);
if (err && err != -EALREADY) {
TEST_FAIL("Subscribe failed (err %d)\n", err);
} else {
LOG_DBG("Battery level status [SUBSCRIBED]\n");
}
}
static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
if (!attr) {
LOG_DBG("Discover complete\n");
memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
LOG_DBG("[ATTRIBUTE] handle %u\n", attr->handle);
if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_BAS)) {
LOG_DBG("Battery Service\n");
memcpy(&uuid, BT_UUID_BAS_BATTERY_LEVEL, sizeof(uuid));
discover_params.uuid = &uuid.uuid;
discover_params.start_handle = attr->handle + 1;
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(conn, &discover_params);
if (err) {
TEST_FAIL("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_BAS_BATTERY_LEVEL)) {
LOG_DBG("Subscribe Battery Level Char\n");
subscribe_battery_level(attr);
memcpy(&uuid, BT_UUID_BAS_BATTERY_LEVEL_STATUS, sizeof(uuid));
discover_params.uuid = &uuid.uuid;
discover_params.start_handle = attr->handle + 1;
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(conn, &discover_params);
if (err) {
TEST_FAIL("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_BAS_BATTERY_LEVEL_STATUS)) {
LOG_DBG("Subscribe Batterry Level Status Char\n");
subscribe_battery_level_status(attr);
}
return BT_GATT_ITER_STOP;
}
static void discover_bas_service(struct bt_conn *conn)
{
int err;
LOG_DBG("%s\n", __func__);
memcpy(&uuid, BT_UUID_BAS, sizeof(uuid));
discover_params.uuid = &uuid.uuid;
discover_params.func = discover_func;
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
err = bt_gatt_discover(conn, &discover_params);
if (err) {
TEST_FAIL("Discover failed(err %d)\n", err);
return;
}
}
static void test_bas_central_main(void)
{
int err;
/* Mark test as in progress. */
TEST_START("central");
/* bk_sync_init only works between two devices in a simulation, with IDs 0 and 1. */
if (get_device_nbr() == 1) {
/* Initialize device sync library */
bk_sync_init();
}
err = bt_enable(NULL);
TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
LOG_DBG("Bluetooth initialized\n");
err = bt_testlib_scan_find_name(&peer, CONFIG_BT_DEVICE_NAME);
TEST_ASSERT(!err, "Failed to start scan (err %d)", err);
/* Create a connection using that address */
err = bt_testlib_connect(&peer, &default_conn);
TEST_ASSERT(!err, "Failed to initiate connection (err %d)", err);
LOG_DBG("Connected");
discover_bas_service(default_conn);
if (get_device_nbr() == 1) {
WAIT_FOR_FLAG(indication_count_reached);
LOG_INF("Indication Count Reached!");
} else {
WAIT_FOR_FLAG(notification_count_reached);
LOG_INF("Notification Count Reached!");
}
/* bk_sync_send only works between two devices in a simulation, with IDs 0 and 1. */
if (get_device_nbr() == 1) {
bk_sync_send();
}
bst_result = Passed;
TEST_PASS("Central Test Passed");
}
static const struct bst_test_instance test_bas_central[] = {
{
.test_id = "central",
.test_descr =
"Battery Service test. It expects that a peripheral device can be found. "
"The test will pass if it can receive notifications and indications more "
"than the threshold set within 15 sec. ",
.test_pre_init_f = test_bas_central_init,
.test_tick_f = test_bas_central_tick,
.test_main_f = test_bas_central_main,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_bas_central_install(struct bst_test_list *tests)
{
tests = bst_add_tests(tests, test_bas_central);
return tests;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2024 Demant A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "bstests.h"
extern struct bst_test_list *test_bas_central_install(struct bst_test_list *tests);
extern struct bst_test_list *test_bas_peripheral_install(struct bst_test_list *tests);
bst_test_install_t test_installers[] = {
test_bas_central_install,
test_bas_peripheral_install,
NULL,
};
int main(void)
{
bst_main();
return 0;
}

View file

@ -0,0 +1,183 @@
/*
* Copyright (c) 2024 Demant A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include "bs_types.h"
#include "bs_tracing.h"
#include "time_machine.h"
#include "bstests.h"
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#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/bluetooth/services/bas.h>
#include <zephyr/sys/byteorder.h>
#include "testlib/conn.h"
#include "testlib/scan.h"
#include "testlib/log_utils.h"
#include "babblekit/flags.h"
#include "babblekit/sync.h"
#include "babblekit/testcase.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(bt_bsim_bas, CONFIG_BT_BAS_LOG_LEVEL);
static struct bt_conn *default_conn;
static struct k_work_delayable update_bas_char_work;
/*
* Battery Service test:
* We expect a central to connect to us.
*/
#define WAIT_TIME 10 /*seconds*/
extern enum bst_result_t bst_result;
static void test_bas_peripheral_init(void)
{
bst_ticker_set_next_tick_absolute(WAIT_TIME * 1e6);
bst_result = In_progress;
}
static void test_bas_peripheral_tick(bs_time_t HW_device_time)
{
/*
* If in WAIT_TIME seconds the testcase did not already pass
* (and finish) we consider it failed
*/
if (bst_result != Passed) {
TEST_FAIL("test_bas_peripheral failed (not passed after %i seconds)\n", WAIT_TIME);
}
}
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_BAS_VAL)),
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
TEST_FAIL("Connection failed (err 0x%02x)\n", err);
} else {
default_conn = bt_conn_ref(conn);
LOG_DBG("Peripheral Connected\n");
}
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
LOG_DBG("Peripheral %s (reason 0x%02x)\n", __func__, reason);
if (default_conn) {
bt_conn_unref(default_conn);
default_conn = NULL;
}
}
static struct bt_conn_cb conn_callbacks = {
.connected = connected,
.disconnected = disconnected,
};
static void bt_ready(void)
{
int err;
LOG_DBG("Peripheral Bluetooth initialized\n");
err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
TEST_FAIL("Advertising failed to start (err %d)\n", err);
return;
}
LOG_DBG("Advertising successfully started");
}
static void update_bas_char(void)
{
LOG_DBG("[PERIPHERAL] setting battery level");
bt_bas_set_battery_level(90);
LOG_DBG("[PERIPHERAL] setting battery present");
bt_bas_bls_set_battery_present(BT_BAS_BLS_BATTERY_PRESENT);
LOG_DBG("[PERIPHERAL] setting battery charge level");
bt_bas_bls_set_battery_charge_level(BT_BAS_BLS_CHARGE_LEVEL_CRITICAL);
LOG_DBG("[PERIPHERAL] setting battery service required true");
bt_bas_bls_set_service_required(BT_BAS_BLS_SERVICE_REQUIRED_TRUE);
LOG_DBG("[PERIPHERAL] setting battery service charge type ");
bt_bas_bls_set_battery_charge_type(BT_BAS_BLS_CHARGE_TYPE_FLOAT);
}
/* Work handler function */
void update_bas_char_work_handler(struct k_work *work)
{
update_bas_char();
k_work_reschedule(&update_bas_char_work, K_SECONDS(1));
}
static void test_bas_peripheral_main(void)
{
int err;
bt_conn_cb_register(&conn_callbacks);
/* Mark test as in progress. */
TEST_START("peripheral");
/* Initialize device sync library */
bk_sync_init();
/* Initialize Bluetooth */
err = bt_enable(NULL);
TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
LOG_DBG("Bluetooth initialized");
bt_ready();
/* Initialize the update bas char work handler */
k_work_init_delayable(&update_bas_char_work, update_bas_char_work_handler);
/* Schedule the update bas char work for delayed execution */
k_work_schedule(&update_bas_char_work, K_SECONDS(1));
/* Main thread waits for the sync signal from other device */
bk_sync_wait();
bst_result = Passed;
TEST_PASS_AND_EXIT("Peripheral Test Passed");
}
static const struct bst_test_instance test_bas_peripheral[] = {
{
.test_id = "peripheral",
.test_descr = "Battery Service test. It expects that a central device can be found "
"The test will pass if ind/ntf can be sent without crash. ",
.test_pre_init_f = test_bas_peripheral_init,
.test_tick_f = test_bas_peripheral_tick,
.test_main_f = test_bas_peripheral_main,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_bas_peripheral_install(struct bst_test_list *tests)
{
tests = bst_add_tests(tests, test_bas_peripheral);
return tests;
}

View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Copyright 2024 Demant A/S
# SPDX-License-Identifier: Apache-2.0
source ${ZEPHYR_BASE}/tests/bsim/sh_common.source
# Battery service test: a central connects to a peripheral and expects a
# indication/notification of BAS chars from peripheral
simulation_id="battery_service_test"
verbosity_level=2
cd ${BSIM_OUT_PATH}/bin
Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_samples_battery_service_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=0 \
-testid=peripheral -rs=23
Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_samples_battery_service_prj_conf\
-v=${verbosity_level} -s=${simulation_id} -d=1 \
-testid=central -rs=6
Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_samples_battery_service_prj_conf\
-v=${verbosity_level} -s=${simulation_id} -d=2 \
-testid=central -rs=6
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \
-D=3 -sim_length=10e6 $@
wait_for_background_jobs

View file

@ -37,5 +37,10 @@ app=tests/bsim/bluetooth/samples/central_hr_peripheral_hr \
extra_conf_file=${ZEPHYR_BASE}/samples/bluetooth/central_hr/prj.conf \ extra_conf_file=${ZEPHYR_BASE}/samples/bluetooth/central_hr/prj.conf \
conf_overlay=${ZEPHYR_BASE}/samples/bluetooth/central_hr/overlay-phy_coded.conf \ conf_overlay=${ZEPHYR_BASE}/samples/bluetooth/central_hr/overlay-phy_coded.conf \
compile compile
if [ ${BOARD} == "nrf52_bsim" ]; then
app=tests/bsim/bluetooth/samples/battery_service \
conf_file=prj.conf \
compile
fi
wait_for_background_jobs wait_for_background_jobs

View file

@ -2,5 +2,5 @@
# This file is used in CI to select which tests are run # This file is used in CI to select which tests are run
tests/bsim/bluetooth/ll/conn/tests_scripts/basic_conn_encrypted_split_privacy.sh tests/bsim/bluetooth/ll/conn/tests_scripts/basic_conn_encrypted_split_privacy.sh
tests/bsim/bluetooth/ll/bis/tests_scripts/broadcast_iso.sh tests/bsim/bluetooth/ll/bis/tests_scripts/broadcast_iso.sh
tests/bsim/bluetooth/samples/ tests/bsim/bluetooth/samples/central_hr_peripheral_hr/
tests/bsim/bluetooth/audio_samples/ tests/bsim/bluetooth/audio_samples/