|
| 1 | +#include <chrono> |
| 2 | +#include <vector> |
| 3 | + |
| 4 | +#include "ble_gatt_server.hpp" |
| 5 | +#include "hid_service.hpp" |
| 6 | + |
| 7 | +#include "hid-rp-gamepad.hpp" |
| 8 | + |
| 9 | +using namespace std::chrono_literals; |
| 10 | + |
| 11 | +extern "C" void app_main(void) { |
| 12 | + espp::Logger logger({.tag = "Hid Service Example", .level = espp::Logger::Verbosity::INFO}); |
| 13 | + logger.info("Starting"); |
| 14 | + |
| 15 | + //! [hid service example] |
| 16 | + |
| 17 | + // NOTE: esp-nimble-cpp already depends on nvs_flash and initializes |
| 18 | + // nvs_flash in the NimBLEDevice::init(), so we don't have to do that |
| 19 | + // to store bonding info |
| 20 | + |
| 21 | + // create the GATT server |
| 22 | + espp::BleGattServer ble_gatt_server; |
| 23 | + std::string device_name = "ESP++ HID"; |
| 24 | + ble_gatt_server.set_log_level(espp::Logger::Verbosity::INFO); |
| 25 | + ble_gatt_server.set_callbacks({ |
| 26 | + .connect_callback = [&](NimBLEConnInfo &conn_info) { logger.info("Device connected"); }, |
| 27 | + .disconnect_callback = [&](NimBLEConnInfo &conn_info) { logger.info("Device disconnected"); }, |
| 28 | + .authentication_complete_callback = |
| 29 | + [&](NimBLEConnInfo &conn_info) { logger.info("Device authenticated"); }, |
| 30 | + }); |
| 31 | + ble_gatt_server.init(device_name); |
| 32 | + ble_gatt_server.set_advertise_on_disconnect(true); |
| 33 | + |
| 34 | + // for HID we need to set some security |
| 35 | + bool bonding = true; |
| 36 | + bool mitm = false; |
| 37 | + bool secure_connections = true; |
| 38 | + ble_gatt_server.set_security(bonding, mitm, secure_connections); |
| 39 | + // and some i/o and key config |
| 40 | + ble_gatt_server.set_io_capabilities(BLE_HS_IO_NO_INPUT_OUTPUT); |
| 41 | + ble_gatt_server.set_init_key_distribution(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); |
| 42 | + ble_gatt_server.set_resp_key_distribution(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); |
| 43 | + |
| 44 | + // let's create a HID service |
| 45 | + espp::HidService hid_service; |
| 46 | + hid_service.init(ble_gatt_server.server()); |
| 47 | + |
| 48 | + // configure it some |
| 49 | + uint8_t country_code = 0x00; |
| 50 | + uint8_t hid_info_flags = 0x01; |
| 51 | + hid_service.set_info(country_code, hid_info_flags); |
| 52 | + |
| 53 | + static constexpr uint8_t report_id = 1; |
| 54 | + static constexpr size_t num_buttons = 15; |
| 55 | + static constexpr int joystick_min = 0; |
| 56 | + static constexpr int joystick_max = 65535; |
| 57 | + static constexpr int trigger_min = 0; |
| 58 | + static constexpr int trigger_max = 1024; |
| 59 | + |
| 60 | + using Gamepad = espp::GamepadReport<num_buttons, joystick_min, joystick_max, trigger_min, |
| 61 | + trigger_max, report_id>; |
| 62 | + Gamepad gamepad_input_report; |
| 63 | + |
| 64 | + // Generate the report descriptor for the gamepad |
| 65 | + auto descriptor = gamepad_input_report.get_descriptor(); |
| 66 | + |
| 67 | + logger.info("Report Descriptor:"); |
| 68 | + logger.info(" Size: {}", descriptor.size()); |
| 69 | + logger.info(" Data: {::#02x}", descriptor); |
| 70 | + |
| 71 | + // set the report map (vector of bytes) |
| 72 | + hid_service.set_report_map(descriptor); |
| 73 | + |
| 74 | + // use the HID service to make an input report characteristic |
| 75 | + auto input_report = hid_service.input_report(report_id); |
| 76 | + |
| 77 | + // now that we've made the input characteristic, we can start the service |
| 78 | + hid_service.start(); |
| 79 | + // starts the device info service and battery service, see |
| 80 | + // hid_service_example for more info |
| 81 | + ble_gatt_server.start_services(); |
| 82 | + |
| 83 | + // now start the gatt server |
| 84 | + ble_gatt_server.start(); |
| 85 | + |
| 86 | + // let's set some of the service data |
| 87 | + auto &battery_service = ble_gatt_server.battery_service(); |
| 88 | + battery_service.set_battery_level(99); |
| 89 | + |
| 90 | + auto &device_info_service = ble_gatt_server.device_info_service(); |
| 91 | + uint8_t vendor_source = 0x02; // USB |
| 92 | + uint16_t vid = 0x045E; // Microsoft |
| 93 | + uint16_t pid = 0x02FD; // Xbox One Controller |
| 94 | + uint16_t product_version = 0x0100; |
| 95 | + device_info_service.set_pnp_id(vendor_source, vid, pid, product_version); |
| 96 | + device_info_service.set_manufacturer_name("ESP-CPP"); |
| 97 | + device_info_service.set_model_number("esp-hid-01"); |
| 98 | + device_info_service.set_serial_number("1234567890"); |
| 99 | + device_info_service.set_software_version("1.0.0"); |
| 100 | + device_info_service.set_firmware_version("1.0.0"); |
| 101 | + device_info_service.set_hardware_version("1.0.0"); |
| 102 | + |
| 103 | + // now lets start advertising |
| 104 | + espp::BleGattServer::AdvertisingData adv_data = { |
| 105 | + .name = device_name, |
| 106 | + .appearance = 0x03C4, // Gamepad |
| 107 | + .services = |
| 108 | + { |
| 109 | + // these are the services that we want to advertise |
| 110 | + hid_service.uuid(), // hid service |
| 111 | + }, |
| 112 | + .service_data = |
| 113 | + { |
| 114 | + // these are the service data that we want to advertise |
| 115 | + }, |
| 116 | + }; |
| 117 | + espp::BleGattServer::AdvertisingParameters adv_params = {}; |
| 118 | + ble_gatt_server.start_advertising(adv_data, adv_params); |
| 119 | + |
| 120 | + // now lets update the battery level and send an input report every second |
| 121 | + uint8_t battery_level = 99; |
| 122 | + // change the gamepad inputs every second |
| 123 | + int button_index = 1; |
| 124 | + while (true) { |
| 125 | + auto start = std::chrono::steady_clock::now(); |
| 126 | + |
| 127 | + // update the battery level |
| 128 | + battery_service.set_battery_level(battery_level); |
| 129 | + battery_level = (battery_level % 100) + 1; |
| 130 | + |
| 131 | + // cycle through the possible d-pad states |
| 132 | + Gamepad::Hat hat = (Gamepad::Hat)button_index; |
| 133 | + // use the button index to set the position of the right joystick |
| 134 | + float angle = 2.0f * M_PI * button_index / num_buttons; |
| 135 | + |
| 136 | + gamepad_input_report.reset(); |
| 137 | + gamepad_input_report.set_hat(hat); |
| 138 | + gamepad_input_report.set_button(button_index, true); |
| 139 | + // joystick inputs are in the range [-1, 1] float |
| 140 | + gamepad_input_report.set_right_joystick(cos(angle), sin(angle)); |
| 141 | + gamepad_input_report.set_left_joystick(sin(angle), cos(angle)); |
| 142 | + // trigger inputs are in the range [0, 1] float |
| 143 | + gamepad_input_report.set_accelerator(std::abs(sin(angle))); |
| 144 | + gamepad_input_report.set_brake(std::abs(cos(angle))); |
| 145 | + |
| 146 | + button_index = (button_index % num_buttons) + 1; |
| 147 | + |
| 148 | + // send an input report |
| 149 | + auto report = gamepad_input_report.get_report(); |
| 150 | + logger.debug("Sending report data ({}): {::#02x}", report.size(), report); |
| 151 | + input_report->notify(report); |
| 152 | + |
| 153 | + // sleep |
| 154 | + std::this_thread::sleep_until(start + 1s); |
| 155 | + } |
| 156 | + //! [hid service example] |
| 157 | +} |
0 commit comments