We use a Human Interface Device (HID) every time we interact with a desktop or laptop computer. Keyboards and mice are the most common types of HID device. HID’s original definition, uses the USB standard to send and receive data. The next generation of HID devices uses the Bluetooth standard to send and receive data.
The Generic Attribute Profile (GATT) describes how Bluetooth LE transfers data among devices. The HID over GATT Profile (HOGP) defines how to create input and output HID devices using BLE.
More information on the HID over GATT Profile can be found on the Bluetooth Developer Portal in the Technology Overview pages.
The BLEPeripheral library provides an additional BLEHIDPeripheral
API to ease the creation of HOGP devices.
BLEPeripheral has the following HID APIs built in:
BLEKeyboard
Used for sending standard keyboard key presses
BLEMouse
Used for sending mouse movement and click inputs
BLEMultimedia
Used for sending multimedia key presses, such as volume up/down, mute/unmute
BLESystemControl
Used for sending system-control key presses, such as power and sleep
Users can also implement other custom HID types, such as a joystick, in their sketches if needed.
These HID types can be added to a BLEHIDPeripheral
much in the same way that attributes (services, characteristics, descriptors) are added to a BLEPeripheral
.
BLEHIDPeripheral
extends the BLEPeripheral
API by adding the HID Service shown in Table 9-1, along with its characteristics and descriptors. They are required by the HOGP specification.
Using HOGP devices also requires pairing/bonding. Pairing is required to encrypt communications between the peripheral and centra, so eavesdroppers can’t spy on your inputs (key strokes) using a BLE sniffer. The BLEHIDPeripheral
API manages bonding data by storing it in the Arduino’s EEPROM so that it is persisted even when power is lost (unlike RAM).
During pairing, the BLE central and peripheral exchange encryption keys. BLEPeripheral
uses Just Works pairing. Instead of using a device-specific pairing code, Just Works uses a Temporary Key (TK) that is 16 bytes in size, with all bytes set to zero. The TK is used to calculate a Short-Term Key (STK) to encrypt communications, which is followed by a process to exchange a Long-Term Key (LTK). Both the central and peripheral store the LTK so that communications can continue to be encrypted in the future.
More information on the BLE pairing process can be found at “Bluetooth Low Energy SMP Pairing”.
Attribute | UUID | Properties | Notes |
---|---|---|---|
HID Service |
|
||
HID Information Characteristic |
|
read |
|
HID Control Point Characteristic |
|
write without response |
|
HID Report Characteristics |
|
read, notify |
One per HID device type |
HID Report Reference Descriptor |
|
One per HID device type |
To see how this works, let’s build a volume knob using a rotary encoder and Arduino. The volume knob will be compatible with iOS 8, Android 5, and OS X 10.10 devices. The volume knob will allow users to increase and decrease the volume on their paired device, as well as to mute and unmute it.
The following hardware is required:
Breadboard
Jumper wires
Knob for rotary encoder (optional)
The wiring for the Bluefruit LE module is the same as in previous chapters; see “Wiring Up the Adafruit Bluefruit LE Module” for more details.
To wire the rotary encoder, place it in the middle of the breadboard with the side that has two pins facing the top of the breadboard. Now connect the button pins of the rotary encoder by connecting a wire from the top-left pin to pin 5 on the Arduino, and connecting the top-right pin to ground. Then connect the rotary encoder side by connecting a wire from the bottom-left pin to pin 4 on the Arduino, the middle-bottom pin to ground, and the bottom-right pin to pin 3 on the Arduino.
When complete, the wiring will look as it does in Figure 9-1. See Figure 9-2 for a photo of the wire sketch.
We’ll be using the following Arduino libraries:
PJRC.com Teensy Encoder Library
See “Installing the BLE Peripheral Library” for instructions on setting up the BLEPeripheral
library if you haven’t already done so.
Follow these steps to set up the Encoder library:
Open the Arduino IDE
.
Go to Sketch → Include Library → Manage Libraries…
Search for Encoder
.
Select Encoder by Paul Stoffregen
.
Click the Install
button.
Close the Library Manager
using the Close
button.
The Encoder library includes a basic example sketch, as shown in Example 9-1. Let’s try it out:
Go to File → Examples → Encoder → Basic
.
Update the pin numbers for the encoder to 3
and 4
to match the wiring we set up earlier.
Load the sketch onto your Arduino.
Open the Serial Monitor and ensure the baud rate is set to 9600.
Turn the knob on the rotary encoder.
/* Encoder Library - Basic Example * http://www.pjrc.com/teensy/td_libs_Encoder.html * * This example code is in the public domain. */
#
include <Encoder.h>
// Change these two numbers to the pins connected to your encoder.
// Best Performance: both pins have interrupt capability
// Good Performance: only the first pin has interrupt capability
// Low Performance: neither pin has interrupt capability
Encoder
myEnc
(
3
,
4
)
;
// avoid using pins with LEDs attached
void
setup
(
)
{
Serial
.
begin
(
9600
)
;
Serial
.
println
(
"
Basic Encoder Test:
"
)
;
}
long
oldPosition
=
-
999
;
void
loop
(
)
{
long
newPosition
=
myEnc
.
read
(
)
;
if
(
newPosition
!
=
oldPosition
)
{
oldPosition
=
newPosition
;
Serial
.
println
(
newPosition
)
;
}
}
The value will:
Now that we are familiar with the rotary encoder API, let’s combine it with the BLEHIDPeripheral
API to create a volume knob.
Let’s create a new sketch in the Arduino IDE: File → New
.
void
setup
()
{
// put your setup code here, to run once:
}
void
loop
()
{
// put your main code here, to run repeatedly:
}
Now we need to import the libraries we will be using at the start of the sketch.
#include <SPI.h>
#include <BLEHIDPeripheral.h>
#include <BLEMultimedia.h>
#include <Encoder.h>
SPI
is needed by BLEHIDPeripheral
, so it is imported first. We use BLEHIDPeripheral
instead of BLEPeripheral
because it provides an easier interface to build HIDD over GATT peripherals; it uses BLEPeripheral
internally. The last import is the Encoder
library we used earlier in the chapter.
Next, let’s define the constants to match the wiring we completed earlier.
#define BLE_REQ 10
#define BLE_RDY 2
#define BLE_RST 9
#define BUTTON_PIN 5
#define ENC_RIGHT_PIN 3
#define ENC_LEFT_PIN 4
The first three are used for the Adafruit Bluefruit LE board, followed by the encoder button pin and then the other encoder pins.
Now let’s create the object instances and variables we need for the rest of the sketch. We’ll need a BLEHIDPeripheral
instance to create a BLE peripheral, a BLEMultimedia
instance to send multimedia inputs to the central, an Encoder
instance, and a variable to keep track of the button state.
BLEHIDPeripheral
bleHIDPeripheral
=
BLEHIDPeripheral
(
BLE_REQ
,
BLE_RDY
,
BLE_RST
);
BLEMultimedia
bleMultimedia
;
Encoder
encoder
(
ENC_RIGHT_PIN
,
ENC_LEFT_PIN
);
int
buttonState
;
Next, we move on to the setup
function. We will configure the Serial object for output at 9600
baud. Set the BUTTON_PIN
as an input in pullup mode. In pullup mode, the Arduino’s internal pullup resistor is used, so there is no need to wire an external one on the breadboard.
void
setup
()
{
Serial
.
begin
(
9600
);
pinMode
(
BUTTON_PIN
,
INPUT_PULLUP
);
}
We then need to set an initial value of 0
for the encoder
instance by using the write
method.
void
setup
()
{
// ...
encoder
.
write
(
0
);
}
Now we will configure the bleHIDPeripheral
instance. We’ll set the local name that the peripheral advertises to HID Volume
. Then we will add our bleMultimedia
instances as a HID device. This will automatically create and add new HID Report Characteristics (UUID 0x2a4d
) and an HID Report Reference Descriptor (UUID 0x2908
) to the peripheral.
void
setup
()
{
// ...
bleHIDPeripheral
.
setLocalName
(
"HID Volume"
);
bleHIDPeripheral
.
addHID
(
bleMultimedia
);
bleHIDPeripheral
.
begin
();
}
The final items remaining in setup
are to start the bleHIDPeripheral
by calling its begin
method, and to print a message to the Serial port to indicate that the HID peripheral is set up.
void
setup
()
{
// ...
bleHIDPeripheral
.
begin
();
Serial
.
println
(
F
(
"BLE HID Volume Knob"
));
}
If you will be using the volume knob with an Android device, you will also need to include the following line before bleHIDPeripheral.begin()
in setup:
bleHIDPeripheral
.
setReportIdOffset
(
1
);
The default report id offset is 0, though most Android devices do not work when it is used so the report id offset needs to be changed to 1.
Now let’s implement the loop
function of the sketch. We first need to check if the peripheral has a central connected. If is does, we will print the central’s address to the serial port.
void
loop
()
{
BLECentral
central
=
bleHIDPeripheral
.
central
();
if
(
central
)
{
Serial
.
(
F
(
"Connected to central: "
));
Serial
.
println
(
central
.
address
());
}
}
While the central is connected, we need to poll the button and encoder for input events. We call a function named pollInputs
, which we will implement shortly.
void
loop
()
{
// ...
if
(
central
)
{
// ...
while
(
bleHIDPeripheral
.
connected
())
{
pollInputs
();
}
}
}
When the central disconnects, let’s also print a message to the serial port.
void
loop
()
{
// ...
if
(
central
)
{
// ...
Serial
.
(
F
(
"Disconnected from central: "
));
Serial
.
println
(
central
.
address
());
}
}
Now let’s implement the pollInputs
function we called from loop
earlier. We’ll have it call two functions: pollButton
for the button and pollEncoder
for the encoder.
void
pollInputs
()
{
pollButton
();
pollEncoder
();
}
The pollButton
function will read the current value of the button pin using the digitalRead
function and compare it to the previously stored value in buttonState
. If the value is different, the button has either been pressed or released, and the new value is stored. When pressed, the value will be LOW
, so we can send the mute key value MMKEY_MUTE
to the central using the write
method on the bleMultimedia
instance.
void
pollButton
()
{
int
tempButtonState
=
digitalRead
(
BUTTON_PIN
);
if
(
tempButtonState
!=
buttonState
)
{
buttonState
=
tempButtonState
;
if
(
buttonState
==
LOW
)
{
Serial
.
println
(
F
(
"Mute"
));
bleMultimedia
.
write
(
MMKEY_MUTE
);
}
}
}
In the pollEncoder
, we first read the current value from the encoder
. If it is non-zero, the encoder has been rotated. When the value is above 0
, the encoder has been rotated clockwise, which corresponds to a volume-up action. We will map this to the volume-up key value MMKEY_VOL_UP
, and again use the write
method on bleMultimedia
to send the value. When the encoder state is less than zero, it has been turned counterclockwise. This action will be mapped to the volume-down key MMKEY_VOL_DOWN
. Then we need to reset the encoder’s current value to 0
.
void
pollEncoder
()
{
int
encoderState
=
encoder
.
read
();
if
(
encoderState
!=
0
)
{
if
(
encoderState
>
0
)
{
Serial
.
println
(
F
(
"Volume up"
));
bleMultimedia
.
write
(
MMKEY_VOL_UP
);
}
else
{
Serial
.
println
(
F
(
"Volume down"
));
bleMultimedia
.
write
(
MMKEY_VOL_DOWN
);
}
encoder
.
write
(
0
);
}
}
We can’t use the absolute position of the encoder, as we have no way of knowing what the current volume level of the central is. Also, the HID APIs only allows us to send key-press events and not absolute volume levels.
Now you can load the sketch on your Arduino, and then follow the steps in Appendix A to pair the HID peripheral with your iOS, Android, or OS X device.
Once paired successfully, you will be able to control the volume of the device using the knob. You will also be able to toggle muting by pressing the button built into the rotary encoder.
You’ll notice that it will turn the volume up or down by more than a notch. We can add some debounce logic to correct this by polling the inputs every 100ms. Update the beginning of the sketch with the following items: a constant for the poll interval, INPUT_POLL_INTERVAL
, and a variable named lastInputPollTime
to store the last input poll time.
// ...
#define ENC_RIGHT_PIN 3
#define ENC_LEFT_PIN 4
#define INPUT_POLL_INTERVAL 100
// ...
int
buttonState
;
unsigned
long
lastInputPollTime
=
0
;
void
setup
()
{
// ...
We can then modify the pollInputs
function to check if the last poll time has been over 100ms. If it has, both the button and encoder inputs are polled. Then we update the lastInputPollTime
variable. The millis
function is used for time tracking, and returns the number of milliseconds the sketch has been running.
void
pollInputs
()
{
if
(
millis
()
-
lastInputPollTime
>
INPUT_POLL_INTERVAL
)
{
pollButton
();
pollEncoder
();
lastInputPollTime
=
millis
();
}
}
The HID peripheral can only be paired with a single device at a time, but we don’t yet have a way to clear the pairing data. Let’s update the sketch to add this feature. We’ll clear the pairing data if the button is pressed on start. This will clear the pairing information stored in the EEPROM.
void
setup
()
{
// ...
pinMode
(
BUTTON_PIN
,
INPUT_PULLUP
);
buttonState
=
digitalRead
(
BUTTON_PIN
);
if
(
buttonState
==
LOW
)
{
Serial
.
println
(
F
(
"BLE HID Peripheral - clearing bond data"
));
bleHIDPeripheral
.
clearBondStoreData
();
}
encoder
.
write
(
0
);
// ...
}
To clear the bonding data, hold the rotary encoder button down, and then press the reset button on the Arduino Uno. You will see a message displayed on the Arduino IDE serial monitor when pairing the data has been cleared successfully.
Whenever you clear the bond store of the peripheral, be sure to remove the device from the previously paired device. Instructions for this can be found in Appendix A.
If you encounter issues connecting or pairing with the volume knob peripheral, clear the bond store of the peripheral and unpair it from all previously paired peripherals.
See the HID_volume.ino example sketch included with the BLEPeripheral library for a full code listing.
In this chapter, we created an Arduino-based BLE volume knob that can be used with a BLE-equipped iOS or Android device, as well as a Mac. We used the HID over GATT APIs included with the BLEPeripheral
library, and the PJRC.com Teensy Encoder Library.
You can use the other HID over GATT APIs included with the BLEPeripheral
library to create other HOGP devices, such as a numeric keypad or joystick.
18.117.158.47