diff --git a/example/Makefile.inc b/example/Makefile.inc index 7cdf21310..02ebd697a 100644 --- a/example/Makefile.inc +++ b/example/Makefile.inc @@ -118,7 +118,8 @@ EXAMPLES = \ hfp_ag_demo \ hfp_hf_demo \ hid_keyboard_demo \ - hog_keyboard_demo \ + hog_keyboard_demo \ + hog_mouse_demo \ hsp_ag_demo \ hsp_hs_demo \ le_counter \ @@ -148,7 +149,8 @@ EXAMPLES_USING_LE = \ gap_le_advertisements \ sm_pairing_peripheral \ sm_pairing_central \ - hog_keyboard_demo \ + hog_keyboard_demo \ + hog_mouse_demo \ # .o for .c CORE_OBJ = $(CORE:.c=.o) @@ -168,25 +170,7 @@ HXCMOD_PLAYER_OBJ = $(HXCMOD_PLAYER:.c=.o) default_target: all # compile .gatt descriptions -profile.h: profile.gatt - python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ -ancs_client_demo.h: ancs_client_demo.gatt - python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ -gatt_browser.h: gatt_browser.gatt - python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ -gatt_battery_query.h: gatt_battery_query.gatt - python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ -spp_and_le_counter.h: spp_and_le_counter.gatt - python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ -spp_and_le_streamer.h: spp_and_le_counter.gatt - python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ -le_counter.h: le_counter.gatt - python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ -le_streamer.h: le_streamer.gatt - python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ -hog_keyboard_demo.h: hog_keyboard_demo.gatt - python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ -sm_pairing_peripheral.h: sm_pairing_peripheral.gatt +%.h: %.gatt python ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ # examples @@ -211,6 +195,9 @@ le_counter: le_counter.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ} hog_keyboard_demo: hog_keyboard_demo.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ} ${SM_OBJ} battery_service_server.o device_information_service_server.o hids_device.o btstack_ring_buffer.o hog_keyboard_demo.c ${CC} $(filter-out hog_keyboard_demo.h,$^) ${CFLAGS} ${LDFLAGS} -o $@ +hog_mouse_demo: hog_mouse_demo.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ} ${SM_OBJ} battery_service_server.o device_information_service_server.o hids_device.o hog_mouse_demo.c + ${CC} $(filter-out hog_mouse_demo.h,$^) ${CFLAGS} ${LDFLAGS} -o $@ + sm_pairing_peripheral: sm_pairing_peripheral.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ} ${SM_OBJ} sm_pairing_peripheral.c ${CC} $(filter-out sm_pairing_peripheral.h,$^) ${CFLAGS} ${LDFLAGS} -o $@ @@ -283,7 +270,7 @@ a2dp_sink_demo: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${SDP_CLIENT} ${SBC_ENC clean: rm -f ${EXAMPLES} *_demo rm -f *.o *.out *.hex *.exe *.wav *.sbc - rm -f ancs_client_demo.h profile.h spp_and_le_counter.h le_counter.h le_streamer.h + rm -f ancs_client_demo.h profile.h spp_and_le_counter.h le_counter.h le_streamer.h hog_keyboard_demo.h hog_mouse_demo.h rm -f gatt_battery_query.h gatt_browser.h sm_pairing_peripheral.h spp_and_le_streamer.h rm -rf *.dSYM rm -rf ${BTSTACK_ROOT}/src/*.o diff --git a/example/hog_mouse_demo.c b/example/hog_mouse_demo.c new file mode 100644 index 000000000..98950c2e8 --- /dev/null +++ b/example/hog_mouse_demo.c @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2017 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#define __BTSTACK_FILE__ "hog_mouse_demo.c" + +// ***************************************************************************** +/* EXAMPLE_START(hog_mouse_demo): HID-over-GATT Mouse + */ +// ***************************************************************************** + +#include +#include +#include +#include +#include + +#include "hog_mouse_demo.h" + +#include "btstack.h" + +#include "ble/gatt-service/battery_service_server.h" +#include "ble/gatt-service/device_information_service_server.h" +#include "ble/gatt-service/hids_device.h" + +// from USB HID Specification 1.1, Appendix B.2 +const uint8_t hid_descriptor_mouse_boot_mode[] = { + 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) + +#if 1 + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x03, // USAGE_MAXIMUM (Button 3) + 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) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x05, // REPORT_SIZE (5) + 0x81, 0x03, // INPUT (Cnst,Var,Abs) +#endif + +#if 1 + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x02, // REPORT_COUNT (2) + 0x81, 0x06, // INPUT (Data,Var,Rel) +#endif + + 0xc0, // END_COLLECTION + 0xc0 // END_COLLECTION +}; + +static btstack_packet_callback_registration_t hci_event_callback_registration; +static btstack_packet_callback_registration_t sm_event_callback_registration; +static uint8_t battery = 100; +static hci_con_handle_t con_handle = HCI_CON_HANDLE_INVALID; + +static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); + +const uint8_t adv_data[] = { + // Flags general discoverable, BR/EDR not supported + 0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06, + // Name + 0x0a, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'H', 'I', 'D', ' ', 'M', 'o', 'u', 's', 'e', + // 16-bit Service UUIDs + 0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE & 0xff, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE >> 8, +}; +const uint8_t adv_data_len = sizeof(adv_data); + +static void hog_mouse_setup(void){ + + // register for HCI events + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + l2cap_init(); + + // setup le device db + le_device_db_init(); + + // setup SM: Display only + sm_init(); + sm_event_callback_registration.callback = &packet_handler; + sm_add_event_handler(&sm_event_callback_registration); + sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY); + // sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING); + sm_set_authentication_requirements(SM_AUTHREQ_BONDING); + + // setup ATT server + att_server_init(profile_data, NULL, NULL); + + // setup battery service + battery_service_server_init(battery); + + // setup device information service + device_information_service_server_init(); + + // setup HID Device service + hids_device_init(0, hid_descriptor_mouse_boot_mode, sizeof(hid_descriptor_mouse_boot_mode)); + hids_device_register_packet_handler(packet_handler); + + // setup advertisements + uint16_t adv_int_min = 0x0030; + uint16_t adv_int_max = 0x0030; + uint8_t adv_type = 0; + bd_addr_t null_addr; + memset(null_addr, 0, 6); + gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00); + gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data); + gap_advertisements_enable(1); +} + +// HID Report sending +static void send_report(uint8_t buttons, int8_t dx, int8_t dy){ + // uint8_t report[] = { (uint8_t) dx, (uint8_t) dy, buttons}; + uint8_t report[] = { buttons, (uint8_t) dx, (uint8_t) dy}; + hids_device_send_input_report(con_handle, report, sizeof(report)); + printf("Mouse: %d/%d - buttons: %02x\n", dx, dy, buttons); +} + +static int dx; +static int dy; +static uint8_t buttons; + +static void mousing_can_send_now(void){ + send_report(buttons, dx, dy); + // reset + dx = 0; + dy = 0; + if (buttons){ + buttons = 0; + hids_device_request_can_send_now_event(con_handle); + } +} + +// Demo Application + +#ifdef HAVE_BTSTACK_STDIN + +static const int MOUSE_SPEED = 30; + +// On systems with STDIN, we can directly type on the console + +static void stdin_process(char character){ + + if (con_handle == HCI_CON_HANDLE_INVALID) { + printf("Mouse not connected, ignoring '%c'\n", character); + return; + } + + switch (character){ + case 'a': + dx -= MOUSE_SPEED; + break; + case 's': + dy += MOUSE_SPEED; + break; + case 'd': + dx += MOUSE_SPEED; + break; + case 'w': + dy -= MOUSE_SPEED; + break; + case 'l': + buttons |= 1; + break; + case 'r': + buttons |= 2; + break; + default: + return; + } + hids_device_request_can_send_now_event(con_handle); +} + +#else + +// On embedded systems, simulate clicking on 4 corners of a square + +#define MOUSE_PERIOD_MS 15 + +static int step; +static const int STEPS_PER_DIRECTION = 50; +static const int MOUSE_SPEED = 10; + +static struct { + int dx; + int dy; +} directions[] = { + { 1, 0 }, + { 0, 1 }, + { -1, 0 }, + { 0, -1 }, +}; + +static btstack_timer_source_t mousing_timer; + +static void mousing_timer_handler(btstack_timer_source_t * ts){ + + if (con_handle == HCI_CON_HANDLE_INVALID) return; + + // simulate left click when corner reached + if (step % STEPS_PER_DIRECTION == 0){ + buttons |= 1; + } + // simulate move + int direction_index = step / STEPS_PER_DIRECTION; + dx += directions[direction_index].dx * MOUSE_SPEED; + dy += directions[direction_index].dy * MOUSE_SPEED; + + // next + step++; + if (step >= STEPS_PER_DIRECTION * 4) { + step = 0; + } + + // trigger send + hids_device_request_can_send_now_event(con_handle); + + // set next timer + btstack_run_loop_set_timer(ts, MOUSE_PERIOD_MS); + btstack_run_loop_add_timer(ts); +} + +static void hid_embedded_start_mousing(void){ + printf("Start mousing..\n"); + + step = 0; + + // set one-shot timer + mousing_timer.process = &mousing_timer_handler; + btstack_run_loop_set_timer(&mousing_timer, MOUSE_PERIOD_MS); + btstack_run_loop_add_timer(&mousing_timer); +} + +#endif + +static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + + switch (packet_type) { + case HCI_EVENT_PACKET: + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_DISCONNECTION_COMPLETE: + printf("Disconnected\n"); + break; + case SM_EVENT_JUST_WORKS_REQUEST: + printf("Just Works requested\n"); + sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); + break; + case SM_EVENT_NUMERIC_COMPARISON_REQUEST: + printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet)); + sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet)); + break; + case SM_EVENT_PASSKEY_DISPLAY_NUMBER: + printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet)); + break; + case HCI_EVENT_HIDS_META: + switch (hci_event_hids_meta_get_subevent_code(packet)){ + case HIDS_SUBEVENT_INPUT_REPORT_ENABLE: + con_handle = hids_subevent_input_report_enable_get_con_handle(packet); + printf("Report Characteristic Subscribed %u\n", hids_subevent_input_report_enable_get_enable(packet)); +#ifndef HAVE_BTSTACK_STDIN + hid_embedded_start_mousing(); +#endif + break; + case HIDS_SUBEVENT_BOOT_KEYBOARD_INPUT_REPORT_ENABLE: + con_handle = hids_subevent_input_report_enable_get_con_handle(packet); + printf("Boot Keyboard Characteristic Subscribed %u\n", hids_subevent_boot_keyboard_input_report_enable_get_enable(packet)); + break; + case HIDS_SUBEVENT_BOOT_MOUSE_INPUT_REPORT_ENABLE: + con_handle = hids_subevent_input_report_enable_get_con_handle(packet); + printf("Boot Mouse Characteristic Subscribed %u\n", hids_subevent_boot_mouse_input_report_enable_get_enable(packet)); + break; + case HIDS_SUBEVENT_PROTOCOL_MODE: + printf("Protocol Mode: %s mode\n", hids_subevent_protocol_mode_get_protocol_mode(packet) ? "Report" : "Boot"); + break; + case HIDS_SUBEVENT_CAN_SEND_NOW: + mousing_can_send_now(); + break; + default: + break; + } + } + break; + } +} + +int btstack_main(void); +int btstack_main(void) +{ + hog_mouse_setup(); + +#ifdef HAVE_BTSTACK_STDIN + btstack_stdin_setup(stdin_process); +#endif + + // turn on! + hci_power_control(HCI_POWER_ON); + + return 0; +} diff --git a/example/hog_mouse_demo.gatt b/example/hog_mouse_demo.gatt new file mode 100644 index 000000000..ff8bf8f86 --- /dev/null +++ b/example/hog_mouse_demo.gatt @@ -0,0 +1,14 @@ +PRIMARY_SERVICE, GAP_SERVICE +CHARACTERISTIC, GAP_DEVICE_NAME, READ, "HID Mouse" + +// add Battery Service +#import + +// add Device ID Service +#import + +// add HID Service +#import + +PRIMARY_SERVICE, GATT_SERVICE +CHARACTERISTIC, GATT_SERVICE_CHANGED, READ,