ble nano hid over gatt

Dependencies:   BLE_API mbed-dev nRF51822

KeyboardService.h

Committer:
cho45
Date:
2016-09-03
Revision:
81:fbe7358e38a4
Parent:
80:3beb0293b384
Child:
82:af52d37b1946

File content as of revision 81:fbe7358e38a4:


/* mbed Microcontroller Library
 * Copyright (c) 2015 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "HIDServiceBase.h"
#include "keyboard.h"
#include "CircularBuffer.h"

/**
 * Report descriptor for a standard 101 keys keyboard, following the HID specification example:
 * - 8 bytes input report (1 byte for modifiers and 6 for keys)
 * - 1 byte output report (LEDs)
 */
const report_map_t KEYBOARD_REPORT_MAP = {
	USAGE_PAGE(1),      0x01,       // Generic Desktop Ctrls
	USAGE(1),           0x06,       // Keyboard
	COLLECTION(1),      0x01,       // Application
		USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
		USAGE_MINIMUM(1),   0xE0,
		USAGE_MAXIMUM(1),   0xE7,
		LOGICAL_MINIMUM(1), 0x00,
		LOGICAL_MAXIMUM(1), 0x01,
		REPORT_SIZE(1),     0x01,       //   1 byte (Modifier)
		REPORT_COUNT(1),    0x08,
		INPUT(1),           0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position

		REPORT_COUNT(1),    0x01,       //   1 byte (Reserved)
		REPORT_SIZE(1),     0x08,
		INPUT(1),           0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position

		REPORT_COUNT(1),    0x05,       //   5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
		REPORT_SIZE(1),     0x01,
		USAGE_PAGE(1),      0x08,       //   LEDs
		USAGE_MINIMUM(1),   0x01,       //   Num Lock
		USAGE_MAXIMUM(1),   0x05,       //   Kana
		OUTPUT(1),          0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile

		REPORT_COUNT(1),    0x01,       //   3 bits (Padding)
		REPORT_SIZE(1),     0x03,
		OUTPUT(1),          0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile

		REPORT_COUNT(1),    0x06,       //   6 bytes (Keys)
		REPORT_SIZE(1),     0x08,
		LOGICAL_MINIMUM(1), 0x00,
		LOGICAL_MAXIMUM(1), 0x65,       //   101 keys
		USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
		USAGE_MINIMUM(1),   0x00,
		USAGE_MAXIMUM(1),   0x65,
		INPUT(1),           0x00,       //   Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position

		USAGE_PAGE(1),      0x00,       //   Undefined
		USAGE_MINIMUM(1),   0x00,
		USAGE_MAXIMUM(1),   0xFF,
		REPORT_COUNT(1),    0x01,       //   1 byte 
		REPORT_SIZE(1),     0x08,
		FEATURE(1),         0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
	END_COLLECTION(0),
};

class KeyboardService : public HIDServiceBase {

	union InputReportData {
		uint8_t raw[8];
		struct {
			uint8_t modifier;
			uint8_t padding;
			uint8_t keycode[6];
		} data;
	};

	union OutputReportData {
		uint8_t raw[1];
	};

	union FeatureReportData {
		uint8_t raw[1];
	};

	/**
	 * Boot Protocol
	 * Share input/output report with Report Protocol for memmory saving
	 */
	GattCharacteristic bootKeyboardInputReportCharacteristic;
	GattCharacteristic bootKeyboardOutputReportCharacteristic;

	InputReportData inputReportDataPublished;
	InputReportData inputReportData;

	// 5ms ごとのスイッチ判定・20ms ごとの送信
	// 人間の連打限界を16press/secとし keydown/keyup それぞれでレポートを送ることを考えると
	// 32report/sec = 31msec で、限界で連打しても20msごとの送信にまにあう
	// ただし上記は1キーに対してであり、複数キーを全力で押すとキューに溜まることがある
	// 実際にカウントして測ってみたところ、どんなに乱暴に連打しても8ぐらいまでしかいかない
	CircularBuffer<InputReportData, 8, uint8_t> inputReportBuffer;
	InputReportData inputReportDataSending;
	OutputReportData outputReportData;
	FeatureReportData featureReportData;
	bool isSending;

	static const uint8_t MODIFIER_LEFT_CONTROL = 1<<0;
	static const uint8_t MODIFIER_LEFT_SHIFT = 1<<1;
	static const uint8_t MODIFIER_LEFT_ALT = 1<<2;
	static const uint8_t MODIFIER_LEFT_GUI = 1<<3;
	static const uint8_t MODIFIER_RIGHT_CONTROL = 1<<4;
	static const uint8_t MODIFIER_RIGHT_SHIFT = 1<<5;
	static const uint8_t MODIFIER_RIGHT_ALT = 1<<6;
	static const uint8_t MODIFIER_RIGHT_GUI = 1<<7;
public:
	bool sendAvailable;
	uint8_t bufferCount;

	KeyboardService(BLE& _ble) :
		HIDServiceBase(
			_ble,
			KEYBOARD_REPORT_MAP,
			sizeof(KEYBOARD_REPORT_MAP),
			inputReport         = inputReportDataPublished.raw,
			outputReport        = outputReportData.raw,
			featureReport       = featureReportData.raw,
			inputReportLength   = sizeof(inputReportData),
			outputReportLength  = sizeof(outputReportData),
			featureReportLength = sizeof(featureReportData),
			reportTickerDelay   = 20
		),
		bootKeyboardInputReportCharacteristic(GattCharacteristic::UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR,
			(uint8_t *)inputReport, inputReportLength, inputReportLength,
			  GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ
			| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
			| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
		),

