Skip to content

Commit 4b0a423

Browse files
tannewtclaude
andcommitted
Implement GATT server for Zephyr _bleio
Implement Service, Characteristic, and Descriptor for local (server-side) GATT services on the Zephyr port. A Service dynamically builds a bt_gatt_attr table and registers it with bt_gatt_service_register(). Characteristics support READ, WRITE, NOTIFY and INDICATE properties with configurable security permissions. Setting a characteristic value with the NOTIFY property automatically calls bt_gatt_notify(). Zephyr's BT_UUID_GATT_PRIMARY / BT_UUID_GATT_CHRC / etc. macros expand to compound-literal pointers that have automatic storage duration inside functions. The attr table outlives those functions, so file-scope static const UUIDs are used instead to avoid dangling pointers. A bsim integration test verifies the full round-trip: CircuitPython creates a Battery Service (0x180F), sets the level to 75, and advertises. A Zephyr central connects, discovers the service, reads the Battery Level characteristic, and confirms the value is 75. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 39597dc commit 4b0a423

14 files changed

Lines changed: 786 additions & 24 deletions

File tree

ports/zephyr-cp/boards/nrf5340bsim_nrf5340_cpuapp.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ CONFIG_BT_BUF_ACL_RX_SIZE=255
2424

2525
CONFIG_BT_DEVICE_NAME_DYNAMIC=y
2626
CONFIG_BT_DEVICE_NAME_MAX=28
27+
CONFIG_BT_GATT_DYNAMIC_DB=y
2728

2829
# Ensure the network core image starts when using native simulator
2930
CONFIG_NATIVE_SIMULATOR_AUTOSTART_MCU=y

ports/zephyr-cp/common-hal/_bleio/Adapter.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ static void bleio_connected_cb(struct bt_conn *conn, uint8_t err) {
159159
}
160160

161161
static void bleio_disconnected_cb(struct bt_conn *conn, uint8_t reason) {
162-
printk("disconnected %p\n", conn);
163162
bleio_connection_release(bleio_connection_find_by_conn(conn), reason);
164163
}
165164

ports/zephyr-cp/common-hal/_bleio/Characteristic.c

Lines changed: 151 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,89 @@
55
//
66
// SPDX-License-Identifier: MIT
77

8+
#include <string.h>
9+
10+
#include <zephyr/bluetooth/gatt.h>
11+
812
#include "py/runtime.h"
913
#include "shared-bindings/_bleio/Characteristic.h"
1014
#include "shared-bindings/_bleio/Descriptor.h"
1115
#include "shared-bindings/_bleio/Service.h"
16+
#include "common-hal/_bleio/__init__.h"
17+
18+
uint16_t bleio_security_to_zephyr_perm(
19+
bleio_attribute_security_mode_t read_perm,
20+
bleio_attribute_security_mode_t write_perm,
21+
bleio_characteristic_properties_t props) {
22+
uint16_t perm = 0;
23+
24+
if (props & CHAR_PROP_READ) {
25+
switch (read_perm) {
26+
case SECURITY_MODE_OPEN:
27+
perm |= BT_GATT_PERM_READ;
28+
break;
29+
case SECURITY_MODE_ENC_NO_MITM:
30+
perm |= BT_GATT_PERM_READ_ENCRYPT;
31+
break;
32+
case SECURITY_MODE_ENC_WITH_MITM:
33+
perm |= BT_GATT_PERM_READ_AUTHEN;
34+
break;
35+
case SECURITY_MODE_LESC_ENC_WITH_MITM:
36+
perm |= BT_GATT_PERM_READ_LESC;
37+
break;
38+
default:
39+
break;
40+
}
41+
}
42+
43+
if (props & (CHAR_PROP_WRITE | CHAR_PROP_WRITE_NO_RESPONSE)) {
44+
switch (write_perm) {
45+
case SECURITY_MODE_OPEN:
46+
perm |= BT_GATT_PERM_WRITE;
47+
break;
48+
case SECURITY_MODE_ENC_NO_MITM:
49+
perm |= BT_GATT_PERM_WRITE_ENCRYPT;
50+
break;
51+
case SECURITY_MODE_ENC_WITH_MITM:
52+
perm |= BT_GATT_PERM_WRITE_AUTHEN;
53+
break;
54+
case SECURITY_MODE_LESC_ENC_WITH_MITM:
55+
perm |= BT_GATT_PERM_WRITE_LESC;
56+
break;
57+
default:
58+
break;
59+
}
60+
}
61+
62+
return perm;
63+
}
64+
65+
ssize_t bleio_char_read_cb(struct bt_conn *conn,
66+
const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) {
67+
bleio_characteristic_obj_t *self = attr->user_data;
68+
return bt_gatt_attr_read(conn, attr, buf, len, offset,
69+
self->current_value, self->current_value_len);
70+
}
71+
72+
ssize_t bleio_char_write_cb(struct bt_conn *conn,
73+
const struct bt_gatt_attr *attr, const void *buf, uint16_t len,
74+
uint16_t offset, uint8_t flags) {
75+
bleio_characteristic_obj_t *self = attr->user_data;
76+
if (offset + len > self->max_length) {
77+
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
78+
}
79+
memcpy(self->current_value + offset, buf, len);
80+
if (offset + len > self->current_value_len) {
81+
self->current_value_len = offset + len;
82+
}
83+
return len;
84+
}
85+
86+
void bleio_ccc_changed_cb(const struct bt_gatt_attr *attr, uint16_t value) {
87+
// Track subscription state if needed in the future.
88+
(void)attr;
89+
(void)value;
90+
}
1291

