Skip to main content
Version: Spec v4.0

Device Control

We've connected, we've enumerated, now it's time for the important stuff. Device Control!

Device Capabilities

The devices Buttplug supports can do many different things. They may vibrate, stroke, rotate, some combination of all of these, or possibly something completely different.

In order to trigger these different mechanisms, OutputCmd Messages are used. For now we'll just look at vibrating and stopping, but there's descriptions of other messages in the Winning Ways section.

When a device is added, it comes with a list of features that OutputCmd can refer to, as well as certain parameters for those features. For instance, if you have a vibrating buttplug (an actual buttplug toy) can be accessed using the following messages:

  • OutputCmd
    • This will take a feature index, feature type (for this, Vibrate), and a value in steps/percent, which the client library is expected to turn into a message for you.
  • StopCmd
    • This command takes no arguments, and simply stops a feature/device from whatever its doing. The Buttplug Server has enough information to know what actions a device or feature can perform, so it handles making sure all of those actions are stopped.

You'll usually interact with devices with Device instances, which will be different than the Buttplug Client. While the Client handles things like scanning and device lists, a Device instance will let you command a specific device.

Didn't we used to be able to update multiple features at a time?

Yes!

And it was really complicated!

And almost no one used that functionality!

And something like < 10% of the devices we support even have > 1 output features!

In the Buttplug v4 message spec, we now put the burden of messages aggregation on the Server. Users send one command for one feature of one device, and we do the work on the backend to smash all of those commands together into the smallest possible packet to go to the hardware. If you're curious about the way this works, check the Devices and Commands section of this guide.

Sending Device Messages

As a user of a Buttplug Client API, you should never be expected to send raw Buttplug Messages. Most Client APIs will provide message sending functions for you, usually attached to device objects or structures. If the device accepts the message type represented by the function you call, it should be sent to the device. Otherwise, you'll receive an error about message compatibility.

use buttplug_client::{
ButtplugClient, ButtplugClientError, connector::ButtplugRemoteClientConnector, device::{ClientDeviceCommandValue, ClientDeviceOutputCommand}, serializer::ButtplugClientJSONSerializer
};

use buttplug_core::message::OutputType;
use buttplug_transport_websocket_tungstenite::ButtplugWebsocketClientTransport;
use strum::IntoEnumIterator;
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<()> {
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?;

println!("Connected!");

// You usually shouldn't run Start/Stop scanning back-to-back like
// this, but with TestDevice we know our device will be found when we
// call StartScanning, so we can get away with it.
client.start_scanning().await?;
client.stop_scanning().await?;
println!("Client currently knows about these devices:");
let mut device_index: i32 = -1;
for (i, device) in client.devices() {
device_index = i as i32;
println!("- {}", device.name());
}
wait_for_input().await;

for (_, device) in client.devices() {
println!("{} supports these outputs:", device.name());
for output_type in OutputType::iter() {
for feature in device.device_features().values() {
if let Some(output) = feature.feature().output()
&& output.contains(output_type)
{
println!("- {}", output_type);
}
}
}
}

println!("Sending commands");

// Now that we know the message types for our connected device, we
// can send a message over! Seeing as we want to stick with the
// modern generic messages, we'll go with VibrateCmd.
//
// There's a couple of ways to send this message.
let devices = client.devices();
let test_client_device = devices.get(&(device_index as u32)).unwrap();

// We can use the convenience functions on ButtplugClientDevice to
// send the message. This version sets all of the motors on a
// vibrating device to the same speed.
test_client_device.run_output(&ClientDeviceOutputCommand::Vibrate(ClientDeviceCommandValue::Percent(0.5f64))).await?;

// If we wanted to just set one motor on and the other off, we could
// try this version that uses an array. It'll throw an exception if
// the array isn't the same size as the number of motors available as
// denoted by FeatureCount, though.
//
// You can get the vibrator count using the following code, though we
// know it's 2 so we don't really have to use it.
let vibrator_count = test_client_device.outputs(OutputType::Vibrate).len();

println!(
"{} has {} vibrators.",
test_client_device.name(),
vibrator_count,
);

// Just set all of the vibrators to full speed.
if vibrator_count > 0 {
test_client_device.run_output(&ClientDeviceOutputCommand::Vibrate(ClientDeviceCommandValue::Steps(10))).await?;
} else {
println!("Device does not have > 1 vibrators, not running multiple vibrator test.");
}

wait_for_input().await;
println!("Disconnecting");
// And now we disconnect as usual.
client.disconnect().await?;
println!("Trying error");
// If we try to send a command to a device after the client has
// disconnected, we'll get an exception thrown.
let vibrate_result = test_client_device.run_output(&ClientDeviceOutputCommand::Vibrate(ClientDeviceCommandValue::Steps(30))).await;
if let Err(ButtplugClientError::ButtplugConnectorError(error)) = vibrate_result {
println!("Tried to send after disconnection! Error: ");
println!("{}", error);
}
wait_for_input().await;

Ok(())
}