Greetings! As promised today I'm going to be talking about turning OpenMote into a BLE keyboard for computer control!
For some low level gaming and simple keystroke replacement this project log is all you need to know! Today I configured OpenMote to play the Dino game and pacman on my computer wirelessly!
I'm hoping to get some more complex and complicated gaming controller connections -- think dolphin -- in the next couple of weeks!
If you're looking for a fun and silly way to connect and control your computer with a wii-remote then look no further than the code I provide today. I tried getting it to work with a couple other BLE libraries and found the NimBLE was easily the best one as it worked first time out of the box.
Enjoy and stay fun!
#include <Arduino.h>
#include <NimBLEDevice.h>
#include <NimBLEHIDDevice.h>
// Pin definitions based on your custom board
#define A_BUTT 14
#define UP_BUTT 11
#define DOWN_BUTT 12
#define LEFT_BUTT 4
#define RIGHT_BUTT 15
// BLE HID Keyboard
NimBLEHIDDevice* hid;
NimBLECharacteristic* input;
NimBLECharacteristic* output;
// Button state variables
bool aButtonPressed = false;
bool upButtonPressed = false;
bool downButtonPressed = false;
bool leftButtonPressed = false;
bool rightButtonPressed = false;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;
// Status tracking
unsigned long lastStatusTime = 0;
const unsigned long statusInterval = 5000;
bool isConnected = false;
// HID Report Descriptor for Keyboard
const uint8_t hidReportDescriptor[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID (1)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0xE0, // Usage Minimum (224)
0x29, 0xE7, // Usage Maximum (231)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Constant)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0x00, // Usage Minimum (0)
0x29, 0x65, // Usage Maximum (101)
0x81, 0x00, // Input (Data, Array)
0xC0 // End Collection
};
// Keyboard report structure
typedef struct {
uint8_t modifiers;
uint8_t reserved;
uint8_t keys[6];
} KeyReport;
KeyReport keyReport = {0};
// BLE Server Callbacks
class ServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer) {
isConnected = true;
Serial.println(">>> BLE Client Connected!");
}
void onDisconnect(NimBLEServer* pServer) {
isConnected = false;
Serial.println(">>> BLE Client Disconnected!");
NimBLEDevice::startAdvertising();
}
};
void sendKey(uint8_t key) {
keyReport.keys[0] = key;
input->setValue((uint8_t*)&keyReport, sizeof(keyReport));
input->notify();
delay(50);
// Release
keyReport.keys[0] = 0;
input->setValue((uint8_t*)&keyReport, sizeof(keyReport));
input->notify();
}
void setup() {
// Initialize serial for debugging
Serial.begin(115200);
delay(1000);
Serial.println("=================================");
Serial.println("OpenMote NimBLE Keyboard Starting...");
Serial.println("=================================");
// Configure button pins
pinMode(A_BUTT, INPUT_PULLUP);
pinMode(UP_BUTT, INPUT_PULLUP);
pinMode(DOWN_BUTT, INPUT_PULLUP);
pinMode(LEFT_BUTT, INPUT_PULLUP);
pinMode(RIGHT_BUTT, INPUT_PULLUP);
// Initialize NimBLE
NimBLEDevice::init("OpenMote Controller");
// Create BLE Server
NimBLEServer *pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
// Create HID Device
hid = new NimBLEHIDDevice(pServer);
// Set HID parameters
hid->manufacturer()->setValue("OpenMote");
hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
hid->hidInfo(0x00, 0x01);
// Set Report Map
hid->reportMap((uint8_t*)hidReportDescriptor, sizeof(hidReportDescriptor));
// Create input report characteristic
input = hid->inputReport(1);
// Start HID service
hid->startServices();
// Start advertising
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->setAppearance(0x03C1); // Keyboard appearance
pAdvertising->addServiceUUID(hid->hidService()->getUUID());
pAdvertising->start();
Serial.println("BLE HID Keyboard started!");
Serial.println("Connect via Bluetooth to 'OpenMote Controller'");
Serial.println("=================================");
}
void loop() {
unsigned long currentTime = millis();
// Periodic status message
if (currentTime - lastStatusTime > statusInterval) {
lastStatusTime = currentTime;
Serial.print("Status: ");
Serial.print(isConnected ? "BLE Connected ✓" : "BLE Disconnected ✗");
Serial.print(" | A Button: ");
Serial.println(digitalRead(A_BUTT) == LOW ? "PRESSED" : "Released");
}
// Check if BLE keyboard is connected
if(isConnected) {
// A Button - Spacebar
bool aState = (digitalRead(A_BUTT) == LOW);
if (aState && !aButtonPressed) {
if ((currentTime - lastDebounceTime) > debounceDelay) {
aButtonPressed = true;
lastDebounceTime = currentTime;
Serial.println(">>> A Button - Spacebar");
sendKey(0x2C); // Spacebar
}
} else if (!aState && aButtonPressed) {
aButtonPressed = false;
}
// UP Button - Up Arrow
bool upState = (digitalRead(UP_BUTT) == LOW);
if (upState && !upButtonPressed) {
if ((currentTime - lastDebounceTime) > debounceDelay) {
upButtonPressed = true;
lastDebounceTime = currentTime;
Serial.println(">>> UP Button - Up Arrow");
sendKey(0x52); // Up Arrow
}
} else if (!upState && upButtonPressed) {
upButtonPressed = false;
}
// DOWN Button - Down Arrow
bool downState = (digitalRead(DOWN_BUTT) == LOW);
if (downState && !downButtonPressed) {
if ((currentTime - lastDebounceTime) > debounceDelay) {
downButtonPressed = true;
lastDebounceTime = currentTime;
Serial.println(">>> DOWN Button - Down Arrow");
sendKey(0x51); // Down Arrow
}
} else if (!downState && downButtonPressed) {
downButtonPressed = false;
}
// LEFT Button - Left Arrow
bool leftState = (digitalRead(LEFT_BUTT) == LOW);
if (leftState && !leftButtonPressed) {
if ((currentTime - lastDebounceTime) > debounceDelay) {
leftButtonPressed = true;
lastDebounceTime = currentTime;
Serial.println(">>> LEFT Button - Left Arrow");
sendKey(0x50); // Left Arrow
}
} else if (!leftState && leftButtonPressed) {
leftButtonPressed = false;
}
// RIGHT Button - Right Arrow
bool rightState = (digitalRead(RIGHT_BUTT) == LOW);
if (rightState && !rightButtonPressed) {
if ((currentTime - lastDebounceTime) > debounceDelay) {
rightButtonPressed = true;
lastDebounceTime = currentTime;
Serial.println(">>> RIGHT Button - Right Arrow");
sendKey(0x4F); // Right Arrow
}
} else if (!rightState && rightButtonPressed) {
rightButtonPressed = false;
}
} else {
// Reset button states when disconnected
aButtonPressed = false;
upButtonPressed = false;
downButtonPressed = false;
leftButtonPressed = false;
rightButtonPressed = false;
}
// Small delay to prevent overwhelming the processor
delay(10);
}
Gangwa Labs
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.