Skip to main content

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, stimulate via electricity, some combination of all of these, or possibly something completely different.

In order to trigger these different mechanisms, Command Messages are used. These messages all end in "Cmd". 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 messages it can accept, as well as certain parameters for those messages. For instance, if you have a vibrating buttplug (an actual buttplug toy), it may accept the following messages

  • VibrateCmd
    • This command can takes speeds from 0.0-1.0, or a list of speeds if a device contains multiple vibrators. For now, we'll assume we just have 1 vibrator.
    • When a device supports this message, it will send info about how many vibrators it has, as well as the number of actual speed steps it can use, so you aren't stuck guessing power values to send and can present the user with options or round to the nearest step in the 0.0-1.0 range.
  • StopDeviceCmd
    • This command takes no arguments, and simply stops the device from whatever its doing. The Buttplug Server has enough information to know what actions a device 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.

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::{device::ScalarValueCommand, ButtplugClient, ButtplugClientError},
core::{
connector::{
new_json_ws_client_connector,
},
message::{ClientGenericDeviceMessageAttributes},
},
};
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 = new_json_ws_client_connector("ws://localhost: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:");
for device in client.devices() {
println!("- {}", device.name());
}
wait_for_input().await;

for device in client.devices() {
fn print_attrs(attrs: &Vec<ClientGenericDeviceMessageAttributes>) {
for attr in attrs {
println!(
"{}: {} - Steps: {}",
attr.actuator_type(),
attr.feature_descriptor(),
attr.step_count()
);
}
}
println!("{} supports these actions:", device.name());
if let Some(attrs) = device.message_attributes().scalar_cmd() {
print_attrs(attrs);
}
print_attrs(&device.rotate_attributes());
print_attrs(&device.linear_attributes());
println!("Battery: {}", device.has_battery_level());
println!("RSSI: {}", device.has_rssi_level());
}

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 test_client_device = &client.devices()[0];

// 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
.vibrate(&ScalarValueCommand::ScalarValue(1.0))
.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
.vibrate_attributes()
.len();

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

// Just set all of the vibrators to full speed.
if vibrator_count > 1 {
test_client_device
.vibrate(&ScalarValueCommand::ScalarValueVec(vec![1.0, 0.0]))
.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
.vibrate(&ScalarValueCommand::ScalarValue(1.0))
.await;
if let Err(ButtplugClientError::ButtplugConnectorError(error)) = vibrate_result {
println!("Tried to send after disconnection! Error: ");
println!("{}", error);
}
wait_for_input().await;

Ok(())
}