MustafaMulla29/wireless-mouse-pcb

A tscircuit package created by MustafaMulla29.

Version
1.0.16
License
unset
Stars
0

firmware/src/hid_mouse.c

/* SPDX-License-Identifier: Apache-2.0 */

/* HID-over-GATT service structure follows Zephyr's official
 * samples/bluetooth/peripheral_hids example.
 */

#include "hid_mouse.h"

#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>

enum {
	HIDS_REMOTE_WAKE = BIT(0),
	HIDS_NORMALLY_CONNECTABLE = BIT(1),
};

enum {
	HIDS_INPUT = 0x01,
};

struct hids_info {
	uint16_t version;
	uint8_t country_code;
	uint8_t flags;
} __packed;

struct hids_report_ref {
	uint8_t id;
	uint8_t type;
} __packed;

struct mouse_report {
	uint8_t buttons;
	int16_t x;
	int16_t y;
	int8_t wheel;
} __packed;

static struct hids_info hids_info = {
	.version = sys_cpu_to_le16(0x0111),
	.country_code = 0,
	.flags = HIDS_NORMALLY_CONNECTABLE,
};

static struct hids_report_ref input_report_ref = {
	.id = 1,
	.type = HIDS_INPUT,
};

static uint8_t control_point;
static bool notifications_enabled;
static struct bt_conn *active_conn;

static const uint8_t report_map[] = {
	0x05, 0x01,       /* Usage Page (Generic Desktop) */
	0x09, 0x02,       /* Usage (Mouse) */
	0xA1, 0x01,       /* Collection (Application) */
	0x85, 0x01,       /*   Report ID (1) */
	0x09, 0x01,       /*   Usage (Pointer) */
	0xA1, 0x00,       /*   Collection (Physical) */
	0x05, 0x09,       /*     Usage Page (Button) */
	0x19, 0x01,       /*     Usage Minimum (1) */
	0x29, 0x05,       /*     Usage Maximum (5) */
	0x15, 0x00,       /*     Logical Minimum (0) */
	0x25, 0x01,       /*     Logical Maximum (1) */
	0x95, 0x05,       /*     Report Count (5) */
	0x75, 0x01,       /*     Report Size (1) */
	0x81, 0x02,       /*     Input (Data, Variable, Absolute) */
	0x95, 0x01,       /*     Report Count (1) */
	0x75, 0x03,       /*     Report Size (3) */
	0x81, 0x03,       /*     Input (Constant) */
	0x05, 0x01,       /*     Usage Page (Generic Desktop) */
	0x09, 0x30,       /*     Usage (X) */
	0x09, 0x31,       /*     Usage (Y) */
	0x16, 0x01, 0x80, /*     Logical Minimum (-32767) */
	0x26, 0xFF, 0x7F, /*     Logical Maximum (32767) */
	0x75, 0x10,       /*     Report Size (16) */
	0x95, 0x02,       /*     Report Count (2) */
	0x81, 0x06,       /*     Input (Data, Variable, Relative) */
	0x09, 0x38,       /*     Usage (Wheel) */
	0x15, 0x81,       /*     Logical Minimum (-127) */
	0x25, 0x7F,       /*     Logical Maximum (127) */
	0x75, 0x08,       /*     Report Size (8) */
	0x95, 0x01,       /*     Report Count (1) */
	0x81, 0x06,       /*     Input (Data, Variable, Relative) */
	0xC0,             /*   End Collection */
	0xC0,             /* End Collection */
};

static ssize_t read_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(hids_info));
}

static ssize_t read_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, report_map,
				 sizeof(report_map));
}

static ssize_t read_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(input_report_ref));
}

static ssize_t read_input_report(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, NULL, 0);
}

static ssize_t write_control_point(struct bt_conn *conn,
				   const struct bt_gatt_attr *attr,
				   const void *buf, uint16_t len,
				   uint16_t offset, uint8_t flags)
{
	if (offset + len > sizeof(control_point)) {
		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
	}

	memcpy((uint8_t *)attr->user_data + offset, buf, len);
	return len;
}

static void input_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
	notifications_enabled = (value == BT_GATT_CCC_NOTIFY);
}

BT_GATT_SERVICE_DEFINE(hog_svc,
	BT_GATT_PRIMARY_SERVICE(BT_UUID_HIDS),
	BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_INFO, BT_GATT_CHRC_READ,
			       BT_GATT_PERM_READ, read_info, NULL, &hids_info),
	BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT_MAP, BT_GATT_CHRC_READ,
			       BT_GATT_PERM_READ, read_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_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,
			   read_report_ref, NULL, &input_report_ref),
	BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT,
			       BT_GATT_CHRC_WRITE_WITHOUT_RESP,
			       BT_GATT_PERM_WRITE_ENCRYPT,
			       NULL, write_control_point, &control_point),
);

static const struct bt_data advertising_data[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR),
	BT_DATA_BYTES(BT_DATA_UUID16_ALL,
		      BT_UUID_16_ENCODE(BT_UUID_HIDS_VAL),
		      BT_UUID_16_ENCODE(BT_UUID_BAS_VAL)),
};

static const struct bt_data scan_response[] = {
	BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME,
		sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};

static int start_advertising(void)
{
	return bt_le_adv_start(BT_LE_ADV_CONN_FAST_1,
			       advertising_data, ARRAY_SIZE(advertising_data),
			       scan_response, ARRAY_SIZE(scan_response));
}

static void connected(struct bt_conn *conn, uint8_t err)
{
	if (err != 0) {
		return;
	}

	active_conn = bt_conn_ref(conn);
	(void)bt_conn_set_security(conn, BT_SECURITY_L2);
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
	ARG_UNUSED(conn);
	ARG_UNUSED(reason);
	notifications_enabled = false;
	if (active_conn != NULL) {
		bt_conn_unref(active_conn);
		active_conn = NULL;
	}
	(void)start_advertising();
}

BT_CONN_CB_DEFINE(connection_callbacks) = {
	.connected = connected,
	.disconnected = disconnected,
};

int hid_mouse_init(void)
{
	int err = bt_enable(NULL);

	if (err != 0) {
		return err;
	}

	if (IS_ENABLED(CONFIG_SETTINGS)) {
		err = settings_load();
		if (err != 0) {
			return err;
		}
	}

	return start_advertising();
}

bool hid_mouse_is_connected(void)
{
	return active_conn != NULL && notifications_enabled;
}

int hid_mouse_send(uint8_t buttons, int16_t x, int16_t y, int8_t wheel)
{
	struct mouse_report report = {
		.buttons = buttons & 0x1F,
		.x = (int16_t)sys_cpu_to_le16((uint16_t)x),
		.y = (int16_t)sys_cpu_to_le16((uint16_t)y),
		.wheel = wheel,
	};

	if (!hid_mouse_is_connected()) {
		return -ENOTCONN;
	}

	/* This is the characteristic declaration index used by Zephyr's
	 * official peripheral_hids sample; bt_gatt_notify resolves its value.
	 */
	return bt_gatt_notify(active_conn, &hog_svc.attrs[5], &report,
			      sizeof(report));
}