diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 314714844f4..de0d57c5cdc 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -73,6 +73,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_bt.c) target_sources(app PRIVATE src/ble.c) target_sources(app PRIVATE src/hog.c) + target_sources(app PRIVATE src/keymap_hog.c) endif() endif() @@ -89,6 +90,7 @@ add_subdirectory(src/split) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c) target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c) +target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/keymap_usb_hid.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) target_sources_ifdef(CONFIG_ZMK_LOW_PRIORITY_WORK_QUEUE app PRIVATE src/workqueue.c) diff --git a/app/include/zmk/keymap_hid.h b/app/include/zmk/keymap_hid.h new file mode 100644 index 00000000000..2df56303c63 --- /dev/null +++ b/app/include/zmk/keymap_hid.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +#ifndef HID_USAGE_PAGE16 +#define HID_USAGE_PAGE16(page, page2) \ + HID_ITEM(HID_ITEM_TAG_USAGE_PAGE, HID_ITEM_TYPE_GLOBAL, 2), page, page2 +#endif + +#ifndef HID_USAGE6 +#define HID_USAGE16(page, page2) HID_ITEM(HID_ITEM_TAG_USAGE, HID_ITEM_TYPE_LOCAL, 2), page, page2 +#endif + +#define KEYMAP_HID_MAX_BYTES 1 + +#define KEYMAP_HID_REPORT_ID 0x42 + +static const uint8_t zmk_keymap_hid_report_desc[] = { + HID_USAGE_PAGE16(0x0B, 0xFF), + HID_USAGE(0x01), + HID_COLLECTION(HID_COLLECTION_APPLICATION), + HID_REPORT_ID(KEYMAP_HID_REPORT_ID), + HID_USAGE_PAGE16(0x0B, 0xFF), + HID_USAGE_MIN8(0x00), + HID_USAGE_MAX16(0xFF, 0x00), + HID_LOGICAL_MIN8(0x00), + HID_LOGICAL_MAX16(0xFF, 0x00), + + HID_REPORT_SIZE(0x08), + HID_REPORT_COUNT(KEYMAP_HID_MAX_BYTES), + /* INPUT (Data,Var,Abs) */ + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + + HID_REPORT_ID(KEYMAP_HID_REPORT_ID), + HID_USAGE_PAGE16(0x0B, 0xFF), + HID_USAGE_MIN8(0x00), + HID_USAGE_MAX16(0xFF, 0x00), + HID_LOGICAL_MIN8(0x00), + HID_LOGICAL_MAX16(0xFF, 0x00), + + HID_REPORT_SIZE(0x08), + HID_REPORT_COUNT(KEYMAP_HID_MAX_BYTES), + /* INPUT (Data,Var,Abs) */ + HID_OUTPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + HID_END_COLLECTION, +}; + +struct zmk_keymap_hid_sample_report_body { + uint8_t data[KEYMAP_HID_MAX_BYTES]; +} __packed; + +struct zmk_keymap_hid_sample_report { + uint8_t report_id; + struct zmk_keymap_hid_sample_report_body body; +} __packed; diff --git a/app/src/keymap_hog.c b/app/src/keymap_hog.c new file mode 100644 index 00000000000..bc4394feda2 --- /dev/null +++ b/app/src/keymap_hog.c @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include + +#include +#include +#include +#include + +enum { + HIDS_REMOTE_WAKE = BIT(0), + HIDS_NORMALLY_CONNECTABLE = BIT(1), +}; + +struct hids_info { + uint16_t version; /* version number of base USB HID Specification */ + uint8_t code; /* country HID Device hardware is localized for. */ + uint8_t flags; +} __packed; + +struct hids_report { + uint8_t id; /* report id */ + uint8_t type; /* report type */ +} __packed; + +static struct hids_info info = { + .version = 0x0000, + .code = 0x00, + .flags = HIDS_NORMALLY_CONNECTABLE | HIDS_REMOTE_WAKE, +}; + +enum { + HIDS_INPUT = 0x01, + HIDS_OUTPUT = 0x02, + HIDS_FEATURE = 0x03, +}; + +static struct hids_report input = { + .id = KEYMAP_HID_REPORT_ID, + .type = HIDS_INPUT, +}; + +static struct hids_report output = { + .id = KEYMAP_HID_REPORT_ID, + .type = HIDS_OUTPUT, +}; + +static bool host_requests_notification = false; +static uint8_t ctrl_point; + +static ssize_t read_hids_info(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, + sizeof(struct hids_info)); +} + +static ssize_t read_hids_report_ref(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, + sizeof(struct hids_report)); +} + +static ssize_t read_hids_report_map(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + return bt_gatt_attr_read(conn, attr, buf, len, offset, zmk_keymap_hid_report_desc, + sizeof(zmk_keymap_hid_report_desc)); +} + +static ssize_t read_hids_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + static const struct zmk_keymap_hid_sample_report_body report_body = {.data = {66}}; + return bt_gatt_attr_read(conn, attr, buf, len, offset, &report_body, + sizeof(struct zmk_keymap_hid_sample_report_body)); +} + +static ssize_t write_hids_output_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, + uint8_t flags) { + if (offset != 0) { + LOG_ERR("Offset is wrong %d", offset); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + struct zmk_keymap_hid_sample_report_body *report; + if (len == sizeof(struct zmk_keymap_hid_sample_report_body)) { + report = (struct zmk_keymap_hid_sample_report_body *)buf; + } else if (len == sizeof(struct zmk_keymap_hid_sample_report)) { + struct zmk_keymap_hid_sample_report *wrapper = (struct zmk_keymap_hid_sample_report *)buf; + if (wrapper->report_id != KEYMAP_HID_REPORT_ID) { + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + report = &wrapper->body; + } else { + LOG_ERR("Length is wrong %d vs %d", len, sizeof(struct zmk_keymap_hid_sample_report_body)); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + int profile = zmk_ble_profile_index(bt_conn_get_dst(conn)); + if (profile < 0) { + LOG_ERR("No profile"); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + LOG_HEXDUMP_DBG(report->data, KEYMAP_HID_MAX_BYTES, "Got output data sent"); + + return len; +} + +static void input_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) { + host_requests_notification = (value == BT_GATT_CCC_NOTIFY) ? 1 : 0; +} + +static ssize_t write_ctrl_point(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + uint8_t *value = attr->user_data; + + if (offset + len > sizeof(ctrl_point)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + memcpy(value + offset, buf, len); + + return len; +} + +/* HID Service Declaration */ +BT_GATT_SERVICE_DEFINE( + keymap_hog_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_HIDS), + // BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_PROTOCOL_MODE, BT_GATT_CHRC_WRITE_WITHOUT_RESP, + // BT_GATT_PERM_WRITE, NULL, write_proto_mode, &proto_mode), + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_INFO, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, read_hids_info, + NULL, &info), + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT_MAP, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, + read_hids_report_map, NULL, NULL), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ_ENCRYPT, read_hids_input_report, NULL, NULL), + BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, + NULL, &input), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, NULL, + write_hids_output_report, NULL), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, + NULL, &output), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); + +static struct bt_conn *destination_connection(void) { + struct bt_conn *conn; + bt_addr_le_t *addr = zmk_ble_active_profile_addr(); + LOG_DBG("Address pointer %p", addr); + if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { + LOG_WRN("Not sending, no active address for current profile"); + return NULL; + } else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) { + LOG_WRN("Not sending, not connected to active profile"); + return NULL; + } + + return conn; +} + +static K_THREAD_STACK_DEFINE(hog_q_stack, CONFIG_ZMK_BLE_THREAD_STACK_SIZE); + +static struct k_work_q hog_work_q; + +K_MSGQ_DEFINE(zmk_hog_keymap_msgq, sizeof(struct zmk_hid_keyboard_report_body), + CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE, 4); + +static void send_keymap_report_callback(struct k_work *work) { + struct zmk_keymap_hid_sample_report_body report; + + while (k_msgq_get(&zmk_hog_keymap_msgq, &report, K_NO_WAIT) == 0) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return; + } + + struct bt_gatt_notify_params notify_params = { + .attr = &keymap_hog_svc.attrs[5], + .data = &report, + .len = sizeof(report), + }; + + int err = bt_gatt_notify_cb(conn, ¬ify_params); + if (err == -EPERM) { + bt_conn_set_security(conn, BT_SECURITY_L2); + } else if (err) { + LOG_DBG("Error notifying %d", err); + } + + bt_conn_unref(conn); + } +} + +static K_WORK_DEFINE(hog_keyboard_work, send_keymap_report_callback); + +int zmk_hog_send_keymap_report(struct zmk_keymap_hid_sample_report_body *report) { + int err = k_msgq_put(&zmk_hog_keymap_msgq, report, K_MSEC(100)); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("Keyboard message queue full, popping first message and queueing again"); + struct zmk_keymap_hid_sample_report_body discarded_report; + k_msgq_get(&zmk_hog_keymap_msgq, &discarded_report, K_NO_WAIT); + return zmk_hog_send_keymap_report(report); + } + default: + LOG_WRN("Failed to queue keymap report to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&hog_work_q, &hog_keyboard_work); + + return 0; +}; + +static void send_work_cb(struct k_work *_work) { + struct zmk_keymap_hid_sample_report_body body = {.data = {42}}; + + zmk_hog_send_keymap_report(&body); +} + +static K_WORK_DEFINE(send_work, send_work_cb); + +static void tick_cb() { k_work_submit(&send_work); } + +static K_TIMER_DEFINE(send_timer, tick_cb, NULL); + +static int zmk_keymap_hog_init(void) { + static const struct k_work_queue_config queue_config = {.name = + "Keymap HID Over GATT Send Work"}; + k_work_queue_start(&hog_work_q, hog_q_stack, K_THREAD_STACK_SIZEOF(hog_q_stack), + CONFIG_ZMK_BLE_THREAD_PRIORITY, &queue_config); + + k_timer_start(&send_timer, K_SECONDS(1), K_SECONDS(1)); + return 0; +} + +SYS_INIT(zmk_keymap_hog_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY); diff --git a/app/src/keymap_usb_hid.c b/app/src/keymap_usb_hid.c new file mode 100644 index 00000000000..5761eddc57c --- /dev/null +++ b/app/src/keymap_usb_hid.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +static const struct device *hid_dev; + +static K_SEM_DEFINE(hid_sem, 1, 1); + +static void in_ready_cb(const struct device *dev) { + LOG_DBG(""); + k_sem_give(&hid_sem); +} + +#ifdef CONFIG_ENABLE_HID_INT_OUT_EP +static void out_ready_cb(const struct device *dev) { + LOG_DBG(""); + uint8_t data[20]; + uint32_t read; + + hid_int_ep_read(dev, data, 20, &read); + + if (read > 0) { + LOG_DBG("Got %d bytes", read); + LOG_HEXDUMP_DBG(data, read, "Got data"); + } +} +#endif + +static uint8_t payload[] = {0x42, 0x02}; + +static int get_report_cb(const struct device *dev, struct usb_setup_packet *setup, int32_t *len, + uint8_t **data) { + LOG_DBG(""); + *data = payload; + *len = sizeof(payload); + return 0; +} + +static int set_report_cb(const struct device *dev, struct usb_setup_packet *setup, int32_t *len, + uint8_t **data) { + LOG_HEXDUMP_DBG(*data, *len, "REPORT SET"); + return 0; +} + +static const struct hid_ops ops = {.get_report = get_report_cb, + .set_report = set_report_cb, + .int_in_ready = in_ready_cb, +#ifdef CONFIG_ENABLE_HID_INT_OUT_EP + .int_out_ready = out_ready_cb +#endif +}; + +int zmk_keymap_hid_usb_hid_send_report(const uint8_t *report, size_t len) { + switch (zmk_usb_get_status()) { + case USB_DC_SUSPEND: + return usb_wakeup_request(); + case USB_DC_ERROR: + case USB_DC_RESET: + case USB_DC_DISCONNECTED: + case USB_DC_UNKNOWN: + return -ENODEV; + default: + k_sem_take(&hid_sem, K_MSEC(30)); + uint32_t written; + int err = hid_int_ep_write(hid_dev, report, len, &written); + + if (err) { + k_sem_give(&hid_sem); + } + + return err; + } +} + +static void send_work_cb(struct k_work *_work) { + uint8_t data[] = {KEYMAP_HID_REPORT_ID, 42}; + + zmk_keymap_hid_usb_hid_send_report(data, sizeof(data)); +} + +K_WORK_DEFINE(send_work, send_work_cb); + +static void tick_cb() { k_work_submit(&send_work); } + +K_TIMER_DEFINE(send_timer, tick_cb, NULL); + +static int zmk_keymap_hid_usb_init(void) { + int ret; + + hid_dev = device_get_binding("HID_1"); + if (hid_dev == NULL) { + LOG_ERR("Unable to locate HID device"); + return -EINVAL; + } + + usb_hid_register_device(hid_dev, zmk_keymap_hid_report_desc, sizeof(zmk_keymap_hid_report_desc), + &ops); + + ret = usb_hid_init(hid_dev); + if (ret < 0) { + LOG_ERR("Failed to init hid device: %d", ret); + return ret; + } + + k_timer_start(&send_timer, K_SECONDS(1), K_SECONDS(1)); + + return 0; +} + +SYS_INIT(zmk_keymap_hid_usb_init, APPLICATION, CONFIG_ZMK_USB_INIT_PRIORITY);