API references and example programs related to Chain DualKey BLE HID.
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEHIDDevice.h>
#include <HIDTypes.h>
const int PIN_BTN_1 = 0;
const int PIN_BTN_2 = 17;
// HID keyboard report structure: 1 byte modifiers + 1 byte reserved + 6 bytes keycodes
struct KeyReport {
uint8_t modifiers;
uint8_t reserved;
uint8_t keys[6];
};
// HID keyboard report map
static const uint8_t hidReportMap[] = {
0x05, 0x01, // Usage Page = Generic Desktop
0x09, 0x06, // Usage = Keyboard
0xA1, 0x01, // Collection = Application
0x85, 0x01, // Report ID = 1
0x75, 0x01, // Report Size = 1
0x95, 0x08, // Report Count = 8
0x05, 0x07, // Usage Page = Key Codes
0x19, 0xE0, // Usage Minimum = Keyboard LeftControl
0x29, 0xE7, // Usage Maximum = Keyboard Right GUI
0x15, 0x00, // Logical Minimum = 0
0x25, 0x01, // Logical Maximum = 1
0x81, 0x02, // Input = Data, Variable, Absolute (1 byte Modifiers)
0x75, 0x08, // Report Size = 8
0x95, 0x01, // Report Count = 1
0x81, 0x01, // Input = Constant (1 byte Reserved)
0x75, 0x08, // Report Size = 8
0x95, 0x06, // Report Count = 6
0x05, 0x07, // Usage Page = Key Codes
0x19, 0x00, // Usage Minimum = Reserved
0x29, 0x65, // Usage Maximum = Keyboard Application
0x15, 0x00, // Logical Minimum = 0
0x25, 0x65, // Logical Maximum = 101
0x81, 0x00, // Input = Data, Array (6 bytes Keycodes)
0xC0 // End Collection
};
BLEHIDDevice* hidDevice;
BLECharacteristic* inputReportCharacteristic;
bool deviceConnected = false;
// Connect status callback
class MyBLEServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) override {
deviceConnected = true;
Serial.println("BLE connected");
}
void onDisconnect(BLEServer* pServer) override {
deviceConnected = false;
Serial.println("BLE disconnected");
BLEDevice::startAdvertising(); // Restart advertising after disconnected
}
};
// Function to report a key was pressed and released, see keycode definitions:
// https://www.usb.org/sites/default/files/hut1_6.pdf
void sendKey(uint8_t keycode, uint8_t modifier = 0) {
if (!deviceConnected || inputReportCharacteristic == nullptr) {
return;
}
KeyReport report = { 0 };
// Pressed
report.modifiers = modifier;
report.keys[0] = keycode;
inputReportCharacteristic->setValue((uint8_t*)&report, sizeof(report));
inputReportCharacteristic->notify();
delay(10);
// Released (clean)
report.modifiers = 0;
report.keys[0] = 0;
inputReportCharacteristic->setValue((uint8_t*)&report, sizeof(report));
inputReportCharacteristic->notify();
}
void setup() {
delay(2000);
Serial.begin(115200);
Serial.println("Starting Chain DualKey BLE Keyboard");
pinMode(PIN_BTN_1, INPUT);
pinMode(PIN_BTN_2, INPUT);
// Init BLE device, create BLE server, create HID device, input report (ID=1)
BLEDevice::init("Chain DualKey Keyboard"); // Device name shown on computers and phones
BLEServer* pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyBLEServerCallbacks());
hidDevice = new BLEHIDDevice(pServer);
inputReportCharacteristic = hidDevice->inputReport(1);
// Set HID device info
hidDevice->manufacturer()->setValue("M5Stack");
hidDevice->pnp(0x02, 0x1234, 0x0001, 0x0100); // vendorIdSource (0x02=Bluetooth SIG), vendorId, productId, version
hidDevice->hidInfo(0x00, 0x01);
// Set HID report map
hidDevice->reportMap((uint8_t*)hidReportMap, sizeof(hidReportMap));
hidDevice->startServices();
// Set BLE security (BOND)
BLESecurity* pSecurity = new BLESecurity();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);
// Set BLE advertising
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->setAppearance(HID_KEYBOARD);
pAdvertising->addServiceUUID(hidDevice->hidService()->getUUID());
pAdvertising->start();
Serial.println("BLE HID Keyboard is advertising, ready to pair.");
}
void loop() {
static bool lastBtnState_1 = HIGH;
static bool lastBtnState_2 = HIGH;
bool btnState_1 = digitalRead(PIN_BTN_1);
bool btnState_2 = digitalRead(PIN_BTN_2);
if (lastBtnState_1 == HIGH && btnState_1 == LOW) {
Serial.println("Button 1 pressed, send 'a'");
sendKey(0x04); // 'a' + no modifier
}
if (lastBtnState_2 == HIGH && btnState_2 == LOW) {
Serial.println("Button 2 pressed, send 'v' + LeftCtrl");
sendKey(0x19, 0x01); // 'v' + LeftCtrl
// sendKey(0x19, 0x08); // 'v' + LeftCmd
}
lastBtnState_1 = btnState_1;
lastBtnState_2 = btnState_2;
delay(10);
}Copy the code above into Arduino IDE, then compile and upload it to Chain DualKey. After the upload is complete, go to the Bluetooth settings on your PC, phone, or other host device, and pair with the keyboard named Chain DualKey Keyboard. Pressing Key1 will input the letter a, and pressing Key2 will trigger the Ctrl + V key combination to paste on Windows (for macOS Command key usage, refer to the comments in the code).
The serial output looks like this:
If there is no serial output after uploading, or if the Bluetooth device cannot be found, you can restart the device. To do so, move the switch to the middle position, unplug the USB-C cable and reconnect it (do not hold Key1).
10 Keyboard/Keypad Page section in HID Usage Tables 1.6.Ctrl, Shift, Alt, GUI (Windows/Command) do not use the normal key codes listed above. They use a separate modifiers field. See the 8.3 Report Format for Array Items section in Device Class Definition for HID 1.12.