forked from tinygo-org/bluetooth
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gattc_sd.go
454 lines (391 loc) · 13.2 KB
/
gattc_sd.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
//go:build (softdevice && s132v6) || (softdevice && s140v6) || (softdevice && s140v7)
// +build softdevice,s132v6 softdevice,s140v6 softdevice,s140v7
package bluetooth
/*
#include "ble_gattc.h"
*/
import "C"
import (
"device/arm"
"errors"
"runtime/volatile"
)
const (
maxDefaultServicesToDiscover = 6
maxDefaultCharacteristicsToDiscover = 8
)
var (
errAlreadyDiscovering = errors.New("bluetooth: already discovering a service or characteristic")
errNotFound = errors.New("bluetooth: not found")
errNoNotify = errors.New("bluetooth: no notify permission")
)
// A global used while discovering services, to communicate between the main
// program and the event handler.
var discoveringService struct {
state volatile.Register8 // 0 means nothing happening, 1 means in progress, 2 means found something
startHandle volatile.Register16
endHandle volatile.Register16
uuid C.ble_uuid_t
}
// DeviceService is a BLE service on a connected peripheral device. It is only
// valid as long as the device remains connected.
type DeviceService struct {
uuid shortUUID
connectionHandle uint16
startHandle uint16
endHandle uint16
}
// UUID returns the UUID for this DeviceService.
func (s *DeviceService) UUID() UUID {
return s.uuid.UUID()
}
// DiscoverServices starts a service discovery procedure. Pass a list of service
// UUIDs you are interested in to this function. Either a slice of all services
// is returned (of the same length as the requested UUIDs and in the same
// order), or if some services could not be discovered an error is returned.
//
// Passing a nil slice of UUIDs will return a complete list of
// services.
//
// On the Nordic SoftDevice, only one service discovery procedure may be done at
// a time.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
if discoveringService.state.Get() != 0 {
// Not concurrency safe, but should catch most concurrency misuses.
return nil, errAlreadyDiscovering
}
sz := maxDefaultServicesToDiscover
if len(uuids) > 0 {
sz = len(uuids)
}
services := make([]DeviceService, 0, sz)
var shortUUIDs []C.ble_uuid_t
// Make a map of UUIDs in SoftDevice short form, for easier comparing.
if len(uuids) > 0 {
shortUUIDs = make([]C.ble_uuid_t, sz)
for i, uuid := range uuids {
var errCode uint32
shortUUIDs[i], errCode = uuid.shortUUID()
if errCode != 0 {
return nil, Error(errCode)
}
}
}
numFound := 0
var startHandle uint16 = 1
for i := 0; i < sz; i++ {
var suuid C.ble_uuid_t
if len(uuids) > 0 {
suuid = shortUUIDs[i]
}
// Start discovery of this service.
discoveringService.state.Set(1)
var errCode uint32
if len(uuids) > 0 {
errCode = C.sd_ble_gattc_primary_services_discover(d.connectionHandle, startHandle, &suuid)
} else {
// calling with nil searches for all primary services.
// TODO: need a way to set suuid from the returned data
errCode = C.sd_ble_gattc_primary_services_discover(d.connectionHandle, startHandle, nil)
}
if errCode != 0 {
discoveringService.state.Set(0)
return nil, Error(errCode)
}
// Wait until it is discovered.
// TODO: use some sort of condition variable once the scheduler supports
// them.
for discoveringService.state.Get() == 1 {
// still waiting...
arm.Asm("wfe")
}
// Retrieve values, and mark the global as unused.
startHandle = discoveringService.startHandle.Get()
endHandle := discoveringService.endHandle.Get()
suuid = discoveringService.uuid
discoveringService.state.Set(0)
if startHandle == 0 {
// The event handler will set the start handle to zero if the
// service was not found.
return nil, errNotFound
}
// Store the discovered service.
svc := DeviceService{
uuid: shortUUID(suuid),
connectionHandle: d.connectionHandle,
startHandle: startHandle,
endHandle: endHandle,
}
services = append(services, svc)
numFound++
if numFound >= sz {
break
}
// last entry
if endHandle == 0xffff {
break
}
// start with the next handle
startHandle = endHandle + 1
}
return services, nil
}
// DeviceCharacteristic is a BLE characteristic on a connected peripheral
// device. It is only valid as long as the device remains connected.
type DeviceCharacteristic struct {
uuid shortUUID
connectionHandle uint16
valueHandle uint16
cccdHandle uint16
permissions CharacteristicPermissions
}
// UUID returns the UUID for this DeviceCharacteristic.
func (c *DeviceCharacteristic) UUID() UUID {
return c.uuid.UUID()
}
// A global used to pass information from the event handler back to the
// DiscoverCharacteristics function below.
var discoveringCharacteristic struct {
uuid C.ble_uuid_t
char_props C.ble_gatt_char_props_t
handle_value volatile.Register16
}
// DiscoverCharacteristics discovers characteristics in this service. Pass a
// list of characteristic UUIDs you are interested in to this function. Either a
// list of all requested services is returned, or if some services could not be
// discovered an error is returned. If there is no error, the characteristics
// slice has the same length as the UUID slice with characteristics in the same
// order in the slice as in the requested UUID list.
//
// Passing a nil slice of UUIDs will return a complete
// list of characteristics.
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
if discoveringCharacteristic.handle_value.Get() != 0 {
return nil, errAlreadyDiscovering
}
sz := maxDefaultCharacteristicsToDiscover
if len(uuids) > 0 {
sz = len(uuids)
}
characteristics := make([]DeviceCharacteristic, 0, sz)
var shortUUIDs []C.ble_uuid_t
// Make a map of UUIDs in SoftDevice short form, for easier comparing.
if len(uuids) > 0 {
shortUUIDs = make([]C.ble_uuid_t, sz)
for i, uuid := range uuids {
var errCode uint32
shortUUIDs[i], errCode = uuid.shortUUID()
if errCode != 0 {
return nil, Error(errCode)
}
}
}
// Request characteristics one by one, until all are found.
numFound := 0
startHandle := s.startHandle
for startHandle < s.endHandle {
// Discover the next characteristic in this service.
handles := C.ble_gattc_handle_range_t{
start_handle: startHandle,
end_handle: startHandle + 1,
}
errCode := C.sd_ble_gattc_characteristics_discover(s.connectionHandle, &handles)
if errCode != 0 {
return nil, Error(errCode)
}
// Wait until it is discovered.
// TODO: use some sort of condition variable once the scheduler supports
// them.
for discoveringCharacteristic.handle_value.Get() == 0 {
arm.Asm("wfe")
}
foundCharacteristicHandle := discoveringCharacteristic.handle_value.Get()
discoveringCharacteristic.handle_value.Set(0)
// was it last characteristic?
if foundCharacteristicHandle == 0xffff {
break
}
// Start the next request from the handle right after this one.
startHandle = foundCharacteristicHandle + 1
// not one of the characteristics we are looking for
if len(shortUUIDs) > 0 && !shortUUID(discoveringCharacteristic.uuid).IsIn(shortUUIDs) {
continue
}
// Found a characteristic.
permissions := CharacteristicPermissions(0)
rawPermissions := discoveringCharacteristic.char_props
if rawPermissions.bitfield_broadcast() != 0 {
permissions |= CharacteristicBroadcastPermission
}
if rawPermissions.bitfield_read() != 0 {
permissions |= CharacteristicReadPermission
}
if rawPermissions.bitfield_write_wo_resp() != 0 {
permissions |= CharacteristicWriteWithoutResponsePermission
}
if rawPermissions.bitfield_write() != 0 {
permissions |= CharacteristicWritePermission
}
if rawPermissions.bitfield_notify() != 0 {
permissions |= CharacteristicNotifyPermission
}
if rawPermissions.bitfield_indicate() != 0 {
permissions |= CharacteristicIndicatePermission
}
dc := DeviceCharacteristic{uuid: shortUUID(discoveringCharacteristic.uuid)}
dc.permissions = permissions
dc.valueHandle = foundCharacteristicHandle
if permissions&CharacteristicNotifyPermission != 0 {
// This characteristic has the notify permission, so most
// likely it also supports notifications.
errCode := C.sd_ble_gattc_descriptors_discover(s.connectionHandle, &C.ble_gattc_handle_range_t{
start_handle: startHandle,
end_handle: startHandle + 1,
})
if errCode != 0 {
return nil, Error(errCode)
}
// Wait until the descriptor handle is found.
for discoveringCharacteristic.handle_value.Get() == 0 {
arm.Asm("wfe")
}
foundDescriptorHandle := discoveringCharacteristic.handle_value.Get()
discoveringCharacteristic.handle_value.Set(0)
dc.cccdHandle = foundDescriptorHandle
}
characteristics = append(characteristics, dc)
numFound++
if numFound >= sz {
break
}
}
if len(uuids) > 0 && numFound != len(uuids) {
return nil, errNotFound
}
return characteristics, nil
}
// WriteWithoutResponse replaces the characteristic value with a new value. The
// call will return before all data has been written. A limited number of such
// writes can be in flight at any given time. This call is also known as a
// "write command" (as opposed to a write request).
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
if len(p) == 0 {
return 0, nil
}
errCode := C.sd_ble_gattc_write(c.connectionHandle, &C.ble_gattc_write_params_t{
write_op: C.BLE_GATT_OP_WRITE_CMD,
handle: c.valueHandle,
offset: 0,
len: uint16(len(p)),
p_value: &p[0],
})
if errCode != 0 {
return 0, Error(errCode)
}
return len(p), nil
}
type gattcNotificationCallback struct {
connectionHandle uint16
valueHandle uint16 // may be 0 if the slot is empty
callback func([]byte)
}
// List of notification callbacks for the current connection. Some slots may be
// empty, they are indicated with a zero valueHandle. They can be reused for new
// notification callbacks.
var gattcNotificationCallbacks []gattcNotificationCallback
// EnableNotifications enables notifications in the Client Characteristic
// Configuration Descriptor (CCCD). This means that most peripherals will send a
// notification with a new value every time the value of the characteristic
// changes.
//
// Warning: when using the SoftDevice, the callback is called from an interrupt
// which means there are various limitations (such as not being able to allocate
// heap memory).
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
if c.permissions&CharacteristicNotifyPermission == 0 {
return errNoNotify
}
// Try to insert the callback in the list.
updatedCallback := false
mask := DisableInterrupts()
for i, callbackInfo := range gattcNotificationCallbacks {
// Check for re-enabling an already enabled notification.
if callbackInfo.valueHandle == c.valueHandle {
gattcNotificationCallbacks[i].callback = callback
updatedCallback = true
break
}
}
if !updatedCallback {
for i, callbackInfo := range gattcNotificationCallbacks {
// Check for empty slots.
if callbackInfo.valueHandle == 0 {
gattcNotificationCallbacks[i] = gattcNotificationCallback{
connectionHandle: c.connectionHandle,
valueHandle: c.valueHandle,
callback: callback,
}
updatedCallback = true
break
}
}
}
RestoreInterrupts(mask)
// Add this callback to the list of callbacks, if it couldn't be inserted
// into the list.
if !updatedCallback {
// The append call is done outside of a cricital section to avoid GC in
// a critical section.
callbackList := append(gattcNotificationCallbacks, gattcNotificationCallback{
connectionHandle: c.connectionHandle,
valueHandle: c.valueHandle,
callback: callback,
})
mask := DisableInterrupts()
gattcNotificationCallbacks = callbackList
RestoreInterrupts(mask)
}
// Write to the CCCD to enable notifications. Don't wait for a response.
value := [2]byte{0x01, 0x00} // 0x0001 enables notifications (and disables indications)
errCode := C.sd_ble_gattc_write(c.connectionHandle, &C.ble_gattc_write_params_t{
write_op: C.BLE_GATT_OP_WRITE_CMD,
handle: c.cccdHandle,
offset: 0,
len: 2,
p_value: &value[0],
})
return makeError(errCode)
}
// A global used to pass information from the event handler back to the
// Read function below.
var readingCharacteristic struct {
handle_value volatile.Register16
offset uint16
length uint16
value []byte
}
// Read reads the current characteristic value up to MTU length.
// A future enhancement would be to be able to retrieve a longer
// value by making multiple calls.
func (c *DeviceCharacteristic) Read(data []byte) (n int, err error) {
// global will copy bytes from read operation into data slice
readingCharacteristic.value = data
errCode := C.sd_ble_gattc_read(c.connectionHandle, c.valueHandle, 0)
if errCode != 0 {
return 0, Error(errCode)
}
// wait for response with data
for readingCharacteristic.handle_value.Get() == 0 {
arm.Asm("wfe")
}
// how much data was read into buffer
n = int(readingCharacteristic.length)
// prepare for next read
readingCharacteristic.handle_value.Set(0)
readingCharacteristic.length = 0
return
}
// GetMTU returns the MTU for the characteristic.
func (c DeviceCharacteristic) GetMTU() (uint16, error) {
return uint16(C.BLE_GATT_ATT_MTU_DEFAULT), nil
}