Device Enumeration
Once the client and server are connected, they can start communicating about devices.
Scanning
To find out about new devices during a session, Buttplug Client libraries will usually provide 2 functions and an event/callback:
- StartScanning (Method)
- Tells the server to start looking for devices via the Device Manager. This will start the Bluetooth Manager doing a bluetooth scan, the USB manager looking for USB or HID devices, etc... for all loaded Device Communication Managers
- Note: Scanning may still require user input on the server side! For instance, using WebBluetooth in browsers with buttplug-wasm will require the user to interact with browser dialogs, so calling StartScanning() may open that dialog.
- StopScanning (Method)
- Tells the server to stop scanning for devices if it hasn't already.
ScanningFinished Event (WASM only)
ScanningFinished is now only used for the Typescript WASM Server setup. For Desktop/Mobile apps connecting to Intiface Central, you do not need to watch for the ScanningFinished event, as most Device Scanners run until StopScanning is called.
- ScanningFinished (Event/Callback)
- When all device communication managers have finished looking for new devices, this event will be fired from the client to let applications know to update their UI (for instance, to change a button name from "Stop Scanning" to "Start Scanning"). This event may fire without StopScanning ever being called, as there are cases where scanning is not indefinite (once again, WebBluetooth is a good example, as well as things like gamepad scanners).
Device Connection Events and Storage
There are 2 events related to device connections that the client may fire:
- DeviceAdded (Event/Callback)
- This event will contain a new device object. It denotes that the server is now connected to this device, and that the device can take commands.
- DeviceRemoved (Event/Callback)
- This event will fire when a device disconnects from the server for some reason. It should contain and instance of the device that disconnected.
While the events are handy for updating UI, Client implementations usually also hold a list of currently connected devices that can be used for iteration if needed.
Both events may be fired at any time during a Buttplug Client/Server session. DeviceAdded can be called outside of StartScanning()/StopScanning(), and even right after connect in some instances.
Already Connected Devices
Servers will normally stay up and running until users stop them, meaning they can have connections from several different clients over the session. This means that devices may already be connected to servers when you connect.
Most clients will query the server for already connected devices when they finish their handshake, after which they will then present them as DeviceAdded() events.
This means you will want to have your event handlers set up BEFORE connecting, in order to catch these messages. You can also check the Devices storage (usually a public collection on your Client instance, like an array or list) after connect to see what devices are there.
Code Example
Here's some examples of how device enumeration works in different implementations of Buttplug.
- Rust
- C#
- Javascript
use buttplug::{client::{ButtplugClientEvent, ButtplugClient}, util::in_process_client, core::connector::new_json_ws_client_connector};
use futures::StreamExt;
use tokio::io::{self, AsyncBufReadExt, BufReader};
async fn wait_for_input() {
BufReader::new(io::stdin())
.lines()
.next_line()
.await
.unwrap();
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Usual embedded connector setup. We'll assume the server found all
// of the subtype managers for us (the default features include all of them).
//let client = in_process_client("Example Client", false).await;
// To create a Websocket Connector, you need the websocket address and some generics fuckery.
let connector = new_json_ws_client_connector("ws://127.0.0.1:12345/buttplug");
let client = ButtplugClient::new("Example Client");
client.connect(connector).await?;
let mut events = client.event_stream();
// Set up our DeviceAdded/DeviceRemoved/ScanningFinished event handlers before connecting.
tokio::spawn(async move {
while let Some(event) = events.next().await {
match event {
ButtplugClientEvent::DeviceAdded(device) => {
println!("Device {} Connected!", device.name());
}
ButtplugClientEvent::DeviceRemoved(info) => {
println!("Device {} Removed!", info.name());
}
ButtplugClientEvent::ScanningFinished => {
println!("Device scanning is finished!");
}
_ => {}
}
}
});
// We're connected, yay!
println!("Connected!");
// Now we can start scanning for devices, and any time a device is
// found, we should see the device name printed out.
client.start_scanning().await?;
wait_for_input().await;
// Some Subtype Managers will scan until we still them to stop, so
// let's stop them now.
client.stop_scanning().await?;
wait_for_input().await;
// Since we've scanned, the client holds information about devices it
// knows about for us. These devices can be accessed with the Devices
// getter on the client.
println!("Client currently knows about these devices:");
for device in client.devices() {
println!("- {}", device.name());
}
wait_for_input().await;
// And now we disconnect as usual.
client.disconnect().await?;
Ok(())
}
using Buttplug.Client;
namespace DeviceEnumerationExample
{
class Program
{
private static async Task WaitForKey()
{
Console.WriteLine("Press any key to continue.");
while (!Console.KeyAvailable)
{
await Task.Delay(1);
}
Console.ReadKey(true);
}
private static async Task RunExample()
{
// Usual embedded connector setup.
var client = new ButtplugClient("Example Client");
// Set up our DeviceAdded/DeviceRemoved event handlers before connecting.
client.DeviceAdded += (aObj, aDeviceEventArgs) =>
Console.WriteLine($"Device {aDeviceEventArgs.Device.Name} Connected!");
client.DeviceRemoved += (aObj, aDeviceEventArgs) =>
Console.WriteLine($"Device {aDeviceEventArgs.Device.Name} Removed!");
// Now that everything is set up, we can connect.
try
{
await client.ConnectAsync(new ButtplugWebsocketConnector(new Uri("ws://127.0.0.1:12345")));
}
catch (Exception ex)
{
Console.WriteLine(
$"Can't connect, exiting! Message: {ex?.InnerException?.Message}");
await WaitForKey();
return;
}
// We're connected, yay!
Console.WriteLine("Connected!");
// Set up our scanning finished function to print whenever scanning is done.
client.ScanningFinished += (aObj, aScanningFinishedArgs) =>
Console.WriteLine("Device scanning is finished!");
// Now we can start scanning for devices, and any time a device is
// found, we should see the device name printed out.
await client.StartScanningAsync();
await WaitForKey();
// Some Subtype Managers will scan until we still them to stop, so
// let's stop them now.
await client.StopScanningAsync();
await WaitForKey();
// Since we've scanned, the client holds information about devices it
// knows about for us. These devices can be accessed with the Devices
// getter on the client.
Console.WriteLine("Client currently knows about these devices:");
foreach (var device in client.Devices)
{
Console.WriteLine($"- {device.Name}");
}
await WaitForKey();
// And now we disconnect as usual.
await client.DisconnectAsync();
}
private static void Main()
{
RunExample().Wait();
}
}
}
// This example assumes Buttplug is brought in as a root namespace, via
// inclusion by a script tag, i.e.
//
// <script lang="javascript"
// src="https://cdn.jsdelivr.net/npm/buttplug@3.0.0/dist/web/buttplug.min.js">
// </script>
//
// If you're trying to load this, change the version to the latest available.
async function runDeviceEnumerationExample() {
let client = new Buttplug.ButtplugClient("Device Enumeration Example");
// Set up our DeviceAdded/DeviceRemoved event handlers before connecting. If
// devices are already held to the server when we connect to it, we'll get
// "deviceadded" events on successful connect.
client.addListener("deviceadded", (device) => {
console.log(`Device Connected: ${device.name}`);
console.log("Client currently knows about these devices:");
client.devices.forEach((device) => console.log(`- ${device.name}`));
});
client
.addListener("deviceremoved", (device) => console.log(`Device Removed: ${device.name}`));
// Usual embedded connector setup.
const connector = new Buttplug.ButtplugBrowserWebsocketClientConnector("ws://localhost:12345");
await client.connect(connector);
// Now that everything is set up, we can scan.
await client.startScanning();
};