1392
bleio_characteristic_properties_t common_hal_bleio_characteristic_get_properties(bleio_characteristic_obj_t *self) {
1493
return self->props;
@@ -31,31 +110,95 @@ size_t common_hal_bleio_characteristic_get_max_length(bleio_characteristic_obj_t
31110
}
32111

33112
size_t common_hal_bleio_characteristic_get_value(bleio_characteristic_obj_t *self, uint8_t *buf, size_t len) {
34-
mp_raise_NotImplementedError(NULL);
113+
size_t copy_len = self->current_value_len;
114+
if (copy_len > len) {
115+
copy_len = len;
116+
}
117+
memcpy(buf, self->current_value, copy_len);
118+
return copy_len;
35119
}
36120

37-
void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t *self, bleio_descriptor_obj_t *descriptor) {
38-
mp_raise_NotImplementedError(NULL);
39-
}
121+
void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self,
122+
bleio_service_obj_t *service, uint16_t handle, bleio_uuid_obj_t *uuid,
123+
bleio_characteristic_properties_t props,
124+
bleio_attribute_security_mode_t read_perm,
125+
bleio_attribute_security_mode_t write_perm,
126+
mp_int_t max_length, bool fixed_length,
127+
mp_buffer_info_t *initial_value_bufinfo,
128+
const char *user_description) {
40129

41-
void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, bleio_service_obj_t *service, uint16_t handle, bleio_uuid_obj_t *uuid, bleio_characteristic_properties_t props, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_int_t max_length, bool fixed_length, mp_buffer_info_t *initial_value_bufinfo, const char *user_description) {
42-
mp_raise_NotImplementedError(NULL);
130+
self->service = service;
131+
self->uuid = uuid;
132+
self->handle = handle;
133+
self->props = props;
134+
self->read_perm = read_perm;
135+
self->write_perm = write_perm;
136+
self->max_length = max_length;
137+
self->fixed_length = fixed_length;
138+
self->observer = mp_const_none;
139+
self->descriptor_list = mp_obj_new_list(0, NULL);
140+
141+
// Allocate value buffer
142+
self->current_value = m_malloc(max_length);
143+
memset(self->current_value, 0, max_length);
144+
self->current_value_alloc = max_length;
145+
self->current_value_len = 0;
146+
147+
// Copy initial value if provided
148+
if (initial_value_bufinfo != NULL && initial_value_bufinfo->len > 0) {
149+
size_t len = initial_value_bufinfo->len;
150+
if (len > (size_t)max_length) {
151+
len = max_length;
152+
}
153+
memcpy(self->current_value, initial_value_bufinfo->buf, len);
154+
self->current_value_len = len;
155+
}
156+
157+
// Convert UUID to Zephyr format
158+
bleio_uuid_to_zephyr(uuid, &self->zephyr_uuid);
159+
160+
if (!service->is_remote) {
161+
common_hal_bleio_service_add_characteristic(service, self,
162+
initial_value_bufinfo, user_description);
163+
}
43164
}
44165

