NimBLE Keyboard Sample - palette-system/az-macro GitHub Wiki
NimBLEになってからセキュリティの設定の仕方が変わったらしく下記が必要だったのと
NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND);それだけではダメだったので おそらく NimBLEHIDDevice クラスと NimBLESecurity クラスの中の動きが少し変わったのではないかという結論です。
こちらのソースを参考に、ESP32-NimBLE-KeyboardのhidReportDescriptorで動かせるようにコードを変更しました。
参考にしたソース: https://gist.github.com/jins-tkomoda/dbc3d02198780a8b2ee2e2f04ba3c92b
こちらがiOSでもペアリング出来て、10秒おきに a を押すだけのサンプルソースです。
ただゴリゴリBLEのコードベタ書きなので参考程度にご参照下さい。
/**
* NimBLE-Arduino HID Mouse
*/
// 参考 : https://gist.github.com/jins-tkomoda/dbc3d02198780a8b2ee2e2f04ba3c92b
#include <NimBLEDevice.h>
#include "HIDKeyboardTypes.h"
#include "HIDTypes.h"
// Appearance
#define GENERIC_HID 0x03C0
#define HID_KEYBOARD 0x03C1
#define HID_MOUSE 0x03C2
#define HID_JOYSTICK 0x03C3
#define HID_GAMEPAD 0x03C4
#define HID_TABLET 0x03C5
#define HID_CARD_READER 0x03C6
#define HID_DIGITAL_PEN 0x03C7
#define HID_BARCODE 0x03C8
/*
* Keyboard
*/
#define KEYBOARD_ID 0x01
#define MEDIA_KEYS_ID 0x02
const uint8_t reportMap[] = {
USAGE_PAGE(1), 0x01, // USAGE_PAGE (Generic Desktop Ctrls)
USAGE(1), 0x06, // USAGE (Keyboard)
COLLECTION(1), 0x01, // COLLECTION (Application)
// ------------------------------------------------- Keyboard
REPORT_ID(1), KEYBOARD_ID, // REPORT_ID (1)
USAGE_PAGE(1), 0x07, // USAGE_PAGE (Kbrd/Keypad)
USAGE_MINIMUM(1), 0xE0, // USAGE_MINIMUM (0xE0)
USAGE_MAXIMUM(1), 0xE7, // USAGE_MAXIMUM (0xE7)
LOGICAL_MINIMUM(1), 0x00, // LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM(1), 0x01, // Logical Maximum (1)
REPORT_SIZE(1), 0x01, // REPORT_SIZE (1)
REPORT_COUNT(1), 0x08, // REPORT_COUNT (8)
HIDINPUT(1), 0x02, // INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
REPORT_COUNT(1), 0x01, // REPORT_COUNT (1) ; 1 byte (Reserved)
REPORT_SIZE(1), 0x08, // REPORT_SIZE (8)
HIDINPUT(1), 0x01, // INPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
REPORT_COUNT(1), 0x05, // REPORT_COUNT (5) ; 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
REPORT_SIZE(1), 0x01, // REPORT_SIZE (1)
USAGE_PAGE(1), 0x08, // USAGE_PAGE (LEDs)
USAGE_MINIMUM(1), 0x01, // USAGE_MINIMUM (0x01) ; Num Lock
USAGE_MAXIMUM(1), 0x05, // USAGE_MAXIMUM (0x05) ; Kana
HIDOUTPUT(1), 0x02, // OUTPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
REPORT_COUNT(1), 0x01, // REPORT_COUNT (1) ; 3 bits (Padding)
REPORT_SIZE(1), 0x03, // REPORT_SIZE (3)
HIDOUTPUT(1), 0x01, // OUTPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
REPORT_COUNT(1), 0x06, // REPORT_COUNT (6) ; 6 bytes (Keys)
REPORT_SIZE(1), 0x08, // REPORT_SIZE(8)
LOGICAL_MINIMUM(1), 0x00, // LOGICAL_MINIMUM(0)
LOGICAL_MAXIMUM(1), 0x65, // LOGICAL_MAXIMUM(0x65) ; 101 keys
USAGE_PAGE(1), 0x07, // USAGE_PAGE (Kbrd/Keypad)
USAGE_MINIMUM(1), 0x00, // USAGE_MINIMUM (0)
USAGE_MAXIMUM(1), 0x65, // USAGE_MAXIMUM (0x65)
HIDINPUT(1), 0x00, // INPUT (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
END_COLLECTION(0), // END_COLLECTION
// ------------------------------------------------- Media Keys
USAGE_PAGE(1), 0x0C, // USAGE_PAGE (Consumer)
USAGE(1), 0x01, // USAGE (Consumer Control)
COLLECTION(1), 0x01, // COLLECTION (Application)
REPORT_ID(1), MEDIA_KEYS_ID, // REPORT_ID (3)
USAGE_PAGE(1), 0x0C, // USAGE_PAGE (Consumer)
LOGICAL_MINIMUM(1), 0x00, // LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM(1), 0x01, // LOGICAL_MAXIMUM (1)
REPORT_SIZE(1), 0x01, // REPORT_SIZE (1)
REPORT_COUNT(1), 0x10, // REPORT_COUNT (16)
USAGE(1), 0xB5, // USAGE (Scan Next Track) ; bit 0: 1
USAGE(1), 0xB6, // USAGE (Scan Previous Track) ; bit 1: 2
USAGE(1), 0xB7, // USAGE (Stop) ; bit 2: 4
USAGE(1), 0xCD, // USAGE (Play/Pause) ; bit 3: 8
USAGE(1), 0xE2, // USAGE (Mute) ; bit 4: 16
USAGE(1), 0xE9, // USAGE (Volume Increment) ; bit 5: 32
USAGE(1), 0xEA, // USAGE (Volume Decrement) ; bit 6: 64
USAGE(2), 0x23, 0x02, // Usage (WWW Home) ; bit 7: 128
USAGE(2), 0x94, 0x01, // Usage (My Computer) ; bit 0: 1
USAGE(2), 0x92, 0x01, // Usage (Calculator) ; bit 1: 2
USAGE(2), 0x2A, 0x02, // Usage (WWW fav) ; bit 2: 4
USAGE(2), 0x21, 0x02, // Usage (WWW search) ; bit 3: 8
USAGE(2), 0x26, 0x02, // Usage (WWW stop) ; bit 4: 16
USAGE(2), 0x24, 0x02, // Usage (WWW back) ; bit 5: 32
USAGE(2), 0x83, 0x01, // Usage (Media sel) ; bit 6: 64
USAGE(2), 0x8A, 0x01, // Usage (Mail) ; bit 7: 128
HIDINPUT(1), 0x02, // INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
END_COLLECTION(0) // END_COLLECTION
};
static NimBLEServer* pServer;
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
class ServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer) {
Serial.println("Client connected");
Serial.println("Multi-connect support: start advertising");
NimBLEDevice::startAdvertising();
};
/** Alternative onConnect() method to extract details of the connection.
* See: src/ble_gap.h for the details of the ble_gap_conn_desc struct.
*/
void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
Serial.print("Client address: ");
Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str());
/** We can use the connection handle here to ask for different connection parameters.
* Args: connection handle, min connection interval, max connection interval
* latency, supervision timeout.
* Units; Min/Max Intervals: 1.25 millisecond increments.
* Latency: number of intervals allowed to skip.
* Timeout: 10 millisecond increments, try for 5x interval time for best results.
*/
pServer->updateConnParams(desc->conn_handle, 0x10, 0x20, 0, 600);
};
void onDisconnect(NimBLEServer* pServer) {
Serial.println("Client disconnected - start advertising");
NimBLEDevice::startAdvertising();
};
/********************* Security handled here **********************
****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){
Serial.println("Server Passkey Request");
/** This should return a random 6 digit number for security
* or make your own static passkey as done here.
*/
return 123456;
};
bool onConfirmPIN(uint32_t pass_key){
Serial.print("The passkey YES/NO number: ");Serial.println(pass_key);
/** Return false if passkeys don't match. */
return true;
};
void onAuthenticationComplete(ble_gap_conn_desc* desc){
/** Check that encryption was successful, if not we disconnect the client */
if(!desc->sec_state.encrypted) {
NimBLEDevice::getServer()->disconnect(desc->conn_handle);
Serial.println("Encrypt connection failed - disconnecting client");
return;
}
Serial.println("Starting BLE work!");
};
};
/** Handler class for characteristic actions */
class CharacteristicCallbacks: public NimBLECharacteristicCallbacks {
void onRead(NimBLECharacteristic* pCharacteristic){
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(": onRead(), value: ");
Serial.println(pCharacteristic->getValue().c_str());
};
void onWrite(NimBLECharacteristic* pCharacteristic) {
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(": onWrite(), value: ");
Serial.println(pCharacteristic->getValue().c_str());
};
/** Called before notification or indication is sent,
* the value can be changed here before sending if desired.
*/
void onNotify(NimBLECharacteristic* pCharacteristic) {
Serial.println("Sending notification to clients");
};
/** The status returned in status is defined in NimBLECharacteristic.h.
* The value returned in code is the NimBLE host return code.
*/
void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) {
String str = ("Notf/Ind stscode: ");
str += status;
str += ", retcode: ";
str += code;
str += ", ";
str += NimBLEUtils::returnCodeToString(code);
//Serial.print(str);
};
void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) {
String str = "Client ID: ";
str += desc->conn_handle;
str += " Address: ";
str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str();
if(subValue == 0) {
str += " Unsubscribed to ";
}else if(subValue == 1) {
str += " Subscribed to notfications for ";
} else if(subValue == 2) {
str += " Subscribed to indications for ";
} else if(subValue == 3) {
str += " Subscribed to notifications and indications for ";
}
str += std::string(pCharacteristic->getUUID()).c_str();
Serial.println(str);
};
};
/** Handler class for descriptor actions */
class DescriptorCallbacks : public NimBLEDescriptorCallbacks {
void onWrite(NimBLEDescriptor* pDescriptor) {
std::string dscVal((char*)pDescriptor->getValue(), pDescriptor->getLength());
Serial.print("Descriptor witten value:");
Serial.println(dscVal.c_str());
};
void onRead(NimBLEDescriptor* pDescriptor) {
Serial.print(pDescriptor->getUUID().toString().c_str());
Serial.println(" Descriptor read");
};
};
/** Define callback instances globally to use for multiple Charateristics \ Descriptors */
static DescriptorCallbacks dscCallbacks;
static CharacteristicCallbacks chrCallbacks;
NimBLECharacteristic* pInputCharacteristic;
void setup() {
Serial.begin(115200);
Serial.println("Starting NimBLE HID Server");
/** sets device name */
NimBLEDevice::init("NimBLE-Hid");
NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */
NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND);
pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
//DeviceInfoService
NimBLEService* pDeviceInfoService = pServer->createService("180A"); // <-デバイスインフォのUUID
NimBLECharacteristic* pPnpCharacteristic = pDeviceInfoService->createCharacteristic("2A50", NIMBLE_PROPERTY::READ);
uint8_t sig = 0x02;
uint16_t vid = 0xe502;
uint16_t pid = 0xa111;
uint16_t version = 0x0210;
uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version };
pPnpCharacteristic->setValue(pnp, sizeof(pnp));
pPnpCharacteristic->setCallbacks(&chrCallbacks);
//DeviceInfoService-Manufacturer
NimBLECharacteristic* pManufacturerCharacteristic = pDeviceInfoService->createCharacteristic("2A29", NIMBLE_PROPERTY::READ); // 0x2a29 = メーカー名
pManufacturerCharacteristic->setValue("NimBLE Community");
pManufacturerCharacteristic->setCallbacks(&chrCallbacks);
//HidService
NimBLEService* pHidService = pServer->createService(NimBLEUUID("1812"), 40);
//HidService-hidInfo
NimBLECharacteristic* pHidInfoCharacteristic = pHidService->createCharacteristic("2A4A", NIMBLE_PROPERTY::READ);// HID Information 会社名?とか?あと何かのフラグ
uint8_t country = 0x00;
uint8_t flags = 0x01;
uint8_t info[] = { 0x11, 0x1, country, flags };
pHidInfoCharacteristic->setValue(info, sizeof(info));
pHidInfoCharacteristic->setCallbacks(&chrCallbacks);
//HidService-reportMap
NimBLECharacteristic* pReportMapCharacteristic = pHidService->createCharacteristic("2A4B",NIMBLE_PROPERTY::READ); // HID Report Map (ここでHIDのいろいろ設定
pReportMapCharacteristic->setValue((uint8_t*)reportMap, sizeof(reportMap));
pReportMapCharacteristic->setCallbacks(&chrCallbacks);
//HidService-HidControl
NimBLECharacteristic* pHidControlCharacteristic = pHidService->createCharacteristic("2A4C", NIMBLE_PROPERTY::WRITE_NR);// HID Control Point
pHidControlCharacteristic->setCallbacks(&chrCallbacks);
//HidService-protocolMode
NimBLECharacteristic* pProtocolModeCharacteristic = pHidService->createCharacteristic("2A4E",NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ); // Protocol Mode
const uint8_t pMode[] = { 0x01 }; // 0: Boot Protocol 1: Rport Protocol
pProtocolModeCharacteristic->setValue((uint8_t*) pMode, 1);
pProtocolModeCharacteristic->setCallbacks(&chrCallbacks);
//HidService-input
pInputCharacteristic = pHidService->createCharacteristic("2A4D", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC); // Report
NimBLEDescriptor* pC01Ddsc = pInputCharacteristic->createDescriptor( "2908", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC, 20); // Report Reference
uint8_t desc1_val[] = { 0x01, 0x01 }; // Report ID 1 を Input に設定
pC01Ddsc->setValue((uint8_t*) desc1_val, 2);
pC01Ddsc->setCallbacks(&dscCallbacks);
//HidService-input2 media port
NimBLECharacteristic* pInputCharacteristic2 = pHidService->createCharacteristic("2A4D", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC); // Report
NimBLEDescriptor* pC01Ddsc2 = pInputCharacteristic2->createDescriptor("2908", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC, 20);
uint8_t desc1_val2[] = { 0x02, 0x01 }; // Report ID 2 を Input に設定
pC01Ddsc2->setValue((uint8_t*) desc1_val2, 2);
pC01Ddsc2->setCallbacks(&dscCallbacks);
// HidService-output
NimBLECharacteristic* pOutputCharacteristic = pHidService->createCharacteristic("2A4D", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
pOutputCharacteristic->setCallbacks(&chrCallbacks);
NimBLEDescriptor* pDesc3 = pOutputCharacteristic->createDescriptor("2908", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC, 20);
uint8_t desc1_val3[] = { 0x01, 0x02}; // Report ID 1 を Output に設定
pDesc3->setValue((uint8_t*) desc1_val3, 2);
//BatteryService
NimBLEService* pBatteryService = pServer->createService("180F");
NimBLECharacteristic* pBatteryLevelCharacteristic = pBatteryService->createCharacteristic("2A19", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
NimBLE2904* pBatteryLevelDescriptor = (NimBLE2904*)pBatteryLevelCharacteristic->createDescriptor("2904");
pBatteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UTF8);
pBatteryLevelDescriptor->setNamespace(1);
pBatteryLevelDescriptor->setUnit(0x27ad);
pBatteryLevelDescriptor->setCallbacks(&dscCallbacks);
/** Start the services when finished creating all Characteristics and Descriptors */
pDeviceInfoService->start();
pHidService->start();
pBatteryService->start();
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->setAppearance(HID_KEYBOARD); //HID_KEYBOARD / HID_MOUSE
pAdvertising->addServiceUUID(pHidService->getUUID());
pAdvertising->setScanResponse(true);
pAdvertising->start();
Serial.println("Advertising Started");
}
void loop() {
/** Do your thing here, this just spams notifications to all connected clients */
if(pServer->getConnectedCount()) {
uint8_t a[] = {0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00};
pInputCharacteristic->setValue(a, sizeof(a));
pInputCharacteristic->notify();
delay(200);
a[2] = 0x00;
pInputCharacteristic->setValue(a, sizeof(a));
pInputCharacteristic->notify();
delay(10000);
}
delay(2000);
}