blob: dd96477f2f6aebc37201170595ca33aa91eea6df [file] [log] [blame]
/*
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdint.h>
#include <zephyr/sys/check.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/services/ias.h>
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_IAS_CLIENT)
#define LOG_MODULE_NAME bt_ias_client
#include "../../common/log.h"
enum {
IAS_DISCOVER_IN_PROGRESS,
IAS_NUM_FLAGS, /* keep as last */
};
struct bt_ias_client {
/* Handle for alert writes */
uint16_t alert_level_handle;
/** Internal flags **/
ATOMIC_DEFINE(flags, IAS_NUM_FLAGS);
/* Gatt discover procedure parameters */
struct bt_gatt_discover_params discover;
};
static const struct bt_uuid *alert_lvl_uuid = BT_UUID_ALERT_LEVEL;
static const struct bt_uuid *ias_uuid = BT_UUID_IAS;
static const struct bt_ias_client_cb *ias_client_cb;
static struct bt_ias_client client_list[CONFIG_BT_MAX_CONN];
static struct bt_ias_client *client_by_conn(struct bt_conn *conn)
{
return &client_list[bt_conn_index(conn)];
}
static void client_cleanup(struct bt_ias_client *ias_client)
{
(void)memset(ias_client, 0, sizeof(*ias_client));
}
static void discover_complete(struct bt_conn *conn, int err)
{
BT_DBG("conn %p", (void *)conn);
if (err) {
client_cleanup(client_by_conn(conn));
BT_DBG("Discover failed (err %d\n)", err);
}
if (ias_client_cb != NULL && ias_client_cb->discover != NULL) {
ias_client_cb->discover(conn, err);
}
}
int bt_ias_client_alert_write(struct bt_conn *conn, enum bt_ias_alert_lvl lvl)
{
int err;
uint8_t lvl_u8;
CHECKIF(conn == NULL) {
return -ENOTCONN;
}
if (client_by_conn(conn)->alert_level_handle == 0) {
return -EINVAL;
}
lvl_u8 = (uint8_t)lvl;
if (lvl_u8 < BT_IAS_ALERT_LVL_NO_ALERT || lvl_u8 > BT_IAS_ALERT_LVL_HIGH_ALERT) {
BT_ERR("Invalid alert value: %u", lvl_u8);
return -EINVAL;
}
err = bt_gatt_write_without_response(conn,
client_by_conn(conn)->alert_level_handle,
&lvl_u8, sizeof(lvl_u8), false);
if (err < 0) {
BT_ERR("IAS client level %d write failed: %d", lvl, err);
}
return err;
}
static uint8_t bt_ias_alert_lvl_disc_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *discover)
{
const struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;
atomic_clear_bit(client_by_conn(conn)->flags, IAS_DISCOVER_IN_PROGRESS);
if (attr == NULL) {
discover_complete(conn, -ENOENT);
return BT_GATT_ITER_STOP;
}
client_by_conn(conn)->alert_level_handle = chrc->value_handle;
discover_complete(conn, 0);
return BT_GATT_ITER_STOP;
}
static uint8_t bt_ias_prim_disc_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *discover)
{
int err;
const struct bt_gatt_service_val *data;
struct bt_ias_client *client = client_by_conn(conn);
if (!attr) {
discover_complete(conn, -ENOENT);
return BT_GATT_ITER_STOP;
}
data = attr->user_data;
client->discover.uuid = alert_lvl_uuid;
client->discover.start_handle = attr->handle + 1;
client->discover.end_handle = data->end_handle;
client->discover.type = BT_GATT_DISCOVER_CHARACTERISTIC;
client->discover.func = bt_ias_alert_lvl_disc_cb;
err = bt_gatt_discover(conn, &client->discover);
if (err) {
discover_complete(conn, err);
}
return BT_GATT_ITER_STOP;
}
int bt_ias_discover(struct bt_conn *conn)
{
int err;
struct bt_ias_client *client = client_by_conn(conn);
CHECKIF(!conn || !ias_client_cb || !ias_client_cb->discover) {
return -EINVAL;
}
if (atomic_test_bit(client->flags, IAS_DISCOVER_IN_PROGRESS)) {
return -EBUSY;
}
client_cleanup(client);
atomic_set_bit(client->flags, IAS_DISCOVER_IN_PROGRESS);
client->discover.uuid = ias_uuid;
client->discover.func = bt_ias_prim_disc_cb;
client->discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
client->discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
client->discover.type = BT_GATT_DISCOVER_PRIMARY;
err = bt_gatt_discover(conn, &client->discover);
if (err < 0) {
discover_complete(conn, err);
}
return err;
}
int bt_ias_client_cb_register(const struct bt_ias_client_cb *cb)
{
CHECKIF(!cb) {
return -EINVAL;
}
CHECKIF(cb->discover == NULL) {
return -EINVAL;
}
CHECKIF(ias_client_cb) {
return -EALREADY;
}
ias_client_cb = cb;
return 0;
}