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 (Web)
- TypeScript
- Python
use buttplug_client::{
ButtplugClient,
ButtplugClientEvent,
connector::ButtplugRemoteClientConnector,
serializer::ButtplugClientJSONSerializer,
};
use buttplug_transport_websocket_tungstenite::ButtplugWebsocketClientTransport;
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 = ButtplugRemoteClientConnector::<
ButtplugWebsocketClientTransport,
ButtplugClientJSONSerializer,
>::new(ButtplugWebsocketClientTransport::new_insecure_connector(
"ws://127.0.0.1:12345",
));
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(())
}
// Buttplug C# - Device Enumeration Example
//
// This example demonstrates how to scan for devices and handle
// device connection/disconnection events.
using Buttplug.Client;
var client = new ButtplugClient("Device Enumeration Example");
// Set up event handlers BEFORE connecting.
// This ensures we don't miss any events.
client.DeviceAdded += (sender, args) =>
{
Console.WriteLine($"Device connected: {args.Device.Name}");
};
client.DeviceRemoved += (sender, args) =>
{
Console.WriteLine($"Device disconnected: {args.Device.Name}");
};
client.ScanningFinished += (sender, args) =>
{
Console.WriteLine("Scanning finished.");
};
// Connect to the server
await client.ConnectAsync("ws://127.0.0.1:12345");
// Start scanning for devices.
// Devices will be announced via the DeviceAdded event.
Console.WriteLine("Turn on your devices now!");
await client.StartScanningAsync();
Console.WriteLine("\nPress Enter to stop scanning...");
Console.ReadLine();
// Stop scanning. Some protocols scan continuously until told to stop.
await client.StopScanningAsync();
// The client maintains a list of all known devices.
// This list persists even after scanning stops.
Console.WriteLine("\nCurrently connected devices:");
foreach (var device in client.Devices)
{
Console.WriteLine($" - {device.Name} (Index: {device.Index})");
}
if (client.Devices.Length == 0)
{
Console.WriteLine(" (no devices connected)");
}
Console.WriteLine("\nPress Enter to disconnect...");
Console.ReadLine();
await client.DisconnectAsync();
Console.WriteLine("Disconnected.");
// Buttplug Web - Device Enumeration Example
//
// This example demonstrates how to scan for devices and handle
// device connection/disconnection events.
//
// Include Buttplug via CDN:
// <script src="https://cdn.jsdelivr.net/npm/buttplug@4.0.0/dist/web/buttplug.min.js"></script>
async function runDeviceEnumerationExample() {
const client = new Buttplug.ButtplugClient("Device Enumeration Example");
// Set up event handlers BEFORE connecting.
// This ensures we don't miss any events, including devices
// that are already connected to the server.
client.addListener("deviceadded", (device) => {
console.log(`Device connected: ${device.name}`);
// The client maintains a Map of all known devices.
// In v4, client.devices is a Map<number, ButtplugClientDevice>
console.log("Currently connected devices:");
for (const [index, dev] of client.devices) {
console.log(` - ${dev.name} (Index: ${index})`);
}
});
client.addListener("deviceremoved", (device) => {
console.log(`Device disconnected: ${device.name}`);
});
client.addListener("scanningfinished", () => {
console.log("Scanning finished.");
});
// Connect to the server (requires Intiface Central running)
const connector = new Buttplug.ButtplugBrowserWebsocketClientConnector("ws://localhost:12345");
console.log("Connecting...");
await client.connect(connector);
console.log("Connected!");
// Start scanning for devices.
// Devices will be announced via the 'deviceadded' event.
console.log("Starting device scan... Turn on your devices now!");
await client.startScanning();
// Note: In a real application, you would call client.stopScanning()
// when you're done scanning, and client.disconnect() when finished.
}
// Buttplug TypeScript - Device Enumeration Example
//
// This example demonstrates how to scan for devices and handle
// device connection/disconnection events.
//
// Prerequisites:
// 1. Install Intiface Central: https://intiface.com/central
// 2. Start the server in Intiface Central
// 3. Run: npx ts-node --esm device-enumeration-example.ts
import {
ButtplugClient,
ButtplugNodeWebsocketClientConnector,
ButtplugClientDevice,
} from 'buttplug';
import * as readline from 'readline';
async function waitForEnter(prompt: string): Promise<void> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
rl.question(prompt, () => {
rl.close();
resolve();
});
});
}
async function main(): Promise<void> {
const client = new ButtplugClient('Device Enumeration Example');
// Set up event handlers BEFORE connecting.
// This ensures we don't miss any events.
client.addListener('deviceadded', (device: ButtplugClientDevice) => {
console.log(`Device connected: ${device.name}`);
});
client.addListener('deviceremoved', (device: ButtplugClientDevice) => {
console.log(`Device disconnected: ${device.name}`);
});
client.addListener('scanningfinished', () => {
console.log('Scanning finished.');
});
// Connect to the server
const connector = new ButtplugNodeWebsocketClientConnector(
'ws://127.0.0.1:12345'
);
console.log('Connecting...');
await client.connect(connector);
console.log('Connected!');
// Start scanning for devices.
// Devices will be announced via the 'deviceadded' event.
console.log('\nStarting device scan...');
console.log('Turn on your devices now!');
await client.startScanning();
await waitForEnter('\nPress Enter to stop scanning...');
// Stop scanning. Some protocols scan continuously until told to stop.
await client.stopScanning();
// The client maintains a map of all known devices.
// This map persists even after scanning stops.
console.log('\nCurrently connected devices:');
if (client.devices.size === 0) {
console.log(' (no devices connected)');
} else {
for (const [index, device] of client.devices) {
console.log(` - ${device.name} (Index: ${index})`);
}
}
await waitForEnter('\nPress Enter to disconnect...');
await client.disconnect();
console.log('Disconnected.');
}
main().catch(console.error);
"""Device Enumeration - Scan for and list devices.
This example shows how to scan for devices and handle device
connection/disconnection events.
Prerequisites:
1. Install Intiface Central: https://intiface.com/central/
2. Start Intiface Central and click "Start Server"
3. Have a supported device nearby and powered on
4. Run this script: python device_enumeration.py
"""
import asyncio
from buttplug import ButtplugClient, ButtplugDevice
def on_device_added(device: ButtplugDevice) -> None:
"""Called when a device connects."""
print(f"Device connected: {device.name} (index {device.index})")
def on_device_removed(device: ButtplugDevice) -> None:
"""Called when a device disconnects."""
print(f"Device disconnected: {device.name}")
async def main() -> None:
client = ButtplugClient("Device Enumeration Example")
# Set up event handlers before connecting
client.on_device_added = on_device_added
client.on_device_removed = on_device_removed
print("Connecting to server...")
await client.connect("ws://127.0.0.1:12345")
print(f"Connected to: {client.server_name}")
# Start scanning for devices
print("\nScanning for devices (5 seconds)...")
await client.start_scanning()
await asyncio.sleep(5)
await client.stop_scanning()
# List all discovered devices
if client.devices:
print(f"\nFound {len(client.devices)} device(s):")
for device in client.devices.values():
print(f" - {device.name}")
if device.display_name:
print(f" Display name: {device.display_name}")
else:
print("\nNo devices found.")
print("Make sure your device is on and in pairing mode.")
await client.disconnect()
print("\nDone!")
if __name__ == "__main__":
asyncio.run(main())