API Basics
Assuming you're using a client written by the Buttplug Core Team, client implementations in Buttplug are built to look as similar as possible no matter what language you're using. However, there may be instances where language options (i.e. existence of things like first-class events) change the API slightly. This section goes over how the client APIs we've provided work in a generic manner.
Buttplug Session Overview
Let's review what a Buttplug Sessions are made up of. Some of this was covered in depth in the architecture section, so this will just be an overview, while also including some example code.
Buttplug sessions (the connection lifetime between the client and server) consist of the following steps.
- Application sets up a connection via a Connector class/object and creates a Client
- Client connects to the Server
- Client negotiates Server Handshake and Device List Update
- Application uses Client to request Device Scanning
- Server communicates Device Connection events to Client/Application.
- Application uses Device Instances to control hardware in Server
- At some point, Application/Client disconnects from the Server
Client/Server Interaction
There are two types of communication between the client and the server:
- Request/Response (Client -> Server -> Client)
- Client sends a message, server replies. For instance, when a device command is sent from the client, the server will return information afterward saying whether or not that command succeeded.
- Events (Server -> Client)
- Server sends a message to the client with no expectation of response. For instance, when a new device connects to the server, the server will tell the client the device has been added, but the server doesn't expect the client to acknowledge this. These messages are considered fire and forget.
Request/Response interaction between the client and the server may be a very long process. Sometimes 100s of milliseconds, or even multiple seconds if device connection quality is poor. In languages where it is available, Client APIs try to deal with this via usage of Async/Await.
For event messages, first-class events are used, where possible. Otherwise, callbacks, promises, streams, or other methods are used depending on language and library capabilities.
- Rust
- C#
- Javascript (Web)
- TypeScript
- Python
use buttplug_client::{
ButtplugClient,
ButtplugClientEvent,
connector::ButtplugRemoteClientConnector,
serializer::ButtplugClientJSONSerializer,
};
use buttplug_transport_websocket_tungstenite::ButtplugWebsocketClientTransport;
use futures::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// In Rust, anything that will block is awaited. For instance, if we're going
// to connect to a remote server, that might take some time due to the network
// connection quality, or other issues. To deal with that, we use async/await.
//
// For now, you can ignore the API calls here, since we're just talking about
// how our API works in general. Setting up a connection is discussed more in
// the Connecting section of this document.
let connector = ButtplugRemoteClientConnector::<
ButtplugWebsocketClientTransport,
ButtplugClientJSONSerializer,
>::new(ButtplugWebsocketClientTransport::new_insecure_connector(
"ws://127.0.0.1:12345",
));
// For Request/Response messages, we'll use our Connect API. Connecting to a
// server requires the client and server to send information back and forth,
// so we'll await that while those (possibly somewhat slow, depending on if
// network is being used and other factors) transfers happen.
let client = ButtplugClient::new("Example Client");
client
.connect(connector)
.await
.expect("Can't connect to Buttplug Server, exiting!");
let mut event_stream = client.event_stream();
// As an example of event messages, we'll assume the server might
// send the client notifications about new devices that it has found.
// The client will let us know about this via events.
while let Some(event) = event_stream.next().await {
if let ButtplugClientEvent::DeviceAdded(device) = event {
println!("Device {} connected", device.name());
}
}
Ok(())
}
// Buttplug C# - Async Patterns Example
//
// This example demonstrates async/await patterns and event handling
// in the Buttplug C# library. The library is fully async - all operations
// that might block (network, device communication) use async/await.
using Buttplug.Client;
var client = new ButtplugClient("Async Example");
// Events in C# use the standard EventHandler pattern.
// Handlers receive (object sender, EventArgs args).
// DeviceAdded is fired when a new device connects
client.DeviceAdded += async (sender, args) =>
{
// Note: Event handlers can be async!
// The device is available via args.Device
Console.WriteLine($"[Event] Device added: {args.Device.Name}");
// You can interact with the device in the event handler
if (args.Device.HasOutput(Buttplug.Core.Messages.OutputType.Vibrate))
{
Console.WriteLine($" Sending welcome vibration...");
await args.Device.RunOutputAsync(DeviceOutput.Vibrate.Percent(0.25));
await Task.Delay(200);
await args.Device.StopAsync();
}
};
// DeviceRemoved is fired when a device disconnects
client.DeviceRemoved += (sender, args) =>
{
Console.WriteLine($"[Event] Device removed: {args.Device.Name}");
};
// ScanningFinished is fired when scanning completes
// (some protocols scan continuously until stopped)
client.ScanningFinished += (sender, args) =>
{
Console.WriteLine("[Event] Scanning finished");
};
// ErrorReceived is fired for asynchronous errors
// (errors not directly caused by a method call you awaited)
client.ErrorReceived += (sender, args) =>
{
Console.WriteLine($"[Event] Error: {args.Exception.Message}");
};
// ServerDisconnect is fired when the server connection drops
client.ServerDisconnect += (sender, args) =>
{
Console.WriteLine("[Event] Server disconnected!");
};
// PingTimeout is fired if the server doesn't respond to keep-alive pings
client.PingTimeout += (sender, args) =>
{
Console.WriteLine("[Event] Server ping timeout!");
};
// InputReadingReceived is fired when subscribed sensor data arrives
client.InputReadingReceived += (sender, args) =>
{
Console.WriteLine($"[Event] Input reading from device {args.DeviceIndex}: {args.Reading}");
};
// Connect asynchronously - this may take time due to network
await client.ConnectAsync("ws://127.0.0.1:12345");
// Scanning is also async - we start it and wait for events
Console.WriteLine("Turn on devices now (events will be printed)...\n");
await client.StartScanningAsync();
// Use CancellationToken for timeouts and cancellation
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
// Wait for user input or timeout
Console.WriteLine("Press Enter to stop scanning (or wait 10 seconds)...");
await Task.Run(() => Console.ReadLine(), cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Scan timeout reached.");
}
await client.StopScanningAsync();
// Demonstrate concurrent operations
var devices = client.Devices;
if (devices.Length > 0)
{
// Send commands to all devices concurrently
var tasks = devices
.Where(d => d.HasOutput(Buttplug.Core.Messages.OutputType.Vibrate))
.Select(async device =>
{
await device.RunOutputAsync(DeviceOutput.Vibrate.Percent(0.5));
await Task.Delay(500);
await device.StopAsync();
});
// Wait for all commands to complete
await Task.WhenAll(tasks);
}
else
{
Console.WriteLine("No devices connected.");
}
Console.WriteLine("\nPress Enter to disconnect...");
Console.ReadLine();
await client.DisconnectAsync();
Console.WriteLine("Disconnected.");
// Buttplug Web - Async Patterns Example
//
// This example demonstrates async/await patterns and event handling
// in the Buttplug library. The library is fully async - all operations
// that might block (network, device communication) use async/await.
//
// Include Buttplug via CDN:
// <script src="https://cdn.jsdelivr.net/npm/buttplug@4.0.0/dist/web/buttplug.min.js"></script>
async function runAsyncExample() {
console.log("Running async example");
const client = new Buttplug.ButtplugClient("Async Example");
// Events in buttplug-js use EventEmitter3.
// You can use addListener or on to subscribe to events.
// 'deviceadded' is fired when a new device connects
client.addListener("deviceadded", async (device) => {
// Note: Event handlers can be async!
console.log(`[Event] Device added: ${device.name}`);
// You can interact with the device in the event handler
if (device.hasOutput(Buttplug.OutputType.Vibrate)) {
console.log(" Sending welcome vibration...");
await device.runOutput(Buttplug.DeviceOutput.Vibrate.percent(0.25));
await new Promise(r => setTimeout(r, 200));
await device.stop();
}
});
// 'deviceremoved' is fired when a device disconnects
client.addListener("deviceremoved", (device) => {
console.log(`[Event] Device removed: ${device.name}`);
});
// 'scanningfinished' is fired when scanning completes
// (some protocols scan continuously until stopped)
client.addListener("scanningfinished", () => {
console.log("[Event] Scanning finished");
});
// 'disconnect' is fired when the server connection drops
client.addListener("disconnect", () => {
console.log("[Event] Server disconnected!");
});
// 'inputreading' is fired when subscribed sensor data arrives
client.addListener("inputreading", (reading) => {
console.log(`[Event] Input reading: ${JSON.stringify(reading)}`);
});
// Connect asynchronously - this may take time due to network
console.log("Connecting to server...");
const connector = new Buttplug.ButtplugBrowserWebsocketClientConnector("ws://127.0.0.1:12345");
await client.connect(connector);
console.log("Connected!");
// Scanning is also async - we start it and wait for events
console.log("Starting scan. Turn on devices now...");
console.log("(Events will be printed as devices connect)");
await client.startScanning();
// Demonstrate concurrent operations after 5 seconds
setTimeout(async () => {
console.log("\nDemonstrating concurrent device control...");
// Convert devices Map to array
const devices = Array.from(client.devices.values());
if (devices.length > 0) {
// Send commands to all devices concurrently
const tasks = devices
.filter((d) => d.hasOutput(Buttplug.OutputType.Vibrate))
.map(async (device) => {
console.log(` Vibrating ${device.name}...`);
await device.runOutput(Buttplug.DeviceOutput.Vibrate.percent(0.5));
await new Promise(r => setTimeout(r, 500));
await device.stop();
console.log(` ${device.name} stopped.`);
});
// Wait for all commands to complete
await Promise.all(tasks);
console.log("All devices stopped.");
} else {
console.log("No devices connected.");
}
}, 5000);
}
// Buttplug TypeScript - Async Patterns Example
//
// This example demonstrates async/await patterns and event handling
// in the Buttplug library. The library is fully async - all operations
// that might block (network, device communication) use async/await.
//
// Prerequisites:
// 1. Install Intiface Central: https://intiface.com/central
// 2. Start the server in Intiface Central
// 3. Run: npx ts-node --esm async-example.ts
import {
ButtplugClient,
ButtplugNodeWebsocketClientConnector,
ButtplugClientDevice,
DeviceOutput,
OutputType,
} 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();
});
});
}
function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function main(): Promise<void> {
const client = new ButtplugClient('Async Example');
// Events in buttplug-js use EventEmitter3.
// You can use addListener or on to subscribe to events.
// 'deviceadded' is fired when a new device connects
client.addListener('deviceadded', async (device: ButtplugClientDevice) => {
// Note: Event handlers can be async!
console.log(`[Event] Device added: ${device.name}`);
// You can interact with the device in the event handler
if (device.hasOutput(OutputType.Vibrate)) {
console.log(' Sending welcome vibration...');
await device.runOutput(DeviceOutput.Vibrate.percent(0.25));
await delay(200);
await device.stop();
}
});
// 'deviceremoved' is fired when a device disconnects
client.addListener('deviceremoved', (device: ButtplugClientDevice) => {
console.log(`[Event] Device removed: ${device.name}`);
});
// 'scanningfinished' is fired when scanning completes
// (some protocols scan continuously until stopped)
client.addListener('scanningfinished', () => {
console.log('[Event] Scanning finished');
});
// 'disconnect' is fired when the server connection drops
client.addListener('disconnect', () => {
console.log('[Event] Server disconnected!');
});
// 'inputreading' is fired when subscribed sensor data arrives
client.addListener('inputreading', (reading: unknown) => {
console.log(`[Event] Input reading: ${JSON.stringify(reading)}`);
});
// Connect asynchronously - this may take time due to network
console.log('Connecting to server...');
const connector = new ButtplugNodeWebsocketClientConnector(
'ws://127.0.0.1:12345'
);
await client.connect(connector);
console.log('Connected!\n');
// Scanning is also async - we start it and wait for events
console.log('Starting scan. Turn on devices now...');
console.log('(Events will be printed as devices connect)\n');
await client.startScanning();
// Wait for user input
await waitForEnter('Press Enter to stop scanning...');
await client.stopScanning();
// Demonstrate concurrent operations
console.log('\nDemonstrating concurrent device control...');
const devices = Array.from(client.devices.values());
if (devices.length > 0) {
// Send commands to all devices concurrently
const tasks = devices
.filter((d) => d.hasOutput(OutputType.Vibrate))
.map(async (device) => {
console.log(` Vibrating ${device.name}...`);
await device.runOutput(DeviceOutput.Vibrate.percent(0.5));
await delay(500);
await device.stop();
console.log(` ${device.name} stopped.`);
});
// Wait for all commands to complete
await Promise.all(tasks);
console.log('All devices stopped.');
} else {
console.log('No devices connected.');
}
await waitForEnter('\nPress Enter to disconnect...');
await client.disconnect();
console.log('Disconnected.');
}
main().catch(console.error);
"""Connection - Connect to a Buttplug server.
This is the simplest possible Buttplug example. It connects to a
Buttplug server (like Intiface Central) and shows connection status.
Prerequisites:
1. Install Intiface Central: https://intiface.com/central/
2. Start Intiface Central and click "Start Server"
3. Run this script: python connection.py
"""
import asyncio
from buttplug import ButtplugClient, ButtplugError
async def main() -> None:
# Create a client with your application's name
client = ButtplugClient("Connection Example")
try:
# Connect to the server (Intiface Central default address)
print("Connecting to server...")
await client.connect("ws://127.0.0.1:12345")
print(f"Connected to: {client.server_name}")
# Connection is established - you can now scan for devices
print("Connection successful!")
except ButtplugError as e:
# Handle connection errors
print(f"Failed to connect: {e}")
return
finally:
# Always disconnect when done
if client.connected:
await client.disconnect()
print("Disconnected.")
if __name__ == "__main__":
asyncio.run(main())
Dealing With Errors
As with all technology, things in Buttplug can and often will go wrong. Due to the context of Buttplug, the user may be having sex with/via an application when things go wrong.
This means things can go very, very wrong.
With that in mind, errors are covered before providing information on how to use things, in the overly optimistic hopes that developers will keep error handling in mind when creating their applications.
Errors in Buttplug sessions come in the follow classes:
- Handshake
- Client and Server connected successfully, but something went wrong when they were negotiating the session. This could include naming problems, schema compatibility issues (see next section), or other problems.
- Message
- Something went wrong in relation to message formation or communication. For instance, a message that was only supposed to be sent by a server to a client was sent in the opposite direction.
- Device
- Something went wrong with a device. For instance, the device may no longer be connected, or a message was sent to a device that has no capabilities to handle it.
- Ping
- If the ping system is in use, this means a ping was missed and the connection is no longer valid.
- Unknown
- Reserved for instances where a newer server version is talking to an older client version, and may have error types that would not be recognized by the older client. See next section for more info on this.
Custom exceptions or errors may also be thrown by implementations of Buttplug. For instance, a Connector may throw a custom error or exception based on the type of transport it is using. For more information, see the documentation of the specific Buttplug implementation you are using.
- Rust
- C#
- Javascript (Web)
- TypeScript
- Python
use buttplug_client::ButtplugClientError;
use buttplug_core::errors::ButtplugError;
#[allow(dead_code)]
fn handle_error(error: ButtplugClientError) {
match error {
ButtplugClientError::ButtplugConnectorError(_details) => {}
ButtplugClientError::ButtplugError(error) => match error {
ButtplugError::ButtplugHandshakeError(_details) => {}
ButtplugError::ButtplugDeviceError(_details) => {}
ButtplugError::ButtplugMessageError(_details) => {}
ButtplugError::ButtplugPingError(_details) => {}
ButtplugError::ButtplugUnknownError(_details) => {}
},
ButtplugClientError::ButtplugOutputCommandConversionError(_details) => {},
ButtplugClientError::ButtplugMultipleInputAvailableError(_details) => {},
}
}
fn main() {
// nothing to do here
}
// Buttplug C# - Exception Handling Example
//
// This example demonstrates the different exception types in Buttplug
// and how to handle them. This is a reference for error handling patterns.
using Buttplug.Client;
using Buttplug.Core;
// All Buttplug exceptions inherit from ButtplugException.
// Here's the hierarchy:
//
// ButtplugException (base class)
// ├── ButtplugClientConnectorException - Connection/transport issues
// ├── ButtplugHandshakeException - Client/server version mismatch
// ├── ButtplugDeviceException - Device communication errors
// ├── ButtplugMessageException - Invalid message format/content
// └── ButtplugPingException - Server ping timeout
void HandleButtplugException(ButtplugException ex)
{
// Pattern match on the specific exception type
switch (ex)
{
case ButtplugClientConnectorException connEx:
// The connector couldn't establish or maintain connection.
// Causes: server not running, wrong address, network issues,
// SSL/TLS problems, connection dropped.
Console.WriteLine($"[Connector Error] {connEx.Message}");
Console.WriteLine("Check that the server is running and accessible.");
break;
case ButtplugHandshakeException hsEx:
// Client and server couldn't agree on protocol version.
// Usually means you need to upgrade client or server.
Console.WriteLine($"[Handshake Error] {hsEx.Message}");
Console.WriteLine("Client and server versions may be incompatible.");
break;
case ButtplugDeviceException devEx:
// Something went wrong communicating with a device.
// Causes: device disconnected, invalid command for device,
// device rejected command, hardware error.
Console.WriteLine($"[Device Error] {devEx.Message}");
Console.WriteLine("The device may have disconnected or doesn't support this command.");
break;
case ButtplugMessageException msgEx:
// The message sent was invalid.
// Causes: malformed message, missing required fields,
// invalid parameter values.
Console.WriteLine($"[Message Error] {msgEx.Message}");
Console.WriteLine("This usually indicates a bug in the client library or application.");
break;
case ButtplugPingException pingEx:
// Server didn't receive ping in time, connection terminated.
// The ping system ensures dead connections are detected.
Console.WriteLine($"[Ping Error] {pingEx.Message}");
Console.WriteLine("Connection was lost due to ping timeout.");
break;
default:
// Unknown or future exception type
Console.WriteLine($"[Buttplug Error] {ex.Message}");
break;
}
}
// Demonstrate catching exceptions during connection
var client = new ButtplugClient("Exception Example");
Console.WriteLine("Exception Handling Example");
Console.WriteLine("==========================\n");
// Example 1: Connection error (server not running)
Console.WriteLine("1. Attempting to connect to non-existent server...");
try
{
await client.ConnectAsync("ws://127.0.0.1:99999");
}
catch (ButtplugException ex)
{
HandleButtplugException(ex);
}
// Example 2: Using the ErrorReceived event for async errors
Console.WriteLine("\n2. Setting up error event handler...");
client.ErrorReceived += (sender, args) =>
{
Console.WriteLine($"[Async Error Event] {args.Exception.Message}");
HandleButtplugException(args.Exception);
};
// Example 3: Handling errors when sending commands after disconnect
Console.WriteLine("\n3. Demonstrating error after disconnect...");
try
{
// Try to connect to actual server this time
await client.ConnectAsync("ws://127.0.0.1:12345");
Console.WriteLine("Connected successfully.");
// Scan briefly to get a device
await client.StartScanningAsync();
await Task.Delay(1000);
await client.StopScanningAsync();
if (client.Devices.Length > 0)
{
var device = client.Devices[0];
Console.WriteLine($"Found device: {device.Name}");
// Disconnect
await client.DisconnectAsync();
Console.WriteLine("Disconnected.");
// Now try to send a command - this will throw
Console.WriteLine("Attempting to send command after disconnect...");
await device.RunOutputAsync(DeviceOutput.Vibrate.Percent(0.5));
}
else
{
Console.WriteLine("No devices found to test with.");
await client.DisconnectAsync();
}
}
catch (ButtplugException ex)
{
HandleButtplugException(ex);
}
Console.WriteLine("\nPress Enter to exit...");
Console.ReadLine();
// Buttplug Web - Error Handling Example
//
// This example demonstrates the different error types in Buttplug
// and how to handle them. This is a reference for error handling patterns.
//
// Include Buttplug via CDN:
// <script src="https://cdn.jsdelivr.net/npm/buttplug@4.0.0/dist/web/buttplug.min.js"></script>
// All Buttplug errors inherit from ButtplugError.
// Here's the hierarchy:
//
// ButtplugError (base class)
// +-- ButtplugClientConnectorException - Connection/transport issues
// +-- ButtplugInitError - Client/server version mismatch
// +-- ButtplugDeviceError - Device communication errors
// +-- ButtplugMessageError - Invalid message format/content
// +-- ButtplugPingError - Server ping timeout
function handleButtplugError(e) {
if (e instanceof Buttplug.ButtplugClientConnectorException) {
// The connector couldn't establish or maintain connection.
// Causes: server not running, wrong address, network issues,
// SSL/TLS problems, connection dropped.
console.log(`[Connector Error] ${e.message}`);
console.log("Check that the server is running and accessible.");
} else if (e instanceof Buttplug.ButtplugInitError) {
// Client and server couldn't agree on protocol version.
// Usually means you need to upgrade client or server.
console.log(`[Init/Handshake Error] ${e.message}`);
console.log("Client and server versions may be incompatible.");
} else if (e instanceof Buttplug.ButtplugDeviceError) {
// Something went wrong communicating with a device.
// Causes: device disconnected, invalid command for device,
// device rejected command, hardware error.
console.log(`[Device Error] ${e.message}`);
console.log("The device may have disconnected or doesn't support this command.");
} else if (e instanceof Buttplug.ButtplugMessageError) {
// The message sent was invalid.
// Causes: malformed message, missing required fields,
// invalid parameter values.
console.log(`[Message Error] ${e.message}`);
console.log("This usually indicates a bug in the client library or application.");
} else if (e instanceof Buttplug.ButtplugPingError) {
// Server didn't receive ping in time, connection terminated.
// The ping system ensures dead connections are detected.
console.log(`[Ping Error] ${e.message}`);
console.log("Connection was lost due to ping timeout.");
} else if (e instanceof Buttplug.ButtplugError) {
// Unknown or future error type
console.log(`[Buttplug Error] ${e.message}`);
} else if (e instanceof Error) {
// Non-Buttplug error
console.log(`[System Error] ${e.message}`);
} else {
console.log(`[Unknown Error] ${e}`);
}
}
async function runErrorExample() {
console.log("Error Handling Example");
console.log("======================\n");
// Example 1: Connection error (server not running on wrong port)
console.log("1. Attempting to connect to non-existent server...");
const client1 = new Buttplug.ButtplugClient("Error Example");
try {
const badConnector = new Buttplug.ButtplugBrowserWebsocketClientConnector("ws://127.0.0.1:99999");
await client1.connect(badConnector);
} catch (e) {
handleButtplugError(e);
}
// Example 2: Demonstrating promise-based error handling
console.log("\n2. Demonstrating promise-based error handling...");
const client2 = new Buttplug.ButtplugClient("Promise Error Example");
const badConnector2 = new Buttplug.ButtplugBrowserWebsocketClientConnector("ws://127.0.0.1:99998");
// You can also catch errors using .catch() on promises
await client2
.connect(badConnector2)
.then(() => {
console.log("Connected (unexpected!)");
})
.catch((e) => {
console.log("Caught error using .catch():");
handleButtplugError(e);
});
// Example 3: Using try/catch with async/await
console.log("\n3. Using try/catch with async/await...");
const client3 = new Buttplug.ButtplugClient("Async Error Example");
const invalidConnector = new Buttplug.ButtplugBrowserWebsocketClientConnector("ws://notadomain.local");
try {
await client3.connect(invalidConnector);
} catch (e) {
// Check for specific Buttplug error types
console.log(`Error: ${e}`);
if (e instanceof Buttplug.ButtplugError) {
console.log("This is a Buttplug-specific error.");
if (e instanceof Buttplug.ButtplugClientConnectorException) {
console.log("Specifically, it's a connector error.");
}
} else {
console.log("This is a non-Buttplug error (system/network level).");
}
}
console.log("\nError handling example complete.");
}
// Buttplug TypeScript - Error Handling Example
//
// This example demonstrates the different error types in Buttplug
// and how to handle them. This is a reference for error handling patterns.
//
// Prerequisites:
// 1. Install Intiface Central: https://intiface.com/central
// 2. Start the server in Intiface Central
// 3. Run: npx ts-node --esm errors-example.ts
import {
ButtplugClient,
ButtplugNodeWebsocketClientConnector,
ButtplugClientConnectorException,
ButtplugError,
ButtplugDeviceError,
ButtplugInitError,
ButtplugMessageError,
ButtplugPingError,
DeviceOutput,
} 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();
});
});
}
function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// All Buttplug errors inherit from ButtplugError.
// Here's the hierarchy:
//
// ButtplugError (base class)
// +-- ButtplugClientConnectorException - Connection/transport issues
// +-- ButtplugInitError - Client/server version mismatch
// +-- ButtplugDeviceError - Device communication errors
// +-- ButtplugMessageError - Invalid message format/content
// +-- ButtplugPingError - Server ping timeout
function handleButtplugError(e: unknown): void {
if (e instanceof ButtplugClientConnectorException) {
// The connector couldn't establish or maintain connection.
// Causes: server not running, wrong address, network issues,
// SSL/TLS problems, connection dropped.
console.log(`[Connector Error] ${e.message}`);
console.log('Check that the server is running and accessible.');
} else if (e instanceof ButtplugInitError) {
// Client and server couldn't agree on protocol version.
// Usually means you need to upgrade client or server.
console.log(`[Init/Handshake Error] ${e.message}`);
console.log('Client and server versions may be incompatible.');
} else if (e instanceof ButtplugDeviceError) {
// Something went wrong communicating with a device.
// Causes: device disconnected, invalid command for device,
// device rejected command, hardware error.
console.log(`[Device Error] ${e.message}`);
console.log(
"The device may have disconnected or doesn't support this command."
);
} else if (e instanceof ButtplugMessageError) {
// The message sent was invalid.
// Causes: malformed message, missing required fields,
// invalid parameter values.
console.log(`[Message Error] ${e.message}`);
console.log(
'This usually indicates a bug in the client library or application.'
);
} else if (e instanceof ButtplugPingError) {
// Server didn't receive ping in time, connection terminated.
// The ping system ensures dead connections are detected.
console.log(`[Ping Error] ${e.message}`);
console.log('Connection was lost due to ping timeout.');
} else if (e instanceof ButtplugError) {
// Unknown or future error type
console.log(`[Buttplug Error] ${e.message}`);
} else if (e instanceof Error) {
// Non-Buttplug error
console.log(`[System Error] ${e.message}`);
} else {
console.log(`[Unknown Error] ${e}`);
}
}
async function main(): Promise<void> {
console.log('Error Handling Example');
console.log('======================\n');
// Example 1: Connection error (server not running)
console.log('1. Attempting to connect to non-existent server...');
const client1 = new ButtplugClient('Error Example');
try {
const badConnector = new ButtplugNodeWebsocketClientConnector(
'ws://127.0.0.1:99999'
);
await client1.connect(badConnector);
} catch (e) {
handleButtplugError(e);
}
// Example 2: Demonstrating promise-based error handling
console.log('\n2. Demonstrating promise-based error handling...');
const client2 = new ButtplugClient('Promise Error Example');
const badConnector2 = new ButtplugNodeWebsocketClientConnector(
'ws://127.0.0.1:99998'
);
// You can also catch errors using .catch() on promises
await client2
.connect(badConnector2)
.then(() => {
console.log('Connected (unexpected!)');
})
.catch((e) => {
console.log('Caught error using .catch():');
handleButtplugError(e);
});
// Example 3: Handling errors when sending commands after disconnect
console.log('\n3. Demonstrating error after disconnect...');
const client3 = new ButtplugClient('Disconnect Error Example');
try {
const connector = new ButtplugNodeWebsocketClientConnector(
'ws://127.0.0.1:12345'
);
await client3.connect(connector);
console.log('Connected successfully.');
// Scan briefly to get a device
await client3.startScanning();
await delay(1000);
await client3.stopScanning();
if (client3.devices.size > 0) {
const device = client3.devices.values().next().value!;
console.log(`Found device: ${device.name}`);
// Disconnect
await client3.disconnect();
console.log('Disconnected.');
// Now try to send a command - this will throw
console.log('Attempting to send command after disconnect...');
await device.runOutput(DeviceOutput.Vibrate.percent(0.5));
} else {
console.log('No devices found to test with.');
await client3.disconnect();
}
} catch (e) {
handleButtplugError(e);
}
await waitForEnter('\nPress Enter to exit...');
}
main().catch(console.error);
"""Error Handling - Handle errors gracefully.
This example shows how to handle various error conditions:
- Connection failures
- Device communication errors
- Server disconnections
Prerequisites:
1. Install Intiface Central: https://intiface.com/central/
2. Run this script (server doesn't need to be running to see error handling)
"""
import asyncio
from buttplug import (
ButtplugClient,
ButtplugConnectionError,
ButtplugDeviceError,
ButtplugError,
ButtplugHandshakeError,
ButtplugPingError,
)
async def main() -> None:
client = ButtplugClient("Error Handling Example")
# Handle disconnection events
def on_disconnect() -> None:
print("Server disconnected unexpectedly!")
client.on_disconnect = on_disconnect
# Try to connect with error handling
try:
print("Attempting to connect to server...")
await client.connect("ws://127.0.0.1:12345")
print(f"Connected to: {client.server_name}")
except ButtplugConnectionError as e:
# Server not running or network issue
print(f"Connection failed: {e}")
print("Is Intiface Central running?")
return
except ButtplugHandshakeError as e:
# Server rejected the connection (version mismatch, etc.)
print(f"Handshake failed: {e}")
return
except ButtplugError as e:
# Catch-all for other Buttplug errors
print(f"Unexpected error: {e}")
return
# Scan and control devices with error handling
try:
print("\nScanning for devices...")
await client.start_scanning()
await asyncio.sleep(3)
await client.stop_scanning()
for device in client.devices.values():
print(f"\nControlling: {device.name}")
try:
await device.vibrate(0.5)
await asyncio.sleep(1)
await device.stop()
print(" Control successful!")
except ButtplugDeviceError as e:
# Device-specific error (disconnected, doesn't support command)
print(f" Device error: {e}")
except ButtplugPingError:
# Server stopped responding
print("Server ping timeout - connection lost")
except ButtplugError as e:
print(f"Error during operation: {e}")
finally:
if client.connected:
await client.disconnect()
print("\nDisconnected cleanly.")
if __name__ == "__main__":
asyncio.run(main())
Common errors include:
- Commands sent to a disconnected device. These can be avoided by monitoring device events.
- Boundary errors, such as trying to send values outside of the range a device accepts (these can be caught in the client before they are sent to the user)
There are some errors application developers won't see. This includes:
- Device scanning errors, as these are logged and displayed by the server side (normally Intiface Central or Engine)
- Device connection issues, as device information will only be sent to the client once it's properly connected.