From 0b115723915dc5832574859a6f996ddad593d148 Mon Sep 17 00:00:00 2001 From: Adrien Friggeri Date: Mon, 26 Feb 2024 14:19:35 +0000 Subject: [PATCH 1/5] [feat] Support for acting as a HID joystick --- app/Kconfig | 12 ++++++ app/include/zmk/endpoints.h | 4 ++ app/include/zmk/hid.h | 40 ++++++++++++++++++ app/include/zmk/hog.h | 4 ++ app/include/zmk/usb_hid.h | 3 ++ app/src/endpoints.c | 40 ++++++++++++++++-- app/src/hid.c | 33 +++++++++++---- app/src/hog.c | 82 +++++++++++++++++++++++++++++++++++++ app/src/usb_hid.c | 13 ++++++ 9 files changed, 220 insertions(+), 11 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index bb6997a4..1ecf2871 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -213,6 +213,10 @@ config ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE int "Max number of mouse HID reports to queue for sending over BLE" default 20 +config ZMK_BLE_JOYSTICK_REPORT_QUEUE_SIZE + int "Max number of joystick HID reports to queue for sending over BLE" + default 20 + config ZMK_BLE_CLEAR_BONDS_ON_START bool "Configuration that clears all bond information from the keyboard on startup." @@ -375,6 +379,14 @@ config ZMK_MOUSE #Mouse Options endmenu +menu "Joystick Options" + +config ZMK_JOYSTICK + bool "Enable ZMK joystick emulation" + +#Mouse Options +endmenu + menu "Power Management" config ZMK_BATTERY_REPORTING diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index 70240183..e7f611f4 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -73,3 +73,7 @@ int zmk_endpoints_send_report(uint16_t usage_page); #if IS_ENABLED(CONFIG_ZMK_MOUSE) int zmk_endpoints_send_mouse_report(); #endif // IS_ENABLE(CONFIG_ZMK_MOUSE) + +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +int zmk_endpoints_send_joystick_report(); +#endif // IS_ENABLE(CONFIG_ZMK_JOYSTICK) diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index d1d3b7d4..a8f81f99 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -58,6 +58,7 @@ #define ZMK_HID_REPORT_ID_LEDS 0x01 #define ZMK_HID_REPORT_ID_CONSUMER 0x02 #define ZMK_HID_REPORT_ID_MOUSE 0x03 +#define ZMK_HID_REPORT_ID_JOYSTICK 0x04 static const uint8_t zmk_hid_report_desc[] = { HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), @@ -175,6 +176,23 @@ static const uint8_t zmk_hid_report_desc[] = { HID_END_COLLECTION, HID_END_COLLECTION, #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) + HID_USAGE_PAGE(HID_USAGE_GD), + HID_USAGE(HID_USAGE_GD_JOYSTICK), + HID_COLLECTION(HID_COLLECTION_APPLICATION), + HID_REPORT_ID(ZMK_HID_REPORT_ID_JOYSTICK), + HID_COLLECTION(HID_COLLECTION_LOGICAL), + HID_USAGE(HID_USAGE_GD_X), + HID_USAGE(HID_USAGE_GD_Y), + HID_USAGE(HID_USAGE_GD_Z), + HID_LOGICAL_MIN8(-0x7F), + HID_LOGICAL_MAX8(0x7F), + HID_REPORT_SIZE(0x08), + HID_REPORT_COUNT(0x03), + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + HID_END_COLLECTION, + HID_END_COLLECTION, +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) }; #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) @@ -252,6 +270,19 @@ struct zmk_hid_mouse_report { #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +struct zmk_hid_joystick_report_body { + int8_t x; + int8_t y; + int8_t z; +} __packed; + +struct zmk_hid_joystick_report { + uint8_t report_id; + struct zmk_hid_joystick_report_body body; +} __packed; +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + zmk_mod_flags_t zmk_hid_get_explicit_mods(void); int zmk_hid_register_mod(zmk_mod_t modifier); int zmk_hid_unregister_mod(zmk_mod_t modifier); @@ -286,6 +317,11 @@ int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons); void zmk_hid_mouse_clear(void); #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +void zmk_hid_joystick_set(uint8_t x, uint8_t y, uint8_t z); +void zmk_hid_joystick_clear(void); +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void); struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void); @@ -296,3 +332,7 @@ zmk_hid_boot_report_t *zmk_hid_get_boot_report(); #if IS_ENABLED(CONFIG_ZMK_MOUSE) struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(); #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +struct zmk_hid_joystick_report *zmk_hid_get_joystick_report(); +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index eb6e653f..d7c567e8 100644 --- a/app/include/zmk/hog.h +++ b/app/include/zmk/hog.h @@ -15,3 +15,7 @@ int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body); #if IS_ENABLED(CONFIG_ZMK_MOUSE) int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +int zmk_hog_send_joystick_report(struct zmk_hid_joystick_report_body *body); +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) diff --git a/app/include/zmk/usb_hid.h b/app/include/zmk/usb_hid.h index c0cbc08a..7c08dd80 100644 --- a/app/include/zmk/usb_hid.h +++ b/app/include/zmk/usb_hid.h @@ -13,4 +13,7 @@ int zmk_usb_hid_send_consumer_report(void); #if IS_ENABLED(CONFIG_ZMK_MOUSE) int zmk_usb_hid_send_mouse_report(void); #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +int zmk_usb_hid_send_joystick_report(void); +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) void zmk_usb_hid_set_protocol(uint8_t protocol); diff --git a/app/src/endpoints.c b/app/src/endpoints.c index f8452d93..3e081a5d 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -116,9 +116,7 @@ int zmk_endpoints_toggle_transport(void) { return zmk_endpoints_select_transport(new_transport); } -struct zmk_endpoint_instance zmk_endpoints_selected(void) { - return current_instance; -} +struct zmk_endpoint_instance zmk_endpoints_selected(void) { return current_instance; } static int send_keyboard_report(void) { switch (current_instance.transport) { @@ -239,6 +237,42 @@ int zmk_endpoints_send_mouse_report() { } #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +int zmk_endpoints_send_joystick_report() { + switch (current_instance.transport) { + case ZMK_TRANSPORT_USB: { +#if IS_ENABLED(CONFIG_ZMK_USB) + int err = zmk_usb_hid_send_joystick_report(); + if (err) { + LOG_ERR("FAILED TO SEND OVER USB: %d", err); + } + return err; +#else + LOG_ERR("USB endpoint is not supported"); + return -ENOTSUP; +#endif /* IS_ENABLED(CONFIG_ZMK_USB) */ + } + + case ZMK_TRANSPORT_BLE: { +#if IS_ENABLED(CONFIG_ZMK_BLE) + struct zmk_hid_joystick_report *joystick_report = zmk_hid_get_joystick_report(); + int err = zmk_hog_send_joystick_report(&joystick_report->body); + if (err) { + LOG_ERR("FAILED TO SEND OVER HOG: %d", err); + } + return err; +#else + LOG_ERR("BLE HOG endpoint is not supported"); + return -ENOTSUP; +#endif /* IS_ENABLED(CONFIG_ZMK_BLE) */ + } + } + + LOG_ERR("Unhandled endpoint transport %d", current_instance.transport); + return -ENOTSUP; +} +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + #if IS_ENABLED(CONFIG_SETTINGS) static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb, diff --git a/app/src/hid.c b/app/src/hid.c index 8b0c23f3..324b9d32 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -32,6 +32,13 @@ static struct zmk_hid_mouse_report mouse_report = {.report_id = ZMK_HID_REPORT_I #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) + +static struct zmk_hid_joystick_report joystick_report = {.report_id = ZMK_HID_REPORT_ID_JOYSTICK, + .body = {.x = 0, .y = 0, .z = 0}}; + +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + // Keep track of how often a modifier was pressed. // Only release the modifier if the count is 0. static int explicit_modifier_counts[8] = {0, 0, 0, 0, 0, 0, 0, 0}; @@ -434,18 +441,28 @@ void zmk_hid_mouse_clear(void) { memset(&mouse_report.body, 0, sizeof(mouse_repo #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) -struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { - return &keyboard_report; -} +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +void zmk_hid_joystick_set(uint8_t x, uint8_t y, uint8_t z) { + joystick_report.body.x = x; + joystick_report.body.y = y; + joystick_report.body.z = z; +}; -struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) { - return &consumer_report; +void zmk_hid_joystick_clear(void) { + memset(&joystick_report.body, 0, sizeof(joystick_report.body)); } +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + +struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { return &keyboard_report; } + +struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) { return &consumer_report; } #if IS_ENABLED(CONFIG_ZMK_MOUSE) -struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { - return &mouse_report; -} +struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { return &mouse_report; } #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +struct zmk_hid_joystick_report *zmk_hid_get_joystick_report(void) { return &joystick_report; }; +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) \ No newline at end of file diff --git a/app/src/hog.c b/app/src/hog.c index f17f759c..a86169b2 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -78,6 +78,15 @@ static struct hids_report mouse_input = { #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) + +static struct hids_report joystick_input = { + .id = ZMK_HID_REPORT_ID_JOYSTICK, + .type = HIDS_INPUT, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + static bool host_requests_notification = false; static uint8_t ctrl_point; // static uint8_t proto_mode; @@ -152,6 +161,16 @@ static ssize_t read_hids_mouse_input_report(struct bt_conn *conn, const struct b } #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +static ssize_t read_hids_joystick_input_report(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + struct zmk_hid_joystick_report_body *report_body = &zmk_hid_get_joystick_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_joystick_report_body)); +} +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + // static ssize_t write_proto_mode(struct bt_conn *conn, // const struct bt_gatt_attr *attr, // const void *buf, uint16_t len, uint16_t offset, @@ -208,6 +227,14 @@ BT_GATT_SERVICE_DEFINE( NULL, &mouse_input), #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ_ENCRYPT, read_hids_joystick_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, &joystick_input), +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + #if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, @@ -398,6 +425,61 @@ int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) + +K_MSGQ_DEFINE(zmk_hog_joystick_msgq, sizeof(struct zmk_hid_joystick_report_body), + CONFIG_ZMK_BLE_JOYSTICK_REPORT_QUEUE_SIZE, 4); + +void send_joystick_report_callback(struct k_work *work) { + struct zmk_hid_joystick_report_body report; + while (k_msgq_get(&zmk_hog_joystick_msgq, &report, K_NO_WAIT) == 0) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return; + } + + struct bt_gatt_notify_params notify_params = { + .attr = &hog_svc.attrs[13], + .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); + } +}; + +K_WORK_DEFINE(hog_joystick_work, send_joystick_report_callback); + +int zmk_hog_send_joystick_report(struct zmk_hid_joystick_report_body *report) { + int err = k_msgq_put(&zmk_hog_joystick_msgq, report, K_MSEC(100)); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("Consumer message queue full, popping first message and queueing again"); + struct zmk_hid_joystick_report_body discarded_report; + k_msgq_get(&zmk_hog_joystick_msgq, &discarded_report, K_NO_WAIT); + return zmk_hog_send_joystick_report(report); + } + default: + LOG_WRN("Failed to queue joystick report to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&hog_work_q, &hog_joystick_work); + + return 0; +}; + +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + static int zmk_hog_init(void) { static const struct k_work_queue_config queue_config = {.name = "HID Over GATT Send Work"}; k_work_queue_start(&hog_work_q, hog_q_stack, K_THREAD_STACK_SIZEOF(hog_q_stack), diff --git a/app/src/usb_hid.c b/app/src/usb_hid.c index cd3ef920..fe007c26 100644 --- a/app/src/usb_hid.c +++ b/app/src/usb_hid.c @@ -177,6 +177,19 @@ int zmk_usb_hid_send_mouse_report() { } #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_JOYSTICK) +int zmk_usb_hid_send_joystick_report() { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + if (hid_protocol == HID_PROTOCOL_BOOT) { + return -ENOTSUP; + } +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + + struct zmk_hid_joystick_report *report = zmk_hid_get_joystick_report(); + return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report)); +} +#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) + static int zmk_usb_hid_init(void) { hid_dev = device_get_binding("HID_0"); if (hid_dev == NULL) { From 20aff40f6aebd5c2e8cc2c8757736eb54bf04477 Mon Sep 17 00:00:00 2001 From: Adrien Friggeri Date: Mon, 26 Feb 2024 14:44:16 +0000 Subject: [PATCH 2/5] Fix formatting --- app/src/hid.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/hid.c b/app/src/hid.c index 324b9d32..ea970cb2 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -453,16 +453,24 @@ void zmk_hid_joystick_clear(void) { } #endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) -struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { return &keyboard_report; } +struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { + return &keyboard_report; +} -struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) { return &consumer_report; } +struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) { + return &consumer_report; +} #if IS_ENABLED(CONFIG_ZMK_MOUSE) -struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { return &mouse_report; } +struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { + return &mouse_report; +} #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #if IS_ENABLED(CONFIG_ZMK_JOYSTICK) -struct zmk_hid_joystick_report *zmk_hid_get_joystick_report(void) { return &joystick_report; }; +struct zmk_hid_joystick_report *zmk_hid_get_joystick_report(void) { + return &joystick_report; +}; #endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK) \ No newline at end of file From c01de7ebbba9cd18fe24b7d1b519221c9a3b500e Mon Sep 17 00:00:00 2001 From: Adrien Friggeri Date: Mon, 26 Feb 2024 14:46:33 +0000 Subject: [PATCH 3/5] More formatting fixes --- app/src/endpoints.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 3e081a5d..877d4c4c 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -116,7 +116,9 @@ int zmk_endpoints_toggle_transport(void) { return zmk_endpoints_select_transport(new_transport); } -struct zmk_endpoint_instance zmk_endpoints_selected(void) { return current_instance; } +struct zmk_endpoint_instance zmk_endpoints_selected(void) { + return current_instance; +} static int send_keyboard_report(void) { switch (current_instance.transport) { From d6670b6cebe24ca44b65b1d1091db3f597e4bf0b Mon Sep 17 00:00:00 2001 From: Adrien Friggeri Date: Mon, 26 Feb 2024 14:54:16 +0000 Subject: [PATCH 4/5] Fix typo --- app/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Kconfig b/app/Kconfig index 1ecf2871..494600b0 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -384,7 +384,7 @@ menu "Joystick Options" config ZMK_JOYSTICK bool "Enable ZMK joystick emulation" -#Mouse Options +#Joystick Options endmenu menu "Power Management" From 5dbf10e3e8f2a892a51407249bfdf6365d4c2b3e Mon Sep 17 00:00:00 2001 From: Adrien Friggeri Date: Mon, 11 Mar 2024 12:56:27 +0000 Subject: [PATCH 5/5] Remove oversampling in battery voltage divider --- app/module/drivers/sensor/battery/battery_voltage_divider.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/module/drivers/sensor/battery/battery_voltage_divider.c b/app/module/drivers/sensor/battery/battery_voltage_divider.c index 62a02e9c..f8bd616a 100644 --- a/app/module/drivers/sensor/battery/battery_voltage_divider.c +++ b/app/module/drivers/sensor/battery/battery_voltage_divider.c @@ -134,7 +134,7 @@ static int bvd_init(const struct device *dev) { .channels = BIT(0), .buffer = &drv_data->value.adc_raw, .buffer_size = sizeof(drv_data->value.adc_raw), - .oversampling = 4, + .oversampling = 0, .calibrate = true, };