45166
bool common_hal_bleio_characteristic_deinited(bleio_characteristic_obj_t *self) {
46167
return self->service == NULL;
47168
}
48169

49170
void common_hal_bleio_characteristic_deinit(bleio_characteristic_obj_t *self) {
50-
// Nothing to do
171+
// Nothing to do - service handles unregistration
51172
}
52173

53174
void common_hal_bleio_characteristic_set_cccd(bleio_characteristic_obj_t *self, bool notify, bool indicate) {
175+
// Client-side only operation
54176
mp_raise_NotImplementedError(NULL);
55177
}
56178

57179
void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, mp_buffer_info_t *bufinfo) {
58-
mp_raise_NotImplementedError(NULL);
180+
size_t len = bufinfo->len;
181+
if (len > self->max_length) {
182+
len = self->max_length;
183+
}
184+
memcpy(self->current_value, bufinfo->buf, len);
185+
self->current_value_len = len;
186+
187+
// If NOTIFY and service is registered, send notification
188+
if ((self->props & CHAR_PROP_NOTIFY) && self->service != NULL &&
189+
self->service->registered) {
190+
bt_gatt_notify(NULL, &self->service->attrs[self->value_attr_index],
191+
self->current_value, self->current_value_len);
192+
}
193+
}
194+
195+
void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t *self,
196+
bleio_descriptor_obj_t *descriptor) {
197+
mp_obj_list_append(MP_OBJ_FROM_PTR(self->descriptor_list),
198+
MP_OBJ_FROM_PTR(descriptor));
199+
// Descriptors added after characteristic construction would need
200+
// service re-registration; for now the common case is handled by
201+
// Service.add_characteristic which adds descriptors at registration time.
59202
}
60203

61204
void bleio_characteristic_set_observer(bleio_characteristic_obj_t *self, mp_obj_t observer) {

ports/zephyr-cp/common-hal/_bleio/Characteristic.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#pragma once
99

10+
#include <zephyr/bluetooth/gatt.h>
11+
1012
#include "py/obj.h"
1113
#include "py/objlist.h"
1214
#include "shared-bindings/_bleio/Attribute.h"
@@ -34,7 +36,26 @@ typedef struct _bleio_characteristic_obj {
3436
uint16_t cccd_handle;
3537
uint16_t sccd_handle;
3638
bool fixed_length;
39+
// Zephyr GATT server fields:
40+
struct bt_gatt_chrc zephyr_chrc;
41+
struct bt_uuid_128 zephyr_uuid;
42+
struct bt_gatt_ccc_managed_user_data zephyr_ccc;
43+
size_t value_attr_index; // index in service attrs for notify
3744
} bleio_characteristic_obj_t;
3845

3946
void bleio_characteristic_set_observer(bleio_characteristic_obj_t *self, mp_obj_t observer);
4047
void bleio_characteristic_clear_observer(bleio_characteristic_obj_t *self);
48+
49+
// Callbacks used by Service.c when building GATT attr table
50+
ssize_t bleio_char_read_cb(struct bt_conn *conn,
51+
const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset);
52+
ssize_t bleio_char_write_cb(struct bt_conn *conn,
53+
const struct bt_gatt_attr *attr, const void *buf, uint16_t len,
54+
uint16_t offset, uint8_t flags);
55+
void bleio_ccc_changed_cb(const struct bt_gatt_attr *attr, uint16_t value);
56+
57+
// Permission mapping helper
58+
uint16_t bleio_security_to_zephyr_perm(
59+
bleio_attribute_security_mode_t read_perm,
60+
bleio_attribute_security_mode_t write_perm,
61+
bleio_characteristic_properties_t props);

ports/zephyr-cp/common-hal/_bleio/Descriptor.c

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,68 @@
55
//
66
// SPDX-License-Identifier: MIT
77

8+
#include <string.h>
9+
810
#include "py/runtime.h"
911
#include "shared-bindings/_bleio/Descriptor.h"
1012
#include "shared-bindings/_bleio/Characteristic.h"
13+
#include "common-hal/_bleio/__init__.h"
14+
15+
void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self,
16+
bleio_characteristic_obj_t *characteristic, bleio_uuid_obj_t *uuid,
17+
bleio_attribute_security_mode_t read_perm,
18+
bleio_attribute_security_mode_t write_perm,
19+
mp_int_t max_length, bool fixed_length,
20+
mp_buffer_info_t *initial_value_bufinfo) {
21+
22+
self->characteristic = characteristic;
23+
self->uuid = uuid;
24+
self->read_perm = read_perm;
25+
self->write_perm = write_perm;
26+
self->max_length = max_length;
27+
self->fixed_length = fixed_length;
28+
29+
// Allocate value buffer
30+
self->value = m_malloc(max_length);
31+
memset(self->value, 0, max_length);
32+
self->value_length = 0;
33+
34+
// Copy initial value if provided
35+
if (initial_value_bufinfo != NULL && initial_value_bufinfo->len > 0) {
36+
size_t len = initial_value_bufinfo->len;
37+
if (len > (size_t)max_length) {
38+
len = max_length;
39+
}
40+
memcpy(self->value, initial_value_bufinfo->buf, len);
41+
self->value_length = len;
42+
}
1143

12-
void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_characteristic_obj_t *characteristic, bleio_uuid_obj_t *uuid, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_int_t max_length, bool fixed_length, mp_buffer_info_t *initial_value_bufinfo) {
13-
mp_raise_NotImplementedError(NULL);
44+
// Convert UUID to Zephyr format
45+
bleio_uuid_to_zephyr(uuid, &self->zephyr_uuid);
1446
}
1547

