Example Application
Now that we've covered most of the basics of Buttplug, here's an example of a simple application. This application provides a simple interfaces for the following workflow:
- Scan for Devices
- List connected devices
- Allow the user to choose a device
- Allow the user to send a generic event to the chosen device
While most interaction will Buttplug will usually be more complicated or context specific than this, this example ties together all of the components of the client into a simple program.
- Rust
- C#
- Javascript (Web)
- TypeScript
- Python
// Buttplug Rust - Complete Application Example
//
// This is a complete, working example that demonstrates the full workflow
// of a Buttplug application. If you're new to Buttplug, start here!
//
// Prerequisites:
// 1. Install Intiface Central: https://intiface.com/central
// 2. Start the server in Intiface Central (click "Start Server")
// 3. Run: cargo run --bin application
use buttplug_client::{
ButtplugClient, ButtplugClientDevice, ButtplugClientError, ButtplugClientEvent, connector::ButtplugRemoteClientConnector, device::ClientDeviceOutputCommand, serializer::ButtplugClientJSONSerializer
};
use buttplug_core::message::{InputType, OutputType};
use buttplug_transport_websocket_tungstenite::ButtplugWebsocketClientTransport;
use futures::StreamExt;
use tokio::io::{self, AsyncBufReadExt, BufReader};
async fn read_line() -> String {
BufReader::new(io::stdin())
.lines()
.next_line()
.await
.unwrap()
.unwrap_or_default()
}
async fn wait_for_input() {
let _ = read_line().await;
}
fn print_device_capabilities(device: &ButtplugClientDevice) {
println!(" {}", device.name());
// Check output capabilities (things we can make the device do)
let mut outputs = Vec::new();
if device.output_available(OutputType::Vibrate) {
outputs.push("Vibrate");
}
/*
if !device.rotate_features().is_empty() {
outputs.push("Rotate");
}
if !device.oscillate_features().is_empty() {
outputs.push("Oscillate");
}
if !device.position_features().is_empty() {
outputs.push("Position");
}
*/
if !outputs.is_empty() {
println!(" Outputs: {}", outputs.join(", "));
}
// Check input capabilities (sensors we can read)
let mut inputs = Vec::new();
if device.input_available(buttplug_core::message::InputType::Battery) {
inputs.push("Battery");
}
if device.input_available(buttplug_core::message::InputType::Rssi) {
inputs.push("RSSI");
}
if !inputs.is_empty() {
println!(" Inputs: {}", inputs.join(", "));
}
println!();
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
println!("===========================================");
println!(" Buttplug Rust Application Example");
println!("===========================================\n");
// Step 1: Create a client
// The client name identifies your application to the server.
let client = ButtplugClient::new("My Buttplug Application");
// Step 2: Set up event handlers
// Get the event stream BEFORE connecting to avoid missing events.
let mut events = client.event_stream();
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 disconnected: {}", info.name());
}
ButtplugClientEvent::ServerDisconnect => {
println!("[!] Server connection lost!");
}
ButtplugClientEvent::Error(err) => {
println!("[!] Error: {}", err);
}
_ => {}
}
}
});
// Step 3: Connect to the server
println!("Connecting to Intiface Central...");
let connector = ButtplugRemoteClientConnector::<
ButtplugWebsocketClientTransport,
ButtplugClientJSONSerializer,
>::new(ButtplugWebsocketClientTransport::new_insecure_connector(
"ws://127.0.0.1:12345",
));
if let Err(e) = client.connect(connector).await {
match e {
ButtplugClientError::ButtplugConnectorError(error) => {
println!("ERROR: Could not connect to Intiface Central!");
println!("Make sure Intiface Central is running and the server is started.");
println!("Default address: ws://127.0.0.1:12345");
println!("Error: {}", error);
return Ok(());
}
_ => return Err(e.into()),
}
}
println!("Connected!\n");
// Step 4: Scan for devices
println!("Scanning for devices...");
println!("Turn on your Bluetooth/USB devices now.\n");
client.start_scanning().await?;
// Wait for devices (in a real app, you might use a UI or timeout)
println!("Press Enter when your devices are connected...");
wait_for_input().await;
client.stop_scanning().await?;
// Step 5: Check what devices we found
let devices: Vec<ButtplugClientDevice> =
client.devices().into_values().collect();
if devices.is_empty() {
println!("No devices found. Make sure your device is:");
println!(" - Turned on");
println!(" - In pairing/discoverable mode");
println!(" - Supported by Buttplug (check https://iostindex.com)");
client.disconnect().await?;
return Ok(());
}
println!("\nFound {} device(s):\n", devices.len());
// Step 6: Display device capabilities
for device in &devices {
print_device_capabilities(device);
}
// Step 7: Interactive device control
println!("=== Interactive Control ===");
println!("Commands:");
println!(" v <0-100> - Vibrate all devices at percentage");
println!(" s - Stop all devices");
println!(" b - Read battery levels");
println!(" q - Quit\n");
loop {
print!("> ");
// Flush stdout to ensure prompt is visible
use std::io::Write;
std::io::stdout().flush().ok();
let input = read_line().await.trim().to_lowercase();
if input.is_empty() {
continue;
}
if input.starts_with("v ") {
// Vibrate command
if let Ok(percent) = input[2..].parse::<u32>() {
if percent <= 100 {
let intensity = percent as f64 / 100.0;
for device in &devices {
if !device.output_available(OutputType::Vibrate) {
match device.run_output(&ClientDeviceOutputCommand::Vibrate(intensity.into())).await {
Ok(_) => println!(" {}: vibrating at {}%", device.name(), percent),
Err(e) => println!(" {}: error - {}", device.name(), e),
}
}
}
} else {
println!(" Usage: v <0-100>");
}
} else {
println!(" Usage: v <0-100>");
}
} else if input == "s" {
// Stop all devices
client.stop_all_devices().await?;
println!(" All devices stopped.");
} else if input == "b" {
// Read battery levels
for device in &devices {
if device.input_available(InputType::Battery) {
match device.battery().await {
Ok(battery) => println!(" {}: {}% battery", device.name(), battery),
Err(e) => println!(" {}: could not read battery - {}", device.name(), e),
}
} else {
println!(" {}: no battery sensor", device.name());
}
}
} else if input == "q" {
break;
} else {
println!(" Unknown command. Use v, s, b, or q.");
}
}
// Step 8: Clean up
println!("\nStopping devices and disconnecting...");
client.stop_all_devices().await?;
client.disconnect().await?;
println!("Goodbye!");
Ok(())
}
// Buttplug C# - Complete Application Example
//
// This is a complete, working example that demonstrates the full workflow
// of a Buttplug application. If you're new to Buttplug, start here!
//
// Prerequisites:
// 1. Install Intiface Central: https://intiface.com/central
// 2. Start the server in Intiface Central (click "Start Server")
// 3. Run this example
using Buttplug.Client;
using Buttplug.Core;
using Buttplug.Core.Messages;
Console.WriteLine("===========================================");
Console.WriteLine(" Buttplug C# Application Example");
Console.WriteLine("===========================================\n");
// Step 1: Create a client
// The client name identifies your application to the server.
var client = new ButtplugClient("My Buttplug Application");
// Step 2: Set up event handlers
// Always do this BEFORE connecting to avoid missing events.
client.DeviceAdded += (_, args) =>
Console.WriteLine($"[+] Device connected: {args.Device.Name}");
client.DeviceRemoved += (_, args) =>
Console.WriteLine($"[-] Device disconnected: {args.Device.Name}");
client.ServerDisconnect += (_, _) =>
Console.WriteLine("[!] Server connection lost!");
client.ErrorReceived += (_, args) =>
Console.WriteLine($"[!] Error: {args.Exception.Message}");
// Step 3: Connect to the server
Console.WriteLine("Connecting to Intiface Central...");
try
{
await client.ConnectAsync("ws://127.0.0.1:12345");
}
catch (ButtplugClientConnectorException)
{
Console.WriteLine("ERROR: Could not connect to Intiface Central!");
Console.WriteLine("Make sure Intiface Central is running and the server is started.");
Console.WriteLine("Default address: ws://127.0.0.1:12345");
return;
}
Console.WriteLine("Connected!\n");
// Step 4: Scan for devices
Console.WriteLine("Scanning for devices...");
Console.WriteLine("Turn on your Bluetooth/USB devices now.\n");
await client.StartScanningAsync();
// Wait for devices (in a real app, you might use a UI or timeout)
Console.WriteLine("Press Enter when your devices are connected...");
Console.ReadLine();
await client.StopScanningAsync();
// Step 5: Check what devices we found
var devices = client.Devices;
if (devices.Length == 0)
{
Console.WriteLine("No devices found. Make sure your device is:");
Console.WriteLine(" - Turned on");
Console.WriteLine(" - In pairing/discoverable mode");
Console.WriteLine(" - Supported by Buttplug (check https://iostindex.com)");
await client.DisconnectAsync();
return;
}
Console.WriteLine($"\nFound {devices.Length} device(s):\n");
// Step 6: Display device capabilities
foreach (var device in devices)
{
Console.WriteLine($" {device.Name}");
// Check output capabilities (things we can make the device do)
var outputs = new List<string>();
if (device.HasOutput(OutputType.Vibrate)) outputs.Add("Vibrate");
if (device.HasOutput(OutputType.Rotate)) outputs.Add("Rotate");
if (device.HasOutput(OutputType.Oscillate)) outputs.Add("Oscillate");
if (device.HasOutput(OutputType.Position)) outputs.Add("Position");
if (device.HasOutput(OutputType.Constrict)) outputs.Add("Constrict");
if (outputs.Count > 0)
Console.WriteLine($" Outputs: {string.Join(", ", outputs)}");
// Check input capabilities (sensors we can read)
var inputs = new List<string>();
if (device.HasInput(InputType.Battery)) inputs.Add("Battery");
if (device.HasInput(InputType.RSSI)) inputs.Add("RSSI");
if (device.HasInput(InputType.Button)) inputs.Add("Button");
if (device.HasInput(InputType.Pressure)) inputs.Add("Pressure");
if (inputs.Count > 0)
Console.WriteLine($" Inputs: {string.Join(", ", inputs)}");
Console.WriteLine();
}
// Step 7: Interactive device control
Console.WriteLine("=== Interactive Control ===");
Console.WriteLine("Commands:");
Console.WriteLine(" v <0-100> - Vibrate all devices at percentage");
Console.WriteLine(" s - Stop all devices");
Console.WriteLine(" b - Read battery levels");
Console.WriteLine(" q - Quit\n");
while (true)
{
Console.Write("> ");
var input = Console.ReadLine()?.Trim().ToLower();
if (string.IsNullOrEmpty(input)) continue;
try
{
if (input.StartsWith("v "))
{
// Vibrate command
if (int.TryParse(input[2..], out var percent) && percent >= 0 && percent <= 100)
{
var intensity = percent / 100.0;
foreach (var device in devices)
{
if (device.HasOutput(OutputType.Vibrate))
{
await device.RunOutputAsync(DeviceOutput.Vibrate.Percent(intensity));
Console.WriteLine($" {device.Name}: vibrating at {percent}%");
}
}
}
else
{
Console.WriteLine(" Usage: v <0-100>");
}
}
else if (input == "s")
{
// Stop all devices
await client.StopAllDevicesAsync();
Console.WriteLine(" All devices stopped.");
}
else if (input == "b")
{
// Read battery levels
foreach (var device in devices)
{
if (device.HasInput(InputType.Battery))
{
var battery = await device.BatteryAsync();
Console.WriteLine($" {device.Name}: {battery * 100:F0}% battery");
}
else
{
Console.WriteLine($" {device.Name}: no battery sensor");
}
}
}
else if (input == "q")
{
break;
}
else
{
Console.WriteLine(" Unknown command. Use v, s, b, or q.");
}
}
catch (ButtplugDeviceException ex)
{
Console.WriteLine($" Device error: {ex.Message}");
}
catch (ButtplugException ex)
{
Console.WriteLine($" Error: {ex.Message}");
}
}
// Step 8: Clean up
Console.WriteLine("\nStopping devices and disconnecting...");
await client.StopAllDevicesAsync();
await client.DisconnectAsync();
Console.WriteLine("Goodbye!");
// Buttplug Web - Complete Application Example
//
// This is a complete, working example that demonstrates the full workflow
// of a Buttplug application in a browser. If you're new to Buttplug, start here!
//
// Prerequisites:
// 1. Install Intiface Central: https://intiface.com/central
// 2. Start the server in Intiface Central (click "Start Server")
// 3. Include Buttplug via CDN in your HTML:
// <script src="https://cdn.jsdelivr.net/npm/buttplug@4.0.0/dist/web/buttplug.min.js"></script>
// 4. Call runApplicationExample() from your page
async function runApplicationExample() {
console.log("===========================================");
console.log(" Buttplug Web Application Example");
console.log("===========================================\n");
// Step 1: Create a client
// The client name identifies your application to the server.
const client = new Buttplug.ButtplugClient("My Buttplug Application");
// Step 2: Set up event handlers
// Always do this BEFORE connecting to avoid missing events.
client.addListener("deviceadded", (device) => {
console.log(`[+] Device connected: ${device.name}`);
});
client.addListener("deviceremoved", (device) => {
console.log(`[-] Device disconnected: ${device.name}`);
});
client.addListener("disconnect", () => {
console.log("[!] Server connection lost!");
});
// Step 3: Connect to the server
console.log("Connecting to Intiface Central...");
try {
const connector = new Buttplug.ButtplugBrowserWebsocketClientConnector(
"ws://127.0.0.1:12345"
);
await client.connect(connector);
} catch (e) {
if (e instanceof Buttplug.ButtplugClientConnectorException) {
alert(
"Could not connect to Intiface Central!\n\n" +
"Make sure Intiface Central is running and the server is started.\n" +
"Default address: ws://127.0.0.1:12345"
);
console.log("ERROR: Could not connect to Intiface Central!");
return;
}
throw e;
}
console.log("Connected!\n");
// Step 4: Scan for devices
console.log("Scanning for devices...");
console.log("Turn on your Bluetooth/USB devices now.\n");
await client.startScanning();
// Wait for devices (using alert/confirm for browser interaction)
alert("Scanning for devices...\n\nTurn on your devices, then click OK when ready.");
await client.stopScanning();
// Step 5: Check what devices we found
const devices = Array.from(client.devices.values());
if (devices.length === 0) {
alert(
"No devices found!\n\n" +
"Make sure your device is:\n" +
"- Turned on\n" +
"- In pairing/discoverable mode\n" +
"- Supported by Buttplug (check https://iostindex.com)"
);
console.log("No devices found.");
await client.disconnect();
return;
}
console.log(`\nFound ${devices.length} device(s):\n`);
// Step 6: Display device capabilities
for (const device of devices) {
console.log(` ${device.name}`);
// Check output capabilities
const outputs = [];
if (device.hasOutput(Buttplug.OutputType.Vibrate)) outputs.push("Vibrate");
if (device.hasOutput(Buttplug.OutputType.Rotate)) outputs.push("Rotate");
if (device.hasOutput(Buttplug.OutputType.Oscillate)) outputs.push("Oscillate");
if (device.hasOutput(Buttplug.OutputType.Position)) outputs.push("Position");
if (device.hasOutput(Buttplug.OutputType.Constrict)) outputs.push("Constrict");
if (outputs.length > 0) {
console.log(` Outputs: ${outputs.join(", ")}`);
}
// Check input capabilities
const inputs = [];
if (device.hasInput(Buttplug.InputType.Battery)) inputs.push("Battery");
if (device.hasInput(Buttplug.InputType.RSSI)) inputs.push("RSSI");
if (inputs.length > 0) {
console.log(` Inputs: ${inputs.join(", ")}`);
}
}
// Step 7: Interactive device control
console.log("\n=== Interactive Control ===");
console.log("Use the prompts to control devices.");
let running = true;
while (running) {
const input = prompt(
"Commands:\n" +
" v <0-100> - Vibrate all devices at percentage\n" +
" s - Stop all devices\n" +
" b - Read battery levels\n" +
" q - Quit\n\n" +
"Enter command:"
);
if (input === null) {
// User clicked Cancel
running = false;
continue;
}
const cmd = input.trim().toLowerCase();
if (!cmd) continue;
try {
if (cmd.startsWith("v ")) {
// Vibrate command
const percentStr = cmd.slice(2);
const percent = parseInt(percentStr, 10);
if (!isNaN(percent) && percent >= 0 && percent <= 100) {
const intensity = percent / 100.0;
for (const device of devices) {
if (device.hasOutput(Buttplug.OutputType.Vibrate)) {
await device.runOutput(Buttplug.DeviceOutput.Vibrate.percent(intensity));
console.log(` ${device.name}: vibrating at ${percent}%`);
}
}
} else {
alert("Usage: v <0-100>");
}
} else if (cmd === "s") {
// Stop all devices
await client.stopAllDevices();
console.log(" All devices stopped.");
} else if (cmd === "b") {
// Read battery levels
let batteryInfo = "Battery Levels:\n\n";
for (const device of devices) {
if (device.hasInput(Buttplug.InputType.Battery)) {
try {
const battery = await device.battery();
const msg = `${device.name}: ${(battery * 100).toFixed(0)}%`;
console.log(` ${msg}`);
batteryInfo += msg + "\n";
} catch (e) {
console.log(` ${device.name}: could not read battery`);
batteryInfo += `${device.name}: could not read battery\n`;
}
} else {
console.log(` ${device.name}: no battery sensor`);
batteryInfo += `${device.name}: no battery sensor\n`;
}
}
alert(batteryInfo);
} else if (cmd === "q") {
running = false;
} else {
alert("Unknown command. Use v, s, b, or q.");
}
} catch (e) {
if (e instanceof Buttplug.ButtplugDeviceError) {
console.log(` Device error: ${e.message}`);
alert(`Device error: ${e.message}`);
} else if (e instanceof Buttplug.ButtplugError) {
console.log(` Error: ${e.message}`);
alert(`Error: ${e.message}`);
} else {
throw e;
}
}
}
// Step 8: Clean up
console.log("\nStopping devices and disconnecting...");
await client.stopAllDevices();
await client.disconnect();
console.log("Goodbye!");
}
// Buttplug TypeScript - Complete Application Example
//
// This is a complete, working example that demonstrates the full workflow
// of a Buttplug application. If you're new to Buttplug, start here!
//
// Prerequisites:
// 1. Install Intiface Central: https://intiface.com/central
// 2. Start the server in Intiface Central (click "Start Server")
// 3. Run: npx ts-node --esm application-example.ts
import {
ButtplugClient,
ButtplugNodeWebsocketClientConnector,
ButtplugClientDevice,
ButtplugClientConnectorException,
ButtplugDeviceError,
ButtplugError,
DeviceOutput,
OutputType,
InputType,
} from 'buttplug';
import * as readline from 'readline';
function createReadlineInterface(): readline.Interface {
return readline.createInterface({
input: process.stdin,
output: process.stdout,
});
}
async function waitForEnter(prompt: string): Promise<void> {
const rl = createReadlineInterface();
return new Promise((resolve) => {
rl.question(prompt, () => {
rl.close();
resolve();
});
});
}
async function prompt(question: string): Promise<string> {
const rl = createReadlineInterface();
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
async function main(): Promise<void> {
console.log('===========================================');
console.log(' Buttplug TypeScript Application Example');
console.log('===========================================\n');
// Step 1: Create a client
// The client name identifies your application to the server.
const client = new ButtplugClient('My Buttplug Application');
// Step 2: Set up event handlers
// Always do this BEFORE connecting to avoid missing 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('disconnect', () => {
console.log('[!] Server connection lost!');
});
// Step 3: Connect to the server
console.log('Connecting to Intiface Central...');
try {
const connector = new ButtplugNodeWebsocketClientConnector(
'ws://127.0.0.1:12345'
);
await client.connect(connector);
} catch (e) {
if (e instanceof ButtplugClientConnectorException) {
console.log('ERROR: Could not connect to Intiface Central!');
console.log(
'Make sure Intiface Central is running and the server is started.'
);
console.log('Default address: ws://127.0.0.1:12345');
return;
}
throw e;
}
console.log('Connected!\n');
// Step 4: Scan for devices
console.log('Scanning for devices...');
console.log('Turn on your Bluetooth/USB devices now.\n');
await client.startScanning();
// Wait for devices
await waitForEnter('Press Enter when your devices are connected...');
await client.stopScanning();
// Step 5: Check what devices we found
const devices = Array.from(client.devices.values());
if (devices.length === 0) {
console.log('No devices found. Make sure your device is:');
console.log(' - Turned on');
console.log(' - In pairing/discoverable mode');
console.log(' - Supported by Buttplug (check https://iostindex.com)');
await client.disconnect();
return;
}
console.log(`\nFound ${devices.length} device(s):\n`);
// Step 6: Display device capabilities
for (const device of devices) {
console.log(` ${device.name}`);
// Check output capabilities (things we can make the device do)
const outputs: string[] = [];
if (device.hasOutput(OutputType.Vibrate)) outputs.push('Vibrate');
if (device.hasOutput(OutputType.Rotate)) outputs.push('Rotate');
if (device.hasOutput(OutputType.Oscillate)) outputs.push('Oscillate');
if (device.hasOutput(OutputType.Position)) outputs.push('Position');
if (device.hasOutput(OutputType.Constrict)) outputs.push('Constrict');
if (outputs.length > 0) {
console.log(` Outputs: ${outputs.join(', ')}`);
}
// Check input capabilities (sensors we can read)
const inputs: string[] = [];
if (device.hasInput(InputType.Battery)) inputs.push('Battery');
if (device.hasInput(InputType.RSSI)) inputs.push('RSSI');
if (device.hasInput(InputType.Button)) inputs.push('Button');
if (device.hasInput(InputType.Pressure)) inputs.push('Pressure');
if (inputs.length > 0) {
console.log(` Inputs: ${inputs.join(', ')}`);
}
console.log();
}
// Step 7: Interactive device control
console.log('=== Interactive Control ===');
console.log('Commands:');
console.log(' v <0-100> - Vibrate all devices at percentage');
console.log(' s - Stop all devices');
console.log(' b - Read battery levels');
console.log(' q - Quit\n');
let running = true;
while (running) {
const input = (await prompt('> ')).trim().toLowerCase();
if (!input) continue;
try {
if (input.startsWith('v ')) {
// Vibrate command
const percentStr = input.slice(2);
const percent = parseInt(percentStr, 10);
if (!isNaN(percent) && percent >= 0 && percent <= 100) {
const intensity = percent / 100.0;
for (const device of devices) {
if (device.hasOutput(OutputType.Vibrate)) {
await device.runOutput(DeviceOutput.Vibrate.percent(intensity));
console.log(` ${device.name}: vibrating at ${percent}%`);
}
}
} else {
console.log(' Usage: v <0-100>');
}
} else if (input === 's') {
// Stop all devices
await client.stopAllDevices();
console.log(' All devices stopped.');
} else if (input === 'b') {
// Read battery levels
for (const device of devices) {
if (device.hasInput(InputType.Battery)) {
const battery = await device.battery();
console.log(` ${device.name}: ${(battery * 100).toFixed(0)}% battery`);
} else {
console.log(` ${device.name}: no battery sensor`);
}
}
} else if (input === 'q') {
running = false;
} else {
console.log(' Unknown command. Use v, s, b, or q.');
}
} catch (e) {
if (e instanceof ButtplugDeviceError) {
console.log(` Device error: ${e.message}`);
} else if (e instanceof ButtplugError) {
console.log(` Error: ${e.message}`);
} else {
throw e;
}
}
}
// Step 8: Clean up
console.log('\nStopping devices and disconnecting...');
await client.stopAllDevices();
await client.disconnect();
console.log('Goodbye!');
}
main().catch(console.error);
"""Buttplug Python - Complete Application Example
This is a complete, working example that demonstrates the full workflow
of a Buttplug application. If you're new to Buttplug, start here!
Prerequisites:
1. Install Intiface Central: https://intiface.com/central
2. Start the server in Intiface Central (click "Start Server")
3. Run: python application.py
"""
import asyncio
from buttplug import ButtplugClient, DeviceOutputCommand, InputType, OutputType
from buttplug.errors import ButtplugDeviceError, ButtplugError
def print_device_capabilities(device) -> None:
"""Print the capabilities of a device."""
print(f" {device.name}")
# Check output capabilities (things we can make the device do)
outputs = []
if device.has_output(OutputType.VIBRATE):
outputs.append("Vibrate")
if device.has_output(OutputType.ROTATE):
outputs.append("Rotate")
if device.has_output(OutputType.OSCILLATE):
outputs.append("Oscillate")
if device.has_output(OutputType.POSITION) or device.has_output(
OutputType.POSITION_WITH_DURATION
):
outputs.append("Position")
if device.has_output(OutputType.CONSTRICT):
outputs.append("Constrict")
if outputs:
print(f" Outputs: {', '.join(outputs)}")
# Check input capabilities (sensors we can read)
inputs = []
if device.has_input(InputType.BATTERY):
inputs.append("Battery")
if device.has_input(InputType.RSSI):
inputs.append("RSSI")
if inputs:
print(f" Inputs: {', '.join(inputs)}")
print()
async def main() -> None:
print("===========================================")
print(" Buttplug Python Application Example")
print("===========================================\n")
# Step 1: Create a client
# The client name identifies your application to the server.
client = ButtplugClient("My Buttplug Application")
# Step 2: Set up event handlers
# Always do this BEFORE connecting to avoid missing events.
client.on_device_added = lambda d: print(f"[+] Device connected: {d.name}")
client.on_device_removed = lambda d: print(f"[-] Device disconnected: {d.name}")
client.on_disconnect = lambda: print("[!] Server connection lost!")
# Step 3: Connect to the server
print("Connecting to Intiface Central...")
try:
await client.connect("ws://127.0.0.1:12345")
except ButtplugError as e:
print("ERROR: Could not connect to Intiface Central!")
print("Make sure Intiface Central is running and the server is started.")
print("Default address: ws://127.0.0.1:12345")
print(f"Error: {e}")
return
print("Connected!\n")
# Step 4: Scan for devices
print("Scanning for devices...")
print("Turn on your Bluetooth/USB devices now.\n")
await client.start_scanning()
# Wait for devices (in a real app, you might use a UI or timeout)
input("Press Enter when your devices are connected...")
await client.stop_scanning()
# Step 5: Check what devices we found
devices = list(client.devices.values())
if not devices:
print("No devices found. Make sure your device is:")
print(" - Turned on")
print(" - In pairing/discoverable mode")
print(" - Supported by Buttplug (check https://iostindex.com)")
await client.disconnect()
return
print(f"\nFound {len(devices)} device(s):\n")
# Step 6: Display device capabilities
for device in devices:
print_device_capabilities(device)
# Step 7: Interactive device control
print("=== Interactive Control ===")
print("Commands:")
print(" v <0-100> - Vibrate all devices at percentage")
print(" s - Stop all devices")
print(" b - Read battery levels")
print(" q - Quit\n")
while True:
try:
user_input = input("> ").strip().lower()
except EOFError:
break
if not user_input:
continue
try:
if user_input.startswith("v "):
# Vibrate command
try:
percent = int(user_input[2:])
if 0 <= percent <= 100:
intensity = percent / 100.0
for device in devices:
if device.has_output(OutputType.VIBRATE):
await device.run_output(
DeviceOutputCommand(OutputType.VIBRATE, intensity)
)
print(f" {device.name}: vibrating at {percent}%")
else:
print(" Usage: v <0-100>")
except ValueError:
print(" Usage: v <0-100>")
elif user_input == "s":
# Stop all devices
await client.stop_all_devices()
print(" All devices stopped.")
elif user_input == "b":
# Read battery levels
for device in devices:
if device.has_input(InputType.BATTERY):
try:
battery = await device.battery()
print(f" {device.name}: {battery * 100:.0f}% battery")
except ButtplugDeviceError as e:
print(f" {device.name}: could not read battery - {e}")
else:
print(f" {device.name}: no battery sensor")
elif user_input == "q":
break
else:
print(" Unknown command. Use v, s, b, or q.")
except ButtplugDeviceError as e:
print(f" Device error: {e}")
except ButtplugError as e:
print(f" Error: {e}")
# Step 8: Clean up
print("\nStopping devices and disconnecting...")
await client.stop_all_devices()
await client.disconnect()
print("Goodbye!")
if __name__ == "__main__":
asyncio.run(main())