diff --git a/app/include/zmk/split/bluetooth/central.h b/app/include/zmk/split/bluetooth/central.h index 5e9e09ff..c609aa3b 100644 --- a/app/include/zmk/split/bluetooth/central.h +++ b/app/include/zmk/split/bluetooth/central.h @@ -3,22 +3,14 @@ #include #include - -#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) -#include -#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) +#include int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event, bool state); -#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) - -int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators); - -#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) - #if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) int zmk_split_get_peripheral_battery_level(uint8_t source, uint8_t *level); -#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) \ No newline at end of file +#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) +int zmk_split_central_send_data(enum data_tag, uint8_t data_size, uint8_t *data); diff --git a/app/include/zmk/split/bluetooth/service.h b/app/include/zmk/split/bluetooth/service.h index 112cd552..c7b9008f 100644 --- a/app/include/zmk/split/bluetooth/service.h +++ b/app/include/zmk/split/bluetooth/service.h @@ -10,6 +10,20 @@ #include #define ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN 9 +#define ZMK_SPLIT_DATA_XFER_MAX_LEN 16 + +enum data_tag { + // RGB state + DATA_TAG_RGB_STATE, + // Backlight state + DATA_TAG_BACKLIGHT_STATE, + // HID indicators state + DATA_TAG_HID_INDICATORS_STATE, + // Keymap state + DATA_TAG_KEYMAP_STATE, + // Start of custom tags + DATA_TAG_CUSTOM_START, +}; struct sensor_event { uint8_t sensor_index; @@ -30,6 +44,12 @@ struct zmk_split_run_behavior_payload { char behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN]; } __packed; +struct zmk_split_data_xfer_data { + enum data_tag data_tag; + uint8_t data_size; + uint8_t data[ZMK_SPLIT_DATA_XFER_MAX_LEN]; +} __packed; + int zmk_split_bt_position_pressed(uint8_t position); int zmk_split_bt_position_released(uint8_t position); int zmk_split_bt_sensor_triggered(uint8_t sensor_index, diff --git a/app/include/zmk/split/bluetooth/uuid.h b/app/include/zmk/split/bluetooth/uuid.h index dccdfc80..737a043f 100644 --- a/app/include/zmk/split/bluetooth/uuid.h +++ b/app/include/zmk/split/bluetooth/uuid.h @@ -17,4 +17,4 @@ #define ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID ZMK_BT_SPLIT_UUID(0x00000001) #define ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID ZMK_BT_SPLIT_UUID(0x00000002) #define ZMK_SPLIT_BT_CHAR_SENSOR_STATE_UUID ZMK_BT_SPLIT_UUID(0x00000003) -#define ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID ZMK_BT_SPLIT_UUID(0x00000004) +#define ZMK_SPLIT_BT_CHAR_DATA_XFER_UUID ZMK_BT_SPLIT_UUID(0x00000004) diff --git a/app/src/split/bluetooth/Kconfig b/app/src/split/bluetooth/Kconfig index 4da50528..e6f34f27 100644 --- a/app/src/split/bluetooth/Kconfig +++ b/app/src/split/bluetooth/Kconfig @@ -58,6 +58,14 @@ config ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_QUEUE_SIZE int "Max number of behavior run events to queue to send to the peripheral(s)" default 5 +config ZMK_SPLIT_BLE_CENTRAL_DATA_XFER_STACK_SIZE + int "BLE split central to peripheral thread stack size" + default 512 + +config ZMK_SPLIT_BLE_CENTRAL_DATA_XFER_QUEUE_SIZE + int "Max number of data transfer requests to queue to send to the peripheral(s)" + default 5 + config ZMK_SPLIT_BLE_PREF_INT int "Connection interval to use for split central/peripheral connection" default 6 diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 61ec4e8d..5acaf6b2 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -28,7 +28,6 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include -#include #include static int start_scanning(void); @@ -53,9 +52,7 @@ struct peripheral_slot { struct bt_gatt_subscribe_params batt_lvl_subscribe_params; struct bt_gatt_read_params batt_lvl_read_params; #endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ -#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) - uint16_t update_hid_indicators; -#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) + uint16_t data_xfer_handle; uint8_t position_state[POSITION_STATE_DATA_LEN]; uint8_t changed_positions[POSITION_STATE_DATA_LEN]; }; @@ -141,9 +138,7 @@ int release_peripheral_slot(int index) { // Clean up previously discovered handles; slot->subscribe_params.value_handle = 0; slot->run_behavior_handle = 0; -#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) - slot->update_hid_indicators = 0; -#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) + slot->data_xfer_handle = 0; return 0; } @@ -442,12 +437,7 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, slot->discover_params.uuid = NULL; slot->discover_params.start_handle = attr->handle + 2; slot->run_behavior_handle = bt_gatt_attr_value_handle(attr); -#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) - } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, - BT_UUID_DECLARE_128(ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID))) { - LOG_DBG("Found update HID indicators handle"); - slot->update_hid_indicators = bt_gatt_attr_value_handle(attr); -#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) + #if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, BT_UUID_BAS_BATTERY_LEVEL)) { @@ -469,13 +459,36 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, bool subscribed = slot->run_behavior_handle && slot->subscribe_params.value_handle; + } else if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_DATA_XFER_UUID)) == 0) { + LOG_DBG("Found data transfer handle"); + slot->discover_params.uuid = NULL; + slot->discover_params.start_handle = attr->handle + 2; + slot->data_xfer_handle = bt_gatt_attr_value_handle(attr); +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, + BT_UUID_BAS_BATTERY_LEVEL)) { + LOG_DBG("Found battery level characteristics"); + slot->batt_lvl_subscribe_params.disc_params = &slot->sub_discover_params; + slot->batt_lvl_subscribe_params.end_handle = slot->discover_params.end_handle; + slot->batt_lvl_subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); + slot->batt_lvl_subscribe_params.notify = split_central_battery_level_notify_func; + slot->batt_lvl_subscribe_params.value = BT_GATT_CCC_NOTIFY; + split_central_subscribe(conn, &slot->batt_lvl_subscribe_params); + + slot->batt_lvl_read_params.func = split_central_battery_level_read_func; + slot->batt_lvl_read_params.handle_count = 1; + slot->batt_lvl_read_params.single.handle = bt_gatt_attr_value_handle(attr); + slot->batt_lvl_read_params.single.offset = 0; + bt_gatt_read(conn, &slot->batt_lvl_read_params); +#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ + } + + bool subscribed = + slot->run_behavior_handle && slot->data_xfer_handle && slot->subscribe_params.value_handle; #if ZMK_KEYMAP_HAS_SENSORS subscribed = subscribed && slot->sensor_subscribe_params.value_handle; #endif /* ZMK_KEYMAP_HAS_SENSORS */ -#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) - subscribed = subscribed && slot->update_hid_indicators; -#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) #if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) subscribed = subscribed && slot->batt_lvl_subscribe_params.value_handle; #endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ @@ -833,47 +846,85 @@ int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *bi return split_bt_invoke_behavior_payload(wrapper); } -#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) +K_THREAD_STACK_DEFINE(split_central_data_xfer_q_stack, + CONFIG_ZMK_SPLIT_BLE_CENTRAL_DATA_XFER_STACK_SIZE); -static zmk_hid_indicators_t hid_indicators = 0; +struct k_work_q split_central_data_xfer_q; -static void split_central_update_indicators_callback(struct k_work *work) { - zmk_hid_indicators_t indicators = hid_indicators; - for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) { - if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED) { - continue; - } +K_MSGQ_DEFINE(zmk_split_central_data_xfer_msgq, sizeof(struct zmk_split_data_xfer_data), + CONFIG_ZMK_SPLIT_BLE_CENTRAL_DATA_XFER_QUEUE_SIZE, 2); - if (peripherals[i].update_hid_indicators == 0) { - // It appears that sometimes the peripheral is considered connected - // before the GATT characteristics have been discovered. If this is - // the case, the update_hid_indicators handle will not yet be set. - continue; - } +void split_central_data_xfer_callback(struct k_work *work) { + struct zmk_split_data_xfer_data payload; - int err = bt_gatt_write_without_response(peripherals[i].conn, - peripherals[i].update_hid_indicators, &indicators, - sizeof(indicators), true); + while (k_msgq_get(&zmk_split_central_data_xfer_msgq, &payload, K_NO_WAIT) == 0) { + for (uint8_t i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) { + if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED) { + LOG_ERR("Source not connected"); + continue; + } - if (err) { - LOG_ERR("Failed to write HID indicator characteristic (err %d)", err); + if (!peripherals[i].data_xfer_handle) { + LOG_ERR("Handle not discovered"); + continue; + } + + int err = bt_gatt_write_without_response(peripherals[i].conn, + peripherals[i].data_xfer_handle, &payload, + sizeof(struct zmk_split_data_xfer_data), true); + + if (err) { + LOG_ERR("Failed to write the data transfer characteristic (err %d)", err); + } } } } -static K_WORK_DEFINE(split_central_update_indicators, split_central_update_indicators_callback); +K_WORK_DEFINE(split_central_data_xfer_work, split_central_data_xfer_callback); -int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators) { - hid_indicators = indicators; - return k_work_submit_to_queue(&split_central_split_run_q, &split_central_update_indicators); +static int split_bt_data_xfer_payload(struct zmk_split_data_xfer_data payload) { + LOG_DBG(""); + + int err = k_msgq_put(&zmk_split_central_data_xfer_msgq, &payload, K_MSEC(100)); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("Consumer message queue full, popping first message and queueing again"); + struct zmk_split_data_xfer_data discarded_report; + k_msgq_get(&zmk_split_central_data_xfer_msgq, &discarded_report, K_NO_WAIT); + return split_bt_data_xfer_payload(payload); + } + default: + LOG_WRN("Failed to queue data to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&split_central_data_xfer_q, &split_central_data_xfer_work); + + return 0; +}; + +int zmk_split_central_send_data(enum data_tag tag, uint8_t size, uint8_t *data) { + if (size > ZMK_SPLIT_DATA_XFER_MAX_LEN) { + LOG_ERR("Payload too large. Size: %d", size); + return -EFBIG; + } + struct zmk_split_data_xfer_data payload; + payload.data_tag = tag; + payload.data_size = size; + memcpy(payload.data, data, size); + LOG_HEXDUMP_DBG(&payload, sizeof(struct zmk_split_data_xfer_data), "sending :"); + return split_bt_data_xfer_payload(payload); } -#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) - -static int zmk_split_bt_central_init(void) { +int zmk_split_bt_central_init(const struct device *_arg) { k_work_queue_start(&split_central_split_run_q, split_central_split_run_q_stack, K_THREAD_STACK_SIZEOF(split_central_split_run_q_stack), CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL); + k_work_queue_start(&split_central_data_xfer_q, split_central_data_xfer_q_stack, + K_THREAD_STACK_SIZEOF(split_central_data_xfer_q_stack), + CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL); bt_conn_cb_register(&conn_callbacks); return IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) ? 0 : start_scanning(); diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index 505eb363..9a508c92 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -49,6 +49,7 @@ static uint8_t num_of_positions = ZMK_KEYMAP_LEN; static uint8_t position_state[POS_STATE_LEN]; static struct zmk_split_run_behavior_payload behavior_run_payload; +static struct zmk_split_data_xfer_data data_xfer_payload; static ssize_t split_svc_pos_state(struct bt_conn *conn, const struct bt_gatt_attr *attrs, void *buf, uint16_t len, uint16_t offset) { @@ -101,6 +102,28 @@ static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt return len; } +static ssize_t split_svc_data_xfer(struct bt_conn *conn, const struct bt_gatt_attr *attrs, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + struct zmk_split_data_xfer_data *payload = attrs->user_data; + uint16_t end_addr = offset + len; + + LOG_DBG("offset %d len %d", offset, len); + + memcpy(payload + offset, buf, len); + + // If whole packet transferred correctly + if ((end_addr == sizeof(struct zmk_split_data_xfer_data))) { + // Raise new data transfer event with received data + LOG_HEXDUMP_DBG(&payload, sizeof(struct zmk_split_data_xfer_data), "receiving :"); + LOG_DBG("Size correct, raising evt, %d", end_addr); + struct zmk_split_data_xfer_event event; + memcpy(&event.data_xfer, payload, sizeof(struct zmk_split_data_xfer_data)); + ZMK_EVENT_RAISE(new_zmk_split_data_xfer_event(event)); + } + + return len; +} + static ssize_t split_svc_num_of_positions(struct bt_conn *conn, const struct bt_gatt_attr *attrs, void *buf, uint16_t len, uint16_t offset) { return bt_gatt_attr_read(conn, attrs, buf, len, offset, attrs->user_data, sizeof(uint8_t)); @@ -147,6 +170,9 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID), BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL, split_svc_run_behavior, &behavior_run_payload), + BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_DATA_XFER_UUID), + BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL, + split_svc_data_xfer, &data_xfer_payload), BT_GATT_DESCRIPTOR(BT_UUID_NUM_OF_DIGITALS, BT_GATT_PERM_READ, split_svc_num_of_positions, NULL, &num_of_positions), #if ZMK_KEYMAP_HAS_SENSORS