| /* |
| * 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> |
| |
| LOG_MODULE_REGISTER(bt_ias_client, CONFIG_BT_IAS_CLIENT_LOG_LEVEL); |
| |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/services/ias.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) |
| { |
| LOG_DBG("conn %p", (void *)conn); |
| |
| if (err) { |
| client_cleanup(client_by_conn(conn)); |
| LOG_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_MILD_ALERT && |
| lvl_u8 != BT_IAS_ALERT_LVL_HIGH_ALERT) { |
| LOG_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) { |
| LOG_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; |
| |
| atomic_clear_bit(client_by_conn(conn)->flags, IAS_DISCOVER_IN_PROGRESS); |
| |
| if (attr == NULL) { |
| discover_complete(conn, -ENOENT); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| chrc = (struct bt_gatt_chrc *)attr->user_data; |
| |
| 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; |
| } |