|
@@ -2,6 +2,8 @@
|
2
|
2
|
* ble.c
|
3
|
3
|
*
|
4
|
4
|
* https://github.com/raspberrypi/pico-examples/blob/master/pico_w/bt/standalone/client.c
|
|
5
|
+ * https://vanhunteradams.com/Pico/BLE/BTStack_HCI.html
|
|
6
|
+ * https://github.com/bluekitchen/btstack/blob/master/example/gap_inquiry.c
|
5
|
7
|
*
|
6
|
8
|
* Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
|
7
|
9
|
*
|
|
@@ -18,13 +20,15 @@
|
18
|
20
|
* See <http://www.gnu.org/licenses/>.
|
19
|
21
|
*/
|
20
|
22
|
|
21
|
|
-#include "btstack.h"
|
22
|
23
|
#include "pico/cyw43_arch.h"
|
23
|
24
|
|
24
|
25
|
#include "config.h"
|
25
|
26
|
#include "log.h"
|
|
27
|
+#include "util.h"
|
26
|
28
|
#include "ble.h"
|
27
|
29
|
|
|
30
|
+#define GAP_INQUIRY_INTERVAL 5 // *1.28s
|
|
31
|
+
|
28
|
32
|
enum ble_state {
|
29
|
33
|
TC_OFF = 0,
|
30
|
34
|
TC_IDLE,
|
|
@@ -38,9 +42,105 @@ enum ble_state {
|
38
|
42
|
|
39
|
43
|
static btstack_packet_callback_registration_t hci_event_callback_registration;
|
40
|
44
|
static enum ble_state state = TC_OFF;
|
|
45
|
+static struct ble_scan_result scans[BLE_MAX_SCAN_RESULTS] = {0};
|
|
46
|
+
|
|
47
|
+// TODO scan result entries are not aging out
|
41
|
48
|
|
42
|
|
-static bd_addr_t server_addr;
|
43
|
|
-static bd_addr_type_t server_addr_type;
|
|
49
|
+static void hci_add_scan_result(bd_addr_t addr, bd_addr_type_t type,
|
|
50
|
+ int8_t rssi, uint8_t scan_mode,
|
|
51
|
+ uint16_t clock_offset, uint32_t class) {
|
|
52
|
+ int unused = -1;
|
|
53
|
+
|
|
54
|
+ for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
|
|
55
|
+ if (!scans[i].set) {
|
|
56
|
+ if (unused < 0) {
|
|
57
|
+ unused = i;
|
|
58
|
+ }
|
|
59
|
+ continue;
|
|
60
|
+ }
|
|
61
|
+
|
|
62
|
+ if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) == 0) {
|
|
63
|
+ // already in list, just update changed values
|
|
64
|
+ scans[i].time = to_ms_since_boot(get_absolute_time());
|
|
65
|
+ if (scans[i].type == 0) {
|
|
66
|
+ scans[i].type = type;
|
|
67
|
+ }
|
|
68
|
+ if (scans[i].rssi == 0) {
|
|
69
|
+ scans[i].rssi = rssi;
|
|
70
|
+ }
|
|
71
|
+ if (scans[i].page_scan_repetition_mode == 0) {
|
|
72
|
+ scans[i].page_scan_repetition_mode = scan_mode;
|
|
73
|
+ }
|
|
74
|
+ if (scans[i].clock_offset == 0) {
|
|
75
|
+ scans[i].clock_offset = clock_offset;
|
|
76
|
+ }
|
|
77
|
+ if (scans[i].class == 0) {
|
|
78
|
+ scans[i].class = class;
|
|
79
|
+ }
|
|
80
|
+ return;
|
|
81
|
+ }
|
|
82
|
+ }
|
|
83
|
+
|
|
84
|
+ if (unused < 0) {
|
|
85
|
+ debug("no space in scan results for %s", bd_addr_to_str(addr));
|
|
86
|
+ return;
|
|
87
|
+ }
|
|
88
|
+
|
|
89
|
+ debug("new device with addr %s", bd_addr_to_str(addr));
|
|
90
|
+ scans[unused].set = true;
|
|
91
|
+ scans[unused].time = to_ms_since_boot(get_absolute_time());
|
|
92
|
+ scans[unused].state = BLE_NAME_REQUEST;
|
|
93
|
+ memcpy(scans[unused].addr, addr, sizeof(bd_addr_t));
|
|
94
|
+ scans[unused].type = type;
|
|
95
|
+ scans[unused].rssi = rssi;
|
|
96
|
+ scans[unused].page_scan_repetition_mode = scan_mode;
|
|
97
|
+ scans[unused].clock_offset = clock_offset;
|
|
98
|
+ scans[unused].class = class;
|
|
99
|
+ scans[unused].name[0] = '\0';
|
|
100
|
+}
|
|
101
|
+
|
|
102
|
+static void hci_scan_result_add_name(bd_addr_t addr, const uint8_t *data, uint8_t data_size) {
|
|
103
|
+ for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
|
|
104
|
+ if (!scans[i].set) {
|
|
105
|
+ continue;
|
|
106
|
+ }
|
|
107
|
+ if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) != 0) {
|
|
108
|
+ continue;
|
|
109
|
+ }
|
|
110
|
+
|
|
111
|
+ uint8_t len = data_size;
|
|
112
|
+ if (len > BLE_MAX_NAME_LENGTH) {
|
|
113
|
+ len = BLE_MAX_NAME_LENGTH;
|
|
114
|
+ }
|
|
115
|
+ memcpy(scans[i].name, data, len);
|
|
116
|
+ scans[i].name[len] = '\0';
|
|
117
|
+ scans[i].time = to_ms_since_boot(get_absolute_time());
|
|
118
|
+ return;
|
|
119
|
+ }
|
|
120
|
+
|
|
121
|
+ debug("no matching entry for %s to add name '%.*s' to", bd_addr_to_str(addr), data_size, data);
|
|
122
|
+}
|
|
123
|
+
|
|
124
|
+static void hci_continue_name_requests(void) {
|
|
125
|
+ for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
|
|
126
|
+ if (!scans[i].set) {
|
|
127
|
+ continue;
|
|
128
|
+ }
|
|
129
|
+
|
|
130
|
+ if (scans[i].state == BLE_NAME_REQUEST) {
|
|
131
|
+ scans[i].state = BLE_NAME_INQUIRED;
|
|
132
|
+ debug("Inquire remote name of %s", bd_addr_to_str(scans[i].addr));
|
|
133
|
+ gap_remote_name_request(scans[i].addr,
|
|
134
|
+ scans[i].page_scan_repetition_mode,
|
|
135
|
+ scans[i].clock_offset | 0x8000);
|
|
136
|
+ return;
|
|
137
|
+ }
|
|
138
|
+ }
|
|
139
|
+
|
|
140
|
+ if (state == TC_W4_SCAN_RESULT) {
|
|
141
|
+ gap_inquiry_start(GAP_INQUIRY_INTERVAL);
|
|
142
|
+ }
|
|
143
|
+}
|
44
|
144
|
|
45
|
145
|
static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
46
|
146
|
UNUSED(size);
|
|
@@ -64,24 +164,147 @@ static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *pa
|
64
|
164
|
}
|
65
|
165
|
break;
|
66
|
166
|
|
|
167
|
+ case HCI_EVENT_INQUIRY_COMPLETE:
|
|
168
|
+ case HCI_EVENT_COMMAND_STATUS:
|
|
169
|
+ case HCI_EVENT_REMOTE_HOST_SUPPORTED_FEATURES:
|
|
170
|
+ case BTSTACK_EVENT_SCAN_MODE_CHANGED:
|
67
|
171
|
case HCI_EVENT_COMMAND_COMPLETE:
|
68
|
172
|
case HCI_EVENT_TRANSPORT_PACKET_SENT:
|
69
|
173
|
break;
|
70
|
174
|
|
71
|
|
- case GAP_EVENT_ADVERTISING_REPORT:
|
|
175
|
+ case GAP_EVENT_ADVERTISING_REPORT: {
|
72
|
176
|
if (state != TC_W4_SCAN_RESULT) {
|
73
|
177
|
debug("scan result in invalid state %d", state);
|
74
|
178
|
return;
|
75
|
179
|
}
|
76
|
180
|
|
77
|
|
- gap_event_advertising_report_get_address(packet, server_addr);
|
78
|
|
- server_addr_type = gap_event_advertising_report_get_address_type(packet);
|
79
|
|
- debug("Found device with addr %s", bd_addr_to_str(server_addr));
|
|
181
|
+ bd_addr_t addr;
|
|
182
|
+ gap_event_advertising_report_get_address(packet, addr);
|
|
183
|
+
|
|
184
|
+ bd_addr_type_t type;
|
|
185
|
+ type = gap_event_advertising_report_get_address_type(packet);
|
|
186
|
+
|
|
187
|
+ int8_t rssi;
|
|
188
|
+ rssi = (int8_t)gap_event_advertising_report_get_rssi(packet);
|
|
189
|
+
|
|
190
|
+ // add data received so far
|
|
191
|
+ hci_add_scan_result(addr, type, rssi, 0, 0, 0);
|
|
192
|
+
|
|
193
|
+ // get advertisement from report event
|
|
194
|
+ const uint8_t *adv_data = gap_event_advertising_report_get_data(packet);
|
|
195
|
+ uint8_t adv_len = gap_event_advertising_report_get_data_length(packet);
|
|
196
|
+
|
|
197
|
+ // iterate over advertisement data
|
|
198
|
+ ad_context_t context;
|
|
199
|
+ for (ad_iterator_init(&context, adv_len, adv_data);
|
|
200
|
+ ad_iterator_has_more(&context);
|
|
201
|
+ ad_iterator_next(&context)) {
|
|
202
|
+ uint8_t data_type = ad_iterator_get_data_type(&context);
|
|
203
|
+ uint8_t data_size = ad_iterator_get_data_len(&context);
|
|
204
|
+ const uint8_t *data = ad_iterator_get_data(&context);
|
|
205
|
+ switch (data_type) {
|
|
206
|
+ case BLUETOOTH_DATA_TYPE_SHORTENED_LOCAL_NAME:
|
|
207
|
+ case BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME:
|
|
208
|
+ // unfortunately not the name we're interested in for our targets...
|
|
209
|
+ hci_scan_result_add_name(addr, data, data_size);
|
|
210
|
+ break;
|
|
211
|
+
|
|
212
|
+ case BLUETOOTH_DATA_TYPE_FLAGS:
|
|
213
|
+ case BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
|
|
214
|
+ case BLUETOOTH_DATA_TYPE_SERVICE_DATA_16_BIT_UUID:
|
|
215
|
+ case BLUETOOTH_DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
|
|
216
|
+ case BLUETOOTH_DATA_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE:
|
|
217
|
+ break;
|
|
218
|
+
|
|
219
|
+ default:
|
|
220
|
+ debug("Unexpected advertisement type 0x%02X from %s", data_type, bd_addr_to_str(addr));
|
|
221
|
+ hexdump(data, data_size);
|
|
222
|
+ break;
|
|
223
|
+ }
|
|
224
|
+ }
|
|
225
|
+ break;
|
|
226
|
+ }
|
|
227
|
+
|
|
228
|
+ case GAP_EVENT_INQUIRY_RESULT: {
|
|
229
|
+ bd_addr_t addr;
|
|
230
|
+ gap_event_inquiry_result_get_bd_addr(packet, addr);
|
|
231
|
+
|
|
232
|
+ uint8_t scan_mode;
|
|
233
|
+ scan_mode = gap_event_inquiry_result_get_page_scan_repetition_mode(packet);
|
|
234
|
+
|
|
235
|
+ uint16_t clock_offset;
|
|
236
|
+ clock_offset = gap_event_inquiry_result_get_clock_offset(packet);
|
|
237
|
+
|
|
238
|
+ uint32_t class;
|
|
239
|
+ class = gap_event_inquiry_result_get_class_of_device(packet);
|
|
240
|
+
|
|
241
|
+ int8_t rssi = 0;
|
|
242
|
+ if (gap_event_inquiry_result_get_rssi_available(packet)) {
|
|
243
|
+ rssi = (int8_t)gap_event_inquiry_result_get_rssi(packet);
|
|
244
|
+ }
|
|
245
|
+
|
|
246
|
+ // add data received so far
|
|
247
|
+ hci_add_scan_result(addr, 0, rssi, scan_mode, clock_offset, class);
|
|
248
|
+
|
|
249
|
+ if (gap_event_inquiry_result_get_name_available(packet)) {
|
|
250
|
+ const uint8_t *data = gap_event_inquiry_result_get_name(packet);
|
|
251
|
+ uint8_t data_size = gap_event_inquiry_result_get_name_len(packet);
|
|
252
|
+ // still not the name we need
|
|
253
|
+ hci_scan_result_add_name(addr, data, data_size);
|
|
254
|
+ } else {
|
|
255
|
+ for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
|
|
256
|
+ if (!scans[i].set) {
|
|
257
|
+ continue;
|
|
258
|
+ }
|
|
259
|
+ if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) == 0) {
|
|
260
|
+ scans[i].state = BLE_NAME_REQUEST;
|
|
261
|
+ break;
|
|
262
|
+ }
|
|
263
|
+ }
|
|
264
|
+ }
|
|
265
|
+ break;
|
|
266
|
+ }
|
|
267
|
+
|
|
268
|
+ case HCI_EVENT_EXTENDED_INQUIRY_RESPONSE:
|
|
269
|
+ // TODO ?
|
|
270
|
+ break;
|
|
271
|
+
|
|
272
|
+ case GAP_EVENT_INQUIRY_COMPLETE:
|
|
273
|
+ // trigger re-read of all names
|
|
274
|
+ for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
|
|
275
|
+ if (scans[i].state == BLE_NAME_INQUIRED) {
|
|
276
|
+ scans[i].state = BLE_NAME_REQUEST;
|
|
277
|
+ }
|
|
278
|
+ }
|
|
279
|
+ hci_continue_name_requests();
|
|
280
|
+ break;
|
|
281
|
+
|
|
282
|
+ case HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE: {
|
|
283
|
+ bd_addr_t addr;
|
|
284
|
+ reverse_bd_addr(&packet[3], addr);
|
|
285
|
+ if (packet[2] != 0) {
|
|
286
|
+ debug("page timeout receiving name from %s", bd_addr_to_str(addr));
|
|
287
|
+ } else {
|
|
288
|
+ for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
|
|
289
|
+ if (!scans[i].set) {
|
|
290
|
+ continue;
|
|
291
|
+ }
|
|
292
|
+ if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) == 0) {
|
|
293
|
+ scans[i].state = BLE_NAME_FETCHED;
|
|
294
|
+ // also not the name we are looking for
|
|
295
|
+ hci_scan_result_add_name(addr, &packet[9], strlen((char *)&packet[9]));
|
|
296
|
+ break;
|
|
297
|
+ }
|
|
298
|
+ }
|
|
299
|
+ }
|
|
300
|
+ hci_continue_name_requests();
|
80
|
301
|
break;
|
|
302
|
+ }
|
81
|
303
|
|
82
|
304
|
case HCI_EVENT_LE_META:
|
83
|
305
|
switch (hci_event_le_meta_get_subevent_code(packet)) {
|
84
|
306
|
case HCI_SUBEVENT_LE_ADVERTISING_REPORT:
|
|
307
|
+ // handled internally by BTstack
|
85
|
308
|
break;
|
86
|
309
|
|
87
|
310
|
case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
|
|
@@ -101,12 +324,18 @@ static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *pa
|
101
|
324
|
}
|
102
|
325
|
|
103
|
326
|
void ble_init(void) {
|
|
327
|
+ for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
|
|
328
|
+ scans[i].set = false;
|
|
329
|
+ }
|
|
330
|
+
|
104
|
331
|
l2cap_init();
|
105
|
332
|
sm_init();
|
106
|
333
|
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
|
107
|
334
|
|
108
|
335
|
gatt_client_init();
|
109
|
336
|
|
|
337
|
+ hci_set_inquiry_mode(INQUIRY_MODE_RSSI_AND_EIR);
|
|
338
|
+
|
110
|
339
|
hci_event_callback_registration.callback = &hci_event_handler;
|
111
|
340
|
hci_add_event_handler(&hci_event_callback_registration);
|
112
|
341
|
|
|
@@ -114,29 +343,38 @@ void ble_init(void) {
|
114
|
343
|
}
|
115
|
344
|
|
116
|
345
|
void ble_scan(enum ble_scan_mode mode) {
|
|
346
|
+ cyw43_thread_enter();
|
|
347
|
+
|
117
|
348
|
switch (mode) {
|
118
|
349
|
case BLE_SCAN_OFF:
|
119
|
350
|
debug("stopping BLE scan");
|
120
|
351
|
state = TC_IDLE;
|
121
|
352
|
gap_stop_scan();
|
|
353
|
+ gap_inquiry_stop();
|
122
|
354
|
break;
|
123
|
355
|
|
124
|
356
|
case BLE_SCAN_ON:
|
125
|
357
|
debug("starting BLE scan");
|
126
|
358
|
state = TC_W4_SCAN_RESULT;
|
|
359
|
+
|
127
|
360
|
gap_set_scan_parameters(0,0x0030, 0x0030);
|
128
|
361
|
gap_start_scan();
|
|
362
|
+
|
|
363
|
+ // also start an inquiry scan
|
|
364
|
+ gap_inquiry_start(GAP_INQUIRY_INTERVAL);
|
129
|
365
|
break;
|
130
|
366
|
|
131
|
367
|
case BLE_SCAN_TOGGLE:
|
132
|
368
|
switch (state) {
|
133
|
369
|
case TC_W4_SCAN_RESULT:
|
|
370
|
+ cyw43_thread_exit();
|
134
|
371
|
ble_scan(0);
|
135
|
|
- break;
|
|
372
|
+ return;
|
136
|
373
|
|
137
|
374
|
case TC_IDLE:
|
|
375
|
+ cyw43_thread_exit();
|
138
|
376
|
ble_scan(1);
|
139
|
|
- break;
|
|
377
|
+ return;
|
140
|
378
|
|
141
|
379
|
default:
|
142
|
380
|
debug("invalid state %d", state);
|
|
@@ -148,4 +386,31 @@ void ble_scan(enum ble_scan_mode mode) {
|
148
|
386
|
debug("invalid mode %d", mode);
|
149
|
387
|
break;
|
150
|
388
|
}
|
|
389
|
+
|
|
390
|
+ cyw43_thread_exit();
|
|
391
|
+}
|
|
392
|
+
|
|
393
|
+int ble_get_scan_results(struct ble_scan_result *buf, uint len) {
|
|
394
|
+ if (!buf || (len <= 0)) {
|
|
395
|
+ return -1;
|
|
396
|
+ }
|
|
397
|
+
|
|
398
|
+ cyw43_thread_enter();
|
|
399
|
+
|
|
400
|
+ uint pos = 0;
|
|
401
|
+ for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
|
|
402
|
+ if (!scans[i].set) {
|
|
403
|
+ continue;
|
|
404
|
+ }
|
|
405
|
+
|
|
406
|
+ memcpy(buf + pos, scans + i, sizeof(struct ble_scan_result));
|
|
407
|
+ pos++;
|
|
408
|
+
|
|
409
|
+ if (pos >= len) {
|
|
410
|
+ break;
|
|
411
|
+ }
|
|
412
|
+ }
|
|
413
|
+
|
|
414
|
+ cyw43_thread_exit();
|
|
415
|
+ return pos;
|
151
|
416
|
}
|