S&B Volcano vaporizer remote control with Pi Pico W
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ble.c 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. /*
  2. * ble.c
  3. *
  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. *
  7. * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * See <http://www.gnu.org/licenses/>.
  20. */
  21. #include "pico/cyw43_arch.h"
  22. #include "hardware/watchdog.h"
  23. #include "config.h"
  24. #include "log.h"
  25. #include "main.h"
  26. #include "util.h"
  27. #include "ble.h"
  28. #define BLE_READ_TIMEOUT_MS (2 * 500)
  29. #define BLE_SRVC_TIMEOUT_MS (2 * 500)
  30. #define BLE_CHAR_TIMEOUT_MS (2 * 2000)
  31. #define BLE_WRTE_TIMEOUT_MS (2 * 500)
  32. #define BLE_MAX_SCAN_AGE_MS (10 * 1000)
  33. #define BLE_MAX_SERVICES 8
  34. #define BLE_MAX_CHARACTERISTICS 8
  35. enum ble_state {
  36. TC_OFF = 0,
  37. TC_IDLE,
  38. TC_W4_SCAN,
  39. TC_W4_CONNECT,
  40. TC_READY,
  41. TC_W4_READ,
  42. TC_READ_COMPLETE,
  43. TC_W4_SERVICE,
  44. TC_W4_CHARACTERISTIC,
  45. TC_W4_WRITE,
  46. TC_WRITE_COMPLETE,
  47. };
  48. struct ble_characteristic {
  49. bool set;
  50. gatt_client_characteristic_t c;
  51. };
  52. struct ble_service {
  53. bool set;
  54. gatt_client_service_t service;
  55. struct ble_characteristic chars[BLE_MAX_CHARACTERISTICS];
  56. };
  57. static btstack_packet_callback_registration_t hci_event_callback_registration;
  58. static hci_con_handle_t connection_handle;
  59. static enum ble_state state = TC_OFF;
  60. static struct ble_scan_result scans[BLE_MAX_SCAN_RESULTS] = {0};
  61. static uint16_t read_len = 0;
  62. static uint8_t data_buff[BLE_MAX_VALUE_LEN] = {0};
  63. static struct ble_service services[BLE_MAX_SERVICES] = {0};
  64. static uint8_t service_idx = 0;
  65. static uint8_t characteristic_idx = 0;
  66. static void hci_add_scan_result(bd_addr_t addr, bd_addr_type_t type, int8_t rssi) {
  67. int unused = -1;
  68. for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
  69. if (!scans[i].set) {
  70. if (unused < 0) {
  71. unused = i;
  72. }
  73. continue;
  74. }
  75. if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) == 0) {
  76. // already in list, just update changing values
  77. scans[i].time = to_ms_since_boot(get_absolute_time());
  78. scans[i].rssi = rssi;
  79. return;
  80. }
  81. }
  82. if (unused < 0) {
  83. debug("no space in scan results for %s", bd_addr_to_str(addr));
  84. return;
  85. }
  86. debug("new device with addr %s", bd_addr_to_str(addr));
  87. scans[unused].set = true;
  88. scans[unused].time = to_ms_since_boot(get_absolute_time());
  89. memcpy(scans[unused].addr, addr, sizeof(bd_addr_t));
  90. scans[unused].type = type;
  91. scans[unused].rssi = rssi;
  92. scans[unused].name[0] = '\0';
  93. scans[unused].data_len = 0;
  94. }
  95. static void hci_scan_result_add_name(bd_addr_t addr, const uint8_t *data, uint8_t data_size) {
  96. for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
  97. if (!scans[i].set) {
  98. continue;
  99. }
  100. if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) != 0) {
  101. continue;
  102. }
  103. uint8_t len = data_size;
  104. if (len > BLE_MAX_NAME_LENGTH) {
  105. len = BLE_MAX_NAME_LENGTH;
  106. }
  107. memcpy(scans[i].name, data, len);
  108. scans[i].name[len] = '\0';
  109. scans[i].time = to_ms_since_boot(get_absolute_time());
  110. return;
  111. }
  112. debug("no matching entry for %s to add name '%.*s' to", bd_addr_to_str(addr), data_size, data);
  113. }
  114. static void hci_scan_result_add_data(bd_addr_t addr, const uint8_t *data, uint8_t data_size) {
  115. for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
  116. if (!scans[i].set) {
  117. continue;
  118. }
  119. if (memcmp(addr, scans[i].addr, sizeof(bd_addr_t)) != 0) {
  120. continue;
  121. }
  122. uint8_t len = data_size;
  123. if (len > BLE_MAX_DATA_LENGTH) {
  124. len = BLE_MAX_DATA_LENGTH;
  125. }
  126. memcpy(scans[i].data, data, len);
  127. scans[i].data_len = len;
  128. scans[i].time = to_ms_since_boot(get_absolute_time());
  129. return;
  130. }
  131. debug("no matching entry for %s to add data to", bd_addr_to_str(addr));
  132. }
  133. static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
  134. UNUSED(size);
  135. UNUSED(channel);
  136. //debug("type=0x%02X size=%d", packet_type, size);
  137. //hexdump(packet, size);
  138. if (packet_type != HCI_EVENT_PACKET) {
  139. //debug("unexpected packet 0x%02X", packet_type);
  140. return;
  141. }
  142. switch (hci_event_packet_get_type(packet)) {
  143. case BTSTACK_EVENT_STATE:
  144. if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) {
  145. bd_addr_t local_addr;
  146. gap_local_bd_addr(local_addr);
  147. debug("BTstack up with %s", bd_addr_to_str(local_addr));
  148. state = TC_IDLE;
  149. } else {
  150. debug("BTstack down (%d)", btstack_event_state_get_state(packet));
  151. state = TC_OFF;
  152. }
  153. break;
  154. case GAP_EVENT_ADVERTISING_REPORT: {
  155. if (state != TC_W4_SCAN) {
  156. debug("scan result in invalid state %d", state);
  157. return;
  158. }
  159. bd_addr_t addr;
  160. gap_event_advertising_report_get_address(packet, addr);
  161. bd_addr_type_t type;
  162. type = gap_event_advertising_report_get_address_type(packet);
  163. int8_t rssi;
  164. rssi = (int8_t)gap_event_advertising_report_get_rssi(packet);
  165. // add data received so far
  166. hci_add_scan_result(addr, type, rssi);
  167. // get advertisement from report event
  168. const uint8_t *adv_data = gap_event_advertising_report_get_data(packet);
  169. uint8_t adv_len = gap_event_advertising_report_get_data_length(packet);
  170. // iterate over advertisement data
  171. ad_context_t context;
  172. for (ad_iterator_init(&context, adv_len, adv_data);
  173. ad_iterator_has_more(&context);
  174. ad_iterator_next(&context)) {
  175. uint8_t data_type = ad_iterator_get_data_type(&context);
  176. uint8_t data_size = ad_iterator_get_data_len(&context);
  177. const uint8_t *data = ad_iterator_get_data(&context);
  178. switch (data_type) {
  179. case BLUETOOTH_DATA_TYPE_SHORTENED_LOCAL_NAME:
  180. case BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME:
  181. hci_scan_result_add_name(addr, data, data_size);
  182. break;
  183. case BLUETOOTH_DATA_TYPE_SERVICE_DATA:
  184. case BLUETOOTH_DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
  185. // TODO ugly
  186. if (data_size == 12) {
  187. // Crafty+
  188. hci_scan_result_add_data(addr, data, data_size);
  189. } else if (data_size == 26) {
  190. // Volcano
  191. hci_scan_result_add_data(addr, data, data_size);
  192. }
  193. break;
  194. default:
  195. //debug("Unexpected advertisement type 0x%02X from %s", data_type, bd_addr_to_str(addr));
  196. //hexdump(data, data_size);
  197. break;
  198. }
  199. }
  200. break;
  201. }
  202. case HCI_EVENT_LE_META:
  203. switch (hci_event_le_meta_get_subevent_code(packet)) {
  204. case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
  205. if (state != TC_W4_CONNECT) {
  206. return;
  207. }
  208. debug("connection complete");
  209. connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
  210. state = TC_READY;
  211. break;
  212. default:
  213. //debug("unexpected LE meta event 0x%02X", hci_event_le_meta_get_subevent_code(packet));
  214. break;
  215. }
  216. break;
  217. case HCI_EVENT_DISCONNECTION_COMPLETE:
  218. debug("disconnected");
  219. connection_handle = HCI_CON_HANDLE_INVALID;
  220. state = TC_IDLE;
  221. break;
  222. case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
  223. if (state != TC_W4_READ) {
  224. debug("gatt value query result in invalid state %d", state);
  225. return;
  226. }
  227. uint16_t len = gatt_event_characteristic_value_query_result_get_value_length(packet);
  228. if ((read_len + len) > BLE_MAX_VALUE_LEN) {
  229. debug("not enough space for value (%d + %d > %d)", read_len, len, BLE_MAX_VALUE_LEN);
  230. return;
  231. }
  232. memcpy(data_buff + read_len,
  233. gatt_event_characteristic_value_query_result_get_value(packet),
  234. len);
  235. read_len += len;
  236. break;
  237. case GATT_EVENT_SERVICE_QUERY_RESULT:
  238. if (state != TC_W4_SERVICE) {
  239. debug("gatt service query result in invalid state %d", state);
  240. return;
  241. }
  242. gatt_event_service_query_result_get_service(packet, &services[service_idx].service);
  243. //debug("got service %s result", uuid128_to_str(services[service_idx].service.uuid128));
  244. break;
  245. case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
  246. if (state != TC_W4_CHARACTERISTIC) {
  247. debug("gatt characteristic query result in invalid state %d", state);
  248. return;
  249. }
  250. gatt_event_characteristic_query_result_get_characteristic(packet, &services[service_idx].chars[characteristic_idx].c);
  251. //debug("got characteristic %s result", uuid128_to_str(services[service_idx].chars[characteristic_idx].c.uuid128));
  252. break;
  253. case GATT_EVENT_QUERY_COMPLETE: {
  254. uint8_t att_status = gatt_event_query_complete_get_att_status(packet);
  255. if (att_status != ATT_ERROR_SUCCESS){
  256. debug("query result has ATT Error 0x%02x in %d", att_status, state);
  257. state = TC_READY;
  258. break;
  259. }
  260. switch (state) {
  261. case TC_W4_READ:
  262. state = TC_READ_COMPLETE;
  263. break;
  264. case TC_W4_SERVICE:
  265. //debug("service %s complete", uuid128_to_str(services[service_idx].service.uuid128));
  266. state = TC_READY;
  267. break;
  268. case TC_W4_CHARACTERISTIC:
  269. //debug("characteristic %s complete", uuid128_to_str(services[service_idx].chars[characteristic_idx].c.uuid128));
  270. state = TC_READY;
  271. break;
  272. case TC_W4_WRITE:
  273. //debug("write complete");
  274. state = TC_WRITE_COMPLETE;
  275. break;
  276. default:
  277. debug("gatt query complete in invalid state %d", state);
  278. break;
  279. }
  280. break;
  281. }
  282. default:
  283. //debug("unexpected event 0x%02X", hci_event_packet_get_type(packet));
  284. break;
  285. }
  286. }
  287. void ble_init(void) {
  288. cyw43_thread_enter();
  289. state = TC_OFF;
  290. for (uint i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
  291. scans[i].set = false;
  292. }
  293. for (uint i = 0; i < BLE_MAX_SERVICES; i++) {
  294. services[i].set = false;
  295. for (uint j = 0; j < BLE_MAX_CHARACTERISTICS; j++) {
  296. services[i].chars[j].set = false;
  297. }
  298. }
  299. cyw43_thread_exit();
  300. l2cap_init();
  301. sm_init();
  302. sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
  303. gatt_client_init();
  304. hci_event_callback_registration.callback = &hci_event_handler;
  305. hci_add_event_handler(&hci_event_callback_registration);
  306. hci_power_control(HCI_POWER_ON);
  307. }
  308. bool ble_is_ready(void) {
  309. cyw43_thread_enter();
  310. bool v = (state != TC_OFF);
  311. cyw43_thread_exit();
  312. return v;
  313. }
  314. void ble_scan(enum ble_scan_mode mode) {
  315. cyw43_thread_enter();
  316. if (state == TC_OFF) {
  317. cyw43_thread_exit();
  318. return;
  319. }
  320. switch (mode) {
  321. case BLE_SCAN_OFF:
  322. debug("stopping BLE scan");
  323. gap_stop_scan();
  324. state = TC_IDLE;
  325. break;
  326. case BLE_SCAN_ON:
  327. debug("starting BLE scan");
  328. state = TC_W4_SCAN;
  329. gap_set_scan_parameters(1, 0x0030, 0x0030);
  330. gap_start_scan();
  331. break;
  332. case BLE_SCAN_TOGGLE:
  333. switch (state) {
  334. case TC_W4_SCAN:
  335. cyw43_thread_exit();
  336. ble_scan(0);
  337. return;
  338. case TC_IDLE:
  339. cyw43_thread_exit();
  340. ble_scan(1);
  341. return;
  342. default:
  343. debug("invalid state %d", state);
  344. break;
  345. }
  346. break;
  347. default:
  348. debug("invalid mode %d", mode);
  349. break;
  350. }
  351. cyw43_thread_exit();
  352. }
  353. int32_t ble_get_scan_results(struct ble_scan_result *buf, uint16_t len) {
  354. if (!buf || (len <= 0)) {
  355. return -1;
  356. }
  357. cyw43_thread_enter();
  358. if (state == TC_OFF) {
  359. cyw43_thread_exit();
  360. return -1;
  361. }
  362. uint16_t pos = 0;
  363. for (uint16_t i = 0; i < BLE_MAX_SCAN_RESULTS; i++) {
  364. if (!scans[i].set) {
  365. continue;
  366. }
  367. // only age out entries while scanning, otherwise keep results cached
  368. if (state == TC_W4_SCAN) {
  369. uint32_t diff = to_ms_since_boot(get_absolute_time()) - scans[i].time;
  370. if (diff >= BLE_MAX_SCAN_AGE_MS) {
  371. //debug("removing %s due to age", bd_addr_to_str(scans[i].addr));
  372. scans[i].set = false;
  373. }
  374. }
  375. memcpy(buf + pos, scans + i, sizeof(struct ble_scan_result));
  376. pos++;
  377. if (pos >= len) {
  378. break;
  379. }
  380. }
  381. cyw43_thread_exit();
  382. return pos;
  383. }
  384. void ble_connect(bd_addr_t addr, bd_addr_type_t type) {
  385. cyw43_thread_enter();
  386. switch (state) {
  387. case TC_W4_SCAN:
  388. cyw43_thread_exit();
  389. ble_scan(0);
  390. cyw43_thread_enter();
  391. break;
  392. case TC_READY:
  393. gap_disconnect(connection_handle);
  394. break;
  395. case TC_IDLE:
  396. break;
  397. default:
  398. debug("invalid state for connect %d", state);
  399. cyw43_thread_exit();
  400. return;
  401. }
  402. debug("connecting to %s", bd_addr_to_str(addr));
  403. state = TC_W4_CONNECT;
  404. gap_connect(addr, type);
  405. cyw43_thread_exit();
  406. }
  407. bool ble_is_connected(void) {
  408. cyw43_thread_enter();
  409. bool v = (state == TC_READY)
  410. || (state == TC_W4_READ)
  411. || (state == TC_READ_COMPLETE)
  412. || (state == TC_W4_SERVICE)
  413. || (state == TC_W4_CHARACTERISTIC)
  414. || (state == TC_W4_WRITE)
  415. || (state == TC_WRITE_COMPLETE);
  416. cyw43_thread_exit();
  417. return v;
  418. }
  419. void ble_disconnect(void) {
  420. cyw43_thread_enter();
  421. if (state == TC_READY) {
  422. debug("disconnecting");
  423. gap_disconnect(connection_handle);
  424. } else {
  425. debug("invalid state for disconnect %d", state);
  426. }
  427. cyw43_thread_exit();
  428. }
  429. int32_t ble_read(const uint8_t *characteristic, uint8_t *buff, uint16_t buff_len) {
  430. cyw43_thread_enter();
  431. if (state != TC_READY) {
  432. cyw43_thread_exit();
  433. debug("invalid state for read (%d)", state);
  434. return -1;
  435. }
  436. uint8_t r = gatt_client_read_value_of_characteristics_by_uuid128(hci_event_handler,
  437. connection_handle,
  438. 0x0001, 0xFFFF,
  439. characteristic);
  440. if (r != ERROR_CODE_SUCCESS) {
  441. cyw43_thread_exit();
  442. debug("gatt read failed %d", r);
  443. return -2;
  444. }
  445. state = TC_W4_READ;
  446. read_len = 0;
  447. cyw43_thread_exit();
  448. uint32_t start_time = to_ms_since_boot(get_absolute_time());
  449. while (1) {
  450. sleep_ms(1);
  451. main_loop_hw();
  452. uint32_t now = to_ms_since_boot(get_absolute_time());
  453. if ((now - start_time) >= BLE_READ_TIMEOUT_MS) {
  454. debug("timeout waiting for read");
  455. cyw43_thread_enter();
  456. state = TC_READY;
  457. cyw43_thread_exit();
  458. return -3;
  459. }
  460. cyw43_thread_enter();
  461. enum ble_state state_cached = state;
  462. cyw43_thread_exit();
  463. if (state_cached == TC_READ_COMPLETE) {
  464. break;
  465. }
  466. }
  467. cyw43_thread_enter();
  468. state = TC_READY;
  469. if (read_len > buff_len) {
  470. debug("buffer too short (%d < %d)", buff_len, read_len);
  471. cyw43_thread_exit();
  472. return -4;
  473. }
  474. memcpy(buff, data_buff, read_len);
  475. cyw43_thread_exit();
  476. return read_len;
  477. }
  478. int8_t ble_write(const uint8_t *service, const uint8_t *characteristic,
  479. uint8_t *buff, uint16_t buff_len) {
  480. cyw43_thread_enter();
  481. if (state != TC_READY) {
  482. cyw43_thread_exit();
  483. debug("invalid state for write (%d)", state);
  484. return -1;
  485. }
  486. // check if service has already been discovered
  487. int srvc = -1, free_srvc = -1;
  488. for (int i = 0; i < BLE_MAX_SERVICES; i++) {
  489. if (!services[i].set) {
  490. if (free_srvc < 0) {
  491. free_srvc = i;
  492. }
  493. continue;
  494. }
  495. if (memcmp(services[i].service.uuid128, service, 16) == 0) {
  496. srvc = i;
  497. break;
  498. }
  499. }
  500. // if this service has not been discovered yet, add it
  501. if (srvc < 0) {
  502. if (free_srvc < 0) {
  503. debug("no space left for BLE service. overwriting.");
  504. free_srvc = 0;
  505. }
  506. srvc = free_srvc;
  507. services[srvc].set = true;
  508. debug("discovering service %s at %d", uuid128_to_str(service), srvc);
  509. uint8_t r = gatt_client_discover_primary_services_by_uuid128(hci_event_handler,
  510. connection_handle,
  511. service);
  512. if (r != ERROR_CODE_SUCCESS) {
  513. cyw43_thread_exit();
  514. debug("gatt service discovery failed %d", r);
  515. return -2;
  516. }
  517. state = TC_W4_SERVICE;
  518. service_idx = srvc;
  519. cyw43_thread_exit();
  520. uint32_t start_time = to_ms_since_boot(get_absolute_time());
  521. while (1) {
  522. sleep_ms(1);
  523. main_loop_hw();
  524. uint32_t now = to_ms_since_boot(get_absolute_time());
  525. if ((now - start_time) >= BLE_SRVC_TIMEOUT_MS) {
  526. debug("timeout waiting for service");
  527. cyw43_thread_enter();
  528. state = TC_READY;
  529. cyw43_thread_exit();
  530. return -3;
  531. }
  532. cyw43_thread_enter();
  533. enum ble_state state_cached = state;
  534. cyw43_thread_exit();
  535. if (state_cached == TC_READY) {
  536. break;
  537. }
  538. }
  539. cyw43_thread_enter();
  540. }
  541. // check if characteristic has already been discovered
  542. int ch = -1, free_ch = -1;
  543. for (int i = 0; i < BLE_MAX_CHARACTERISTICS; i++) {
  544. if (!services[srvc].chars[i].set) {
  545. if (free_ch < 0) {
  546. free_ch = i;
  547. }
  548. continue;
  549. }
  550. if (memcmp(services[srvc].chars[i].c.uuid128, characteristic, 16) == 0) {
  551. ch = i;
  552. break;
  553. }
  554. }
  555. // if this characteristic has not been discovered yet, add it
  556. if (ch < 0) {
  557. if (free_ch < 0) {
  558. debug("no space left for BLE characteristic. overwriting.");
  559. free_ch = 0;
  560. }
  561. ch = free_ch;
  562. services[srvc].chars[ch].set = true;
  563. debug("discovering characteristic %s at %d", uuid128_to_str(characteristic), ch);
  564. uint8_t r = gatt_client_discover_characteristics_for_service_by_uuid128(hci_event_handler,
  565. connection_handle,
  566. &services[srvc].service,
  567. characteristic);
  568. if (r != ERROR_CODE_SUCCESS) {
  569. cyw43_thread_exit();
  570. debug("gatt characteristic discovery failed %d", r);
  571. return -4;
  572. }
  573. state = TC_W4_CHARACTERISTIC;
  574. characteristic_idx = ch;
  575. cyw43_thread_exit();
  576. uint32_t start_time = to_ms_since_boot(get_absolute_time());
  577. while (1) {
  578. sleep_ms(1);
  579. main_loop_hw();
  580. uint32_t now = to_ms_since_boot(get_absolute_time());
  581. if ((now - start_time) >= BLE_CHAR_TIMEOUT_MS) {
  582. debug("timeout waiting for characteristic");
  583. cyw43_thread_enter();
  584. state = TC_READY;
  585. cyw43_thread_exit();
  586. return -5;
  587. }
  588. cyw43_thread_enter();
  589. enum ble_state state_cached = state;
  590. cyw43_thread_exit();
  591. if (state_cached == TC_READY) {
  592. break;
  593. }
  594. }
  595. cyw43_thread_enter();
  596. }
  597. if (buff_len > BLE_MAX_VALUE_LEN) {
  598. buff_len = BLE_MAX_VALUE_LEN;
  599. }
  600. memcpy(data_buff, buff, buff_len);
  601. uint8_t r = gatt_client_write_value_of_characteristic(hci_event_handler,
  602. connection_handle,
  603. services[srvc].chars[ch].c.value_handle,
  604. buff_len, data_buff);
  605. if (r != ERROR_CODE_SUCCESS) {
  606. cyw43_thread_exit();
  607. debug("gatt write failed %d", r);
  608. return -6;
  609. }
  610. state = TC_W4_WRITE;
  611. cyw43_thread_exit();
  612. uint32_t start_time = to_ms_since_boot(get_absolute_time());
  613. while (1) {
  614. sleep_ms(1);
  615. main_loop_hw();
  616. uint32_t now = to_ms_since_boot(get_absolute_time());
  617. if ((now - start_time) >= BLE_WRTE_TIMEOUT_MS) {
  618. debug("timeout waiting for write");
  619. cyw43_thread_enter();
  620. state = TC_READY;
  621. cyw43_thread_exit();
  622. return -7;
  623. }
  624. cyw43_thread_enter();
  625. enum ble_state state_cached = state;
  626. cyw43_thread_exit();
  627. if ((state_cached == TC_WRITE_COMPLETE) || (state_cached == TC_READY)) {
  628. break;
  629. }
  630. }
  631. cyw43_thread_enter();
  632. int8_t ret = (state == TC_WRITE_COMPLETE) ? 0 : -8;
  633. state = TC_READY;
  634. cyw43_thread_exit();
  635. return ret;
  636. }