#include "hid_service.h" #include "app_common.h" // IWYU pragma: keep #include #include #include #include #include #define TAG "BleHid" #define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255) #define BLE_SVC_HID_REPORT_MAX_LEN (255) #define BLE_SVC_HID_REPORT_REF_LEN (2) #define BLE_SVC_HID_INFO_LEN (4) #define BLE_SVC_HID_CONTROL_POINT_LEN (1) #define BLE_SVC_HID_INPUT_REPORT_COUNT (3) #define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0) #define BLE_SVC_HID_FEATURE_REPORT_COUNT (0) #define BLE_SVC_HID_REPORT_COUNT \ (BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \ BLE_SVC_HID_FEATURE_REPORT_COUNT) typedef enum { HidSvcGattCharacteristicProtocolMode = 0, HidSvcGattCharacteristicReportMap, HidSvcGattCharacteristicInfo, HidSvcGattCharacteristicCtrlPoint, HidSvcGattCharacteristicCount, } HidSvcGattCharacteristicId; typedef struct { uint8_t report_idx; uint8_t report_type; } HidSvcReportId; static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes"); static const Service_UUID_t ble_svc_hid_uuid = { .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, }; static bool ble_svc_hid_char_desc_data_callback( const void* context, const uint8_t** data, uint16_t* data_len) { const HidSvcReportId* report_id = context; *data_len = sizeof(HidSvcReportId); if(data) { *data = (const uint8_t*)report_id; } return false; } typedef struct { const void* data_ptr; uint16_t data_len; } HidSvcDataWrapper; static bool ble_svc_hid_report_data_callback( const void* context, const uint8_t** data, uint16_t* data_len) { const HidSvcDataWrapper* report_data = context; if(data) { *data = report_data->data_ptr; *data_len = report_data->data_len; } else { *data_len = BLE_SVC_HID_REPORT_MAP_MAX_LEN; } return false; } static const BleGattCharacteristicParams ble_svc_hid_chars[HidSvcGattCharacteristicCount] = { [HidSvcGattCharacteristicProtocolMode] = {.name = "Protocol Mode", .data_prop_type = FlipperGattCharacteristicDataFixed, .data.fixed.length = 1, .uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP, .security_permissions = ATTR_PERMISSION_NONE, .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, .is_variable = CHAR_VALUE_LEN_CONSTANT}, [HidSvcGattCharacteristicReportMap] = {.name = "Report Map", .data_prop_type = FlipperGattCharacteristicDataCallback, .data.callback.fn = ble_svc_hid_report_data_callback, .data.callback.context = NULL, .uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ, .security_permissions = ATTR_PERMISSION_NONE, .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, .is_variable = CHAR_VALUE_LEN_VARIABLE}, [HidSvcGattCharacteristicInfo] = {.name = "HID Information", .data_prop_type = FlipperGattCharacteristicDataFixed, .data.fixed.length = BLE_SVC_HID_INFO_LEN, .data.fixed.ptr = NULL, .uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ, .security_permissions = ATTR_PERMISSION_NONE, .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, .is_variable = CHAR_VALUE_LEN_CONSTANT}, [HidSvcGattCharacteristicCtrlPoint] = {.name = "HID Control Point", .data_prop_type = FlipperGattCharacteristicDataFixed, .data.fixed.length = BLE_SVC_HID_CONTROL_POINT_LEN, .uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP, .security_permissions = ATTR_PERMISSION_NONE, .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, .is_variable = CHAR_VALUE_LEN_CONSTANT}, }; static const BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr_template = { .uuid_type = UUID_TYPE_16, .uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID, .max_length = BLE_SVC_HID_REPORT_REF_LEN, .data_callback.fn = ble_svc_hid_char_desc_data_callback, .security_permissions = ATTR_PERMISSION_NONE, .access_permissions = ATTR_ACCESS_READ_WRITE, .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, .is_variable = CHAR_VALUE_LEN_CONSTANT, }; static const BleGattCharacteristicParams ble_svc_hid_report_template = { .name = "Report", .data_prop_type = FlipperGattCharacteristicDataCallback, .data.callback.fn = ble_svc_hid_report_data_callback, .data.callback.context = NULL, .uuid.Char_UUID_16 = REPORT_CHAR_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, .security_permissions = ATTR_PERMISSION_NONE, .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, .is_variable = CHAR_VALUE_LEN_VARIABLE, }; struct BleServiceHid { uint16_t svc_handle; BleGattCharacteristicInstance chars[HidSvcGattCharacteristicCount]; BleGattCharacteristicInstance input_report_chars[BLE_SVC_HID_INPUT_REPORT_COUNT]; BleGattCharacteristicInstance output_report_chars[BLE_SVC_HID_OUTPUT_REPORT_COUNT]; BleGattCharacteristicInstance feature_report_chars[BLE_SVC_HID_FEATURE_REPORT_COUNT]; GapSvcEventHandler* event_handler; }; static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) { UNUSED(context); BleEventAckStatus ret = BleEventNotAck; hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; // aci_gatt_attribute_modified_event_rp0* attribute_modified; if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { // Process modification events ret = BleEventAckFlowEnable; } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { // Process notification confirmation ret = BleEventAckFlowEnable; } } return ret; } BleServiceHid* ble_svc_hid_start(void) { BleServiceHid* hid_svc = malloc(sizeof(BleServiceHid)); // Register event handler hid_svc->event_handler = ble_event_dispatcher_register_svc_handler(ble_svc_hid_event_handler, hid_svc); /** * Add Human Interface Device Service */ if(!ble_gatt_service_add( UUID_TYPE_16, &ble_svc_hid_uuid, PRIMARY_SERVICE, 2 + /* protocol mode */ (4 * BLE_SVC_HID_INPUT_REPORT_COUNT) + (3 * BLE_SVC_HID_OUTPUT_REPORT_COUNT) + (3 * BLE_SVC_HID_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + 2, /* Service + Report Map + HID Information + HID Control Point */ &hid_svc->svc_handle)) { free(hid_svc); return NULL; } // Maintain previously defined characteristic order ble_gatt_characteristic_init( hid_svc->svc_handle, &ble_svc_hid_chars[HidSvcGattCharacteristicProtocolMode], &hid_svc->chars[HidSvcGattCharacteristicProtocolMode]); uint8_t protocol_mode = 1; ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicProtocolMode], &protocol_mode); // reports BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr; BleGattCharacteristicParams report_char; HidSvcReportId report_id; memcpy( &ble_svc_hid_char_descr, &ble_svc_hid_char_descr_template, sizeof(ble_svc_hid_char_descr)); memcpy(&report_char, &ble_svc_hid_report_template, sizeof(report_char)); ble_svc_hid_char_descr.data_callback.context = &report_id; report_char.descriptor_params = &ble_svc_hid_char_descr; typedef struct { uint8_t report_type; uint8_t report_count; BleGattCharacteristicInstance* chars; } HidSvcReportCharProps; HidSvcReportCharProps hid_report_chars[] = { {0x01, BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, {0x02, BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, {0x03, BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, }; for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); report_type_idx++) { report_id.report_type = hid_report_chars[report_type_idx].report_type; for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; report_idx++) { report_id.report_idx = report_idx + 1; ble_gatt_characteristic_init( hid_svc->svc_handle, &report_char, &hid_report_chars[report_type_idx].chars[report_idx]); } } // Setup remaining characteristics for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) { ble_gatt_characteristic_init( hid_svc->svc_handle, &ble_svc_hid_chars[i], &hid_svc->chars[i]); } return hid_svc; } bool ble_svc_hid_update_report_map(BleServiceHid* hid_svc, const uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); HidSvcDataWrapper report_data = { .data_ptr = data, .data_len = len, }; return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data); } bool ble_svc_hid_update_input_report( BleServiceHid* hid_svc, uint8_t input_report_num, uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); furi_assert(input_report_num < BLE_SVC_HID_INPUT_REPORT_COUNT); HidSvcDataWrapper report_data = { .data_ptr = data, .data_len = len, }; return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data); } bool ble_svc_hid_update_info(BleServiceHid* hid_svc, uint8_t* data) { furi_assert(data); furi_assert(hid_svc); return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data); } void ble_svc_hid_stop(BleServiceHid* hid_svc) { furi_assert(hid_svc); ble_event_dispatcher_unregister_svc_handler(hid_svc->event_handler); // Delete characteristics for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { ble_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]); } typedef struct { uint8_t report_count; BleGattCharacteristicInstance* chars; } HidSvcReportCharProps; HidSvcReportCharProps hid_report_chars[] = { {BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, {BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, {BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, }; for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); report_type_idx++) { for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; report_idx++) { ble_gatt_characteristic_delete( hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]); } } // Delete service ble_gatt_service_delete(hid_svc->svc_handle); free(hid_svc); }