I'm trying to write a program that makes management of Wiimotes paired with Windows a lot simpler and automated. The program uses WiimoteLib (which uses hidsdi.h and setupapi.h) to connect to Wiimote devices, and 32feet (uses Windows Bluetooth API) to automatically pair/unpair devices. The code for the pairer/unpairer is based off of Wiipair. At the moment, the process is a little bumpy and slow, but it works. (But only for one Wiimote)
The problem is that my module to pair/unpair Bluetooth devices has no information on how to identify if the HID device (used by the Wiimote class) is the same device. I want to be able to alert the Wiimote class if the Bluetooth device has been forcefully shutdown or unpaired so that it can gracefully disconnect itself. And vice-versa, I'd like the Wiimote to alert the pairer/unpairer when the HID device is disconnected so that the Bluetooth device can optionally be unpaired (assuming you plan on shutting down the Wiimote).
If I only wanted access to one Wiimote then this wouldn't be much of a problem, but I'd like to be able to access multiple Wiimotes and be able to differentiate them by using their HID info and Bluetooth Info. I'm already using plenty of my own P/Invoke to cover for areas that 32feet lacks in so using any more isn't a problem.
Here's the main code for my pairer. (Although I'm not sure if it's really necessary):
(Note: IsDiscoverable()
, ToPin()
, and ToMacAddress()
are all extension methods.)
// Once this is true, the Wiimote will
// attempt to connect to an HID device.
public bool IsAnyDeviceAvailable { get; private set; }
private void PairTask(CancellationToken token) {
// Setup automatic authentication
BluetoothWin32Authentication auth = new BluetoothWin32Authentication(OnHandleRequests);
while (!token.IsCancellationRequested)
PairLoop(token);
}
private void PairLoop(CancellationToken token) {
// Get a copy of known addresses since
// these are added to in another task.
BluetoothAddress[] addresses;
lock (knownAddresses)
addresses = KnownAddresses.ToArray();
bool available = false;
foreach (BluetoothAddress address in addresses) {
if (token.IsCancellationRequested)
return;
BluetoothDeviceInfo device = new BluetoothDeviceInfo(address);
if (device.Connected) {
if (!available && !IsAnyDeviceAvailable) {
lock (knownAddresses)
IsAnyDeviceAvailable = true;
}
available = true;
continue;
}
if (device.Remembered) {
RemoveDevice(device, token);
}
else if (device.IsDiscoverable() && !device.Authenticated) {
if (PairDevice(device, token, available))
available = true;
}
token.WaitHandle.WaitOne(500);
}
if (!available && IsAnyDeviceAvailable) {
Trace.WriteLine("No more devices connected");
lock (knownAddresses)
IsAnyDeviceAvailable = false;
}
}
private void RemoveDevice(BluetoothDeviceInfo device, CancellationToken token) {
token.WaitHandle.WaitOne(1000);
if (BluetoothSecurity.RemoveDevice(device.DeviceAddress)) {
Trace.WriteLine($"Wiimote removed: {device.DeviceAddress.ToMacAddress()}");
token.WaitHandle.WaitOne(2000);
}
}
private bool PairDevice(BluetoothDeviceInfo device, CancellationToken token,
bool available)
{
string pin = device.DeviceAddress.ToPin();
try {
if (BluetoothSecurity.PairRequest(device.DeviceAddress, pin)) {
Trace.WriteLine($"Wiimote authenticated: {device.DeviceAddress.ToMacAddress()}");
token.WaitHandle.WaitOne(1000);
// Calling this before and after seems to help unsure
// the device works when paired programmatically.
Guid[] services = device.InstalledServices;
device.SetServiceState(Uuids.HumanInterfaceDeviceServiceClass_UUID, true, true);
services = device.InstalledServices;
Trace.WriteLine($"Wiimote paired: {device.DeviceAddress.ToMacAddress()}");
token.WaitHandle.WaitOne(8000);
if (!available && !IsAnyDeviceAvailable) {
Trace.WriteLine("First device has been connected");
lock (knownAddresses)
IsAnyDeviceAvailable = true;
}
return true;
}
else {
Trace.WriteLine($"Wiimote authentication failed: {device.DeviceAddress.ToMacAddress()}");
}
}
catch {
Trace.WriteLine($"Wiimote pairing failed: {device.DeviceAddress.ToMacAddress()}");
}
return false;
}
private void OnHandleRequests(object sender, BluetoothWin32AuthenticationEventArgs e) {
e.Confirm = true;
}