1648
bleio_uuid_obj_t *common_hal_bleio_descriptor_get_uuid(bleio_descriptor_obj_t *self) {
1749
return self->uuid;
1850
}
1951

2052
bleio_characteristic_obj_t *common_hal_bleio_descriptor_get_characteristic(bleio_descriptor_obj_t *self) {
21-
mp_raise_NotImplementedError(NULL);
53+
return (bleio_characteristic_obj_t *)self->characteristic;
2254
}
2355

2456
size_t common_hal_bleio_descriptor_get_value(bleio_descriptor_obj_t *self, uint8_t *buf, size_t len) {
25-
mp_raise_NotImplementedError(NULL);
57+
size_t copy_len = self->value_length;
58+
if (copy_len > len) {
59+
copy_len = len;
60+
}
61+
memcpy(buf, self->value, copy_len);
62+
return copy_len;
2663
}
2764

2865
void common_hal_bleio_descriptor_set_value(bleio_descriptor_obj_t *self, mp_buffer_info_t *bufinfo) {
29-
mp_raise_NotImplementedError(NULL);
66+
size_t len = bufinfo->len;
67+
if (len > self->max_length) {
68+
len = self->max_length;
69+
}
70+
memcpy(self->value, bufinfo->buf, len);
71+
self->value_length = len;
3072
}

ports/zephyr-cp/common-hal/_bleio/Descriptor.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77

88
#pragma once
99

10+
#include <zephyr/bluetooth/uuid.h>
11+
1012
#include "py/obj.h"
1113
#include "shared-bindings/_bleio/Attribute.h"
1214
#include "common-hal/_bleio/UUID.h"
1315

16+
// Forward declaration to break circular dependency
17+
struct _bleio_characteristic_obj;
18+
1419
typedef struct _bleio_descriptor_obj {
1520
mp_obj_base_t base;
1621
bleio_uuid_obj_t *uuid;
@@ -21,4 +26,6 @@ typedef struct _bleio_descriptor_obj {
2126
bool fixed_length;
2227
uint8_t *value;
2328
uint16_t value_length;
29+
struct _bleio_characteristic_obj *characteristic;
30+
struct bt_uuid_128 zephyr_uuid;
2431
} bleio_descriptor_obj_t;

0 commit comments

Comments
 (0)