|
| 1 | +/* |
| 2 | + * Copyright (c) 2025 Koppel Electronic |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#include <zephyr/kernel.h> |
| 8 | +#include <zephyr/bluetooth/bluetooth.h> |
| 9 | +#include <zephyr/sys/byteorder.h> |
| 10 | +#include <zephyr/bluetooth/gatt.h> |
| 11 | + |
| 12 | + |
| 13 | +#define DEVICE_NAME CONFIG_BT_DEVICE_NAME |
| 14 | +#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1) |
| 15 | + |
| 16 | +static const struct bt_data ad[] = { |
| 17 | + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), |
| 18 | + BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN), |
| 19 | +}; |
| 20 | + |
| 21 | +/* ----------------------------------------------------------------------------- |
| 22 | + * Local implementation of GAP service |
| 23 | + */ |
| 24 | + |
| 25 | +static ssize_t read_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, |
| 26 | + void *buf, uint16_t len, uint16_t offset) |
| 27 | +{ |
| 28 | + const char *name = bt_get_name(); |
| 29 | + |
| 30 | + return bt_gatt_attr_read(conn, attr, buf, len, offset, name, |
| 31 | + strlen(name)); |
| 32 | +} |
| 33 | + |
| 34 | +static ssize_t write_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, |
| 35 | + uint16_t len, uint16_t offset, uint8_t flags) |
| 36 | +{ |
| 37 | + /* adding one to fit the terminating null character */ |
| 38 | + char value[CONFIG_BT_DEVICE_NAME_MAX + 1] = {}; |
| 39 | + |
| 40 | + if (offset != 0) { |
| 41 | + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); |
| 42 | + } |
| 43 | + |
| 44 | + if (offset + len > CONFIG_BT_DEVICE_NAME_MAX) { |
| 45 | + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); |
| 46 | + } |
| 47 | + |
| 48 | + memcpy(value, buf, len); |
| 49 | + |
| 50 | + value[len] = '\0'; |
| 51 | + |
| 52 | + /* Check if the name starts with capital letter */ |
| 53 | + if (value[0] < 'A' || value[0] > 'Z') { |
| 54 | + printk("Rejected name change to \"%s\": must start with capital letter\n", value); |
| 55 | + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); |
| 56 | + } |
| 57 | + |
| 58 | + bt_set_name(value); |
| 59 | + |
| 60 | + printk("Name changed to \"%s\"\n", value); |
| 61 | + |
| 62 | + return len; |
| 63 | +} |
| 64 | + |
| 65 | +static ssize_t read_appearance(struct bt_conn *conn, |
| 66 | + const struct bt_gatt_attr *attr, void *buf, |
| 67 | + uint16_t len, uint16_t offset) |
| 68 | +{ |
| 69 | + uint16_t appearance = sys_cpu_to_le16(bt_get_appearance()); |
| 70 | + |
| 71 | + return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance, sizeof(appearance)); |
| 72 | +} |
| 73 | + |
| 74 | +BT_GATT_SERVICE_DEFINE(BT_GATT_GAP_SVC_DEFAULT_NAME, |
| 75 | + BT_GATT_PRIMARY_SERVICE(BT_UUID_GAP), |
| 76 | + /* Require pairing for writes to device name */ |
| 77 | + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, |
| 78 | + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, |
| 79 | + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, |
| 80 | + read_name, write_name, NULL), |
| 81 | + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_APPEARANCE, |
| 82 | + BT_GATT_CHRC_READ, |
| 83 | + BT_GATT_PERM_READ, |
| 84 | + read_appearance, NULL, NULL), |
| 85 | +); |
| 86 | + |
| 87 | +/* End of local implementation of GAP service |
| 88 | + * --------------------------------------------------------------------------- |
| 89 | + */ |
| 90 | + |
| 91 | +static void connected(struct bt_conn *conn, uint8_t conn_err) |
| 92 | +{ |
| 93 | + char addr[BT_ADDR_LE_STR_LEN]; |
| 94 | + |
| 95 | + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); |
| 96 | + |
| 97 | + if (conn_err) { |
| 98 | + printk("Failed to connect to %s (%u)\n", addr, conn_err); |
| 99 | + return; |
| 100 | + } |
| 101 | + |
| 102 | + printk("Connected: %s\n", addr); |
| 103 | +} |
| 104 | + |
| 105 | +static void disconnected(struct bt_conn *conn, uint8_t reason) |
| 106 | +{ |
| 107 | + char addr[BT_ADDR_LE_STR_LEN]; |
| 108 | + |
| 109 | + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); |
| 110 | + |
| 111 | + printk("Disconnected: %s (reason 0x%02x)\n", addr, reason); |
| 112 | +} |
| 113 | + |
| 114 | +static int start_advertising(void) |
| 115 | +{ |
| 116 | + int err; |
| 117 | + |
| 118 | + err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), NULL, 0); |
| 119 | + if (err) { |
| 120 | + printk("Advertising failed to start (err %d)\n", err); |
| 121 | + } |
| 122 | + |
| 123 | + return err; |
| 124 | +} |
| 125 | + |
| 126 | +static void recycled(void) |
| 127 | +{ |
| 128 | + start_advertising(); |
| 129 | +} |
| 130 | + |
| 131 | +BT_CONN_CB_DEFINE(conn_callbacks) = { |
| 132 | + .connected = connected, |
| 133 | + .disconnected = disconnected, |
| 134 | + .recycled = recycled, |
| 135 | +}; |
| 136 | + |
| 137 | +int main(void) |
| 138 | +{ |
| 139 | + int err; |
| 140 | + |
| 141 | + printk("Sample - Bluetooth Peripheral GAP service\n"); |
| 142 | + |
| 143 | + err = bt_enable(NULL); |
| 144 | + if (err) { |
| 145 | + printk("Failed to enable bluetooth: %d\n", err); |
| 146 | + return err; |
| 147 | + } |
| 148 | + |
| 149 | + err = start_advertising(); |
| 150 | + if (err) { |
| 151 | + return err; |
| 152 | + } |
| 153 | + |
| 154 | + printk("Initialization complete\n"); |
| 155 | + |
| 156 | + return 0; |
| 157 | +} |
0 commit comments