| /** @file |
| * @brief HoG Service sample |
| */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/types.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <zephyr/sys/printk.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/kernel.h> |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/hci.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/uuid.h> |
| #include <zephyr/bluetooth/gatt.h> |
| |
| 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, |
| }; |
| |
| enum { |
| HIDS_INPUT = 0x01, |
| HIDS_OUTPUT = 0x02, |
| HIDS_FEATURE = 0x03, |
| }; |
| |
| static struct hids_report input = { |
| .id = 0x01, |
| .type = HIDS_INPUT, |
| }; |
| |
| static uint8_t simulate_input; |
| static uint8_t ctrl_point; |
| static uint8_t report_map[] = { |
| 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ |
| 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 (0x01) */ |
| 0x29, 0x03, /* Usage Maximum (0x03) */ |
| 0x15, 0x00, /* Logical Minimum (0) */ |
| 0x25, 0x01, /* Logical Maximum (1) */ |
| 0x95, 0x03, /* Report Count (3) */ |
| 0x75, 0x01, /* Report Size (1) */ |
| 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,...) */ |
| 0x95, 0x01, /* Report Count (1) */ |
| 0x75, 0x05, /* Report Size (5) */ |
| 0x81, 0x03, /* Input (Const,Var,Abs,No Wrap,Linear,...) */ |
| 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ |
| 0x09, 0x30, /* Usage (X) */ |
| 0x09, 0x31, /* Usage (Y) */ |
| 0x15, 0x81, /* Logical Minimum (129) */ |
| 0x25, 0x7F, /* Logical Maximum (127) */ |
| 0x75, 0x08, /* Report Size (8) */ |
| 0x95, 0x02, /* Report Count (2) */ |
| 0x81, 0x06, /* Input (Data,Var,Rel,No Wrap,Linear,...) */ |
| 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(struct 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(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 void input_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) |
| { |
| simulate_input = (value == BT_GATT_CCC_NOTIFY) ? 1 : 0; |
| } |
| |
| 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_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; |
| } |
| |
| #if CONFIG_SAMPLE_BT_USE_AUTHENTICATION |
| /* Require encryption using authenticated link-key. */ |
| #define SAMPLE_BT_PERM_READ BT_GATT_PERM_READ_AUTHEN |
| #define SAMPLE_BT_PERM_WRITE BT_GATT_PERM_WRITE_AUTHEN |
| #else |
| /* Require encryption. */ |
| #define SAMPLE_BT_PERM_READ BT_GATT_PERM_READ_ENCRYPT |
| #define SAMPLE_BT_PERM_WRITE BT_GATT_PERM_WRITE_ENCRYPT |
| #endif |
| |
| /* HID Service Declaration */ |
| 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, &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, |
| SAMPLE_BT_PERM_READ, |
| read_input_report, NULL, NULL), |
| BT_GATT_CCC(input_ccc_changed, |
| SAMPLE_BT_PERM_READ | SAMPLE_BT_PERM_WRITE), |
| BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ, |
| read_report, NULL, &input), |
| BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, |
| BT_GATT_CHRC_WRITE_WITHOUT_RESP, |
| BT_GATT_PERM_WRITE, |
| NULL, write_ctrl_point, &ctrl_point), |
| ); |
| |
| void hog_init(void) |
| { |
| } |
| |
| #define SW0_NODE DT_ALIAS(sw0) |
| |
| void hog_button_loop(void) |
| { |
| #if DT_NODE_HAS_STATUS(SW0_NODE, okay) |
| const struct gpio_dt_spec sw0 = GPIO_DT_SPEC_GET(SW0_NODE, gpios); |
| |
| gpio_pin_configure_dt(&sw0, GPIO_INPUT); |
| |
| for (;;) { |
| if (simulate_input) { |
| /* HID Report: |
| * Byte 0: buttons (lower 3 bits) |
| * Byte 1: X axis (int8) |
| * Byte 2: Y axis (int8) |
| */ |
| int8_t report[3] = {0, 0, 0}; |
| |
| if (gpio_pin_get_dt(&sw0)) { |
| report[0] |= BIT(0); |
| } |
| |
| bt_gatt_notify(NULL, &hog_svc.attrs[5], |
| report, sizeof(report)); |
| } |
| k_sleep(K_MSEC(100)); |
| } |
| #endif |
| } |