		bootKeyboardOutputReportCharacteristic(GattCharacteristic::UUID_BOOT_KEYBOARD_OUTPUT_REPORT_CHAR,
			(uint8_t *)outputReport, outputReportLength, outputReportLength,
			  GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ
			| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE
			| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
		),
		isSending(false),
		sendAvailable(false),
		bufferCount(0)
	{
		for (int i = 0; i < 8; i++) {
			inputReportData.raw[i] = 0;
		}
		outputReportData.raw[0] = 0;

		inputReportBuffer.reset();
	}

	virtual void addExtraCharacteristics(GattCharacteristic** characteristics, uint8_t& charIndex) {
		DEBUG_PRINTF_BLE("addExtraCharacteristics %d\r\n", charIndex);
		characteristics[charIndex++] = &bootKeyboardInputReportCharacteristic;
		characteristics[charIndex++] = &bootKeyboardOutputReportCharacteristic;
	}

	virtual ble_error_t send(const report_t report) {
		if (protocolMode == REPORT_PROTOCOL) { // this is default
			return ble.gattServer().write(
				inputReportCharacteristic.getValueHandle(),
				report,
				inputReportLength
			);
		} else {
			return ble.gattServer().write(
				bootKeyboardInputReportCharacteristic.getValueHandle(),
				report,
				inputReportLength
			);
		}
	}

	void appendReportData(const uint8_t keycode) {
		uint8_t modifier = toModifierBit(keycode);
		if (modifier) {
			inputReportData.data.modifier |= modifier;
			return;
		}


		for (int i = 0; i < 6; i++) {
			if (inputReportData.data.keycode[i] == 0) {
				inputReportData.data.keycode[i] = keycode;
				return;
			}
		}

		// TODO: report data is full
	}

	void deleteReportData(const uint8_t keycode) {
		uint8_t modifier = toModifierBit(keycode);
		if (modifier) {
			inputReportData.data.modifier &= ~modifier;
			return;
		}

		for (int i = 0; i < 6; i++) {
			if (inputReportData.data.keycode[i] == keycode) {
				inputReportData.data.keycode[i] = 0;
				return;
			}
		}
	}

	void queueCurrentReportData() {
		DEBUG_PRINTF_BLE("Q %d\r\n", bufferCount);
		bufferCount++;
		inputReportBuffer.push(inputReportData);
		startReportTicker();
	}

	uint8_t toModifierBit(const uint8_t keycode) const {
		switch (keycode) {
			case KEY_LeftControl:  return MODIFIER_LEFT_CONTROL;
			case KEY_LeftShift:    return MODIFIER_LEFT_SHIFT;
			case KEY_LeftAlt:      return MODIFIER_LEFT_ALT;
			case KEY_LeftGUI:      return MODIFIER_LEFT_GUI;
			case KEY_RightControl: return MODIFIER_RIGHT_CONTROL;
			case KEY_RightShift:   return MODIFIER_RIGHT_SHIFT;
			case KEY_RightAlt:     return MODIFIER_RIGHT_ALT;
			case KEY_RightGUI:     return MODIFIER_RIGHT_GUI;
		}
		return 0;
	}

	bool isKeyPressed() {
		for (int i = 0; i < 8; i++) {
			if (inputReportData.raw[i]) {
				return 1;
			}
		}
		return 0;
	}

	virtual void sendCallback(void) {
		// do not call printf in this function... it cause BLE_STACK_BUSY
		sendAvailable = true;
	}
	
	void processSend() {
		if (!sendAvailable) {
			return;
		}
		sendAvailable = false;
		
		// isSending の場合現在送信中の report があり、再送中である可能性がある
		// そうではない場合のみ queue から pop して新しく send する
		if (!isSending) {
			if (!inputReportBuffer.pop(inputReportDataSending)) {
				// 送るデータがないなら送信をやめる
				stopReportTicker();
				return;
			}
			bufferCount--;
		}

		static uint8_t busyCount = 0;
		isSending = true;
		ble_error_t error = send(inputReportDataSending.raw);
		if (error == BLE_STACK_BUSY) {
			if (busyCount++ > 10) {
				DEBUG_PRINTF_BLE("BLE_STACK_BUSY\r\n");
				busyCount = 0;
				stopReportTicker();
			}
			// retry after
			return;
		}
		isSending = false;
	}

	virtual void onDataSent(unsigned int count) {
		startReportTicker();
	}

	virtual void onDataWritten(const GattWriteCallbackParams *params) {
		if (params->handle == inputReportCharacteristic.getValueHandle()) {
			DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: inputReport %d bytes\r\n", params->len);
		} else
		if (params->handle == outputReportCharacteristic.getValueHandle()) {
			DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: outputReport %d bytes\r\n", params->len);
		} else
		if (params->handle == featureReportCharacteristic.getValueHandle()) {
			DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: featureReport %d bytes\r\n", params->len);
		} else
		if (params->handle == protocolModeCharacteristic.getValueHandle()) {
			DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: protocolMode %d bytes %d\r\n", params->len, params->data[0]);
			protocolMode = params->data[0];
		}
	}

	virtual void stopReportTicker(void) {
		if (reportTickerIsActive) {
			HIDServiceBase::stopReportTicker();
		}
	}
};