Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(joystick): add joystick HID descriptor & utility methods #2176

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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."

Expand Down Expand Up @@ -375,6 +379,14 @@ config ZMK_MOUSE
#Mouse Options
endmenu

menu "Joystick Options"

config ZMK_JOYSTICK
bool "Enable ZMK joystick emulation"

#Joystick Options
endmenu

menu "Power Management"

config ZMK_BATTERY_REPORTING
Expand Down
4 changes: 4 additions & 0 deletions app/include/zmk/endpoints.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
40 changes: 40 additions & 0 deletions app/include/zmk/hid.h
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -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)
4 changes: 4 additions & 0 deletions app/include/zmk/hog.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
3 changes: 3 additions & 0 deletions app/include/zmk/usb_hid.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down
36 changes: 36 additions & 0 deletions app/src/endpoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,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,
Expand Down
25 changes: 25 additions & 0 deletions app/src/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -434,6 +441,18 @@ void zmk_hid_mouse_clear(void) { memset(&mouse_report.body, 0, sizeof(mouse_repo

#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) {
joystick_report.body.x = x;
joystick_report.body.y = y;
joystick_report.body.z = z;
};

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;
}
Expand All @@ -449,3 +468,9 @@ struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) {
}

#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)
82 changes: 82 additions & 0 deletions app/src/hog.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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, &notify_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),
Expand Down
13 changes: 13 additions & 0 deletions app/src/usb_hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading