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.
- Rust
- C#
- Javascript
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(())
}
using Buttplug.Client;
namespace DeviceControlExample
{
class Program
{
private static async Task WaitForKey()
{
Console.WriteLine("Press any key to continue.");
while (!Console.KeyAvailable)
{
await Task.Delay(1);
}
Console.ReadKey(true);
}
private static async Task RunExample()
{
var client = new ButtplugClient("Example Client");
var connector = new ButtplugWebsocketConnector(new Uri("ws://127.0.0.1:12345"));
try
{
await client.ConnectAsync(connector);
}
catch (Exception ex)
{
Console.WriteLine("Can't connect, exiting!");
Console.WriteLine($"Message: {ex.InnerException?.Message}");
await WaitForKey();
return;
}
Console.WriteLine("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.
await client.StartScanningAsync();
await client.StopScanningAsync();
Console.WriteLine("Client currently knows about these devices:");
foreach (var device in client.Devices)
{
Console.WriteLine($"- {device.Name}");
}
await WaitForKey();
foreach (var device in client.Devices)
{
Console.WriteLine($"{device.Name} supports vibration: ${device.VibrateAttributes.Count > 0}");
if (device.VibrateAttributes.Count > 0)
{
Console.WriteLine($" - Number of Vibrators: {device.VibrateAttributes.Count}");
}
}
Console.WriteLine("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.
var testClientDevice = 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.
await testClientDevice.VibrateAsync(1.0);
// 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.
//
// This vibrateType variable is just used to keep us under 80
// characters for the dev guide, so don't feel that you have to reassign
// types like this. I'm just trying to make it so you don't have to
// horizontally scroll in the manual. :)
var vibratorCount = testClientDevice.VibrateAttributes.Count;
await testClientDevice.VibrateAsync(new[] { 1.0, 0.0 });
await WaitForKey();
// And now we disconnect as usual.
await client.DisconnectAsync();
// If we try to send a command to a device after the client has
// disconnected, we'll get an exception thrown.
try
{
await testClientDevice.VibrateAsync(1.0);
}
catch (ButtplugClientConnectorException e)
{
Console.WriteLine("Tried to send after disconnection! Exception: ");
Console.WriteLine(e);
}
await WaitForKey();
}
private static void Main()
{
// Setup a client, and wait until everything is done before exiting.
RunExample().Wait();
}
}
}
// This example assumes Buttplug is brought in as a root namespace, via
// inclusion by a script tag, i.e.
//
// <script lang="javascript"
// src="https://cdn.jsdelivr.net/npm/buttplug@3.0.0/dist/web/buttplug.min.js">
// </script>
//
// If you're trying to load this, change the version to the latest available.
async function runDeviceControlExample() {
// Usual embedded connector setup.
const connector = new Buttplug.ButtplugBrowserWebsocketClientConnector("ws://127.0.0.1:12345");
const client = new Buttplug.ButtplugClient("Device Control Example");
// Set up our DeviceAdded/DeviceRemoved event handlers before connecting. If
// devices are already held to the server when we connect to it, we'll get
// "deviceadded" events on successful connect.
client.addListener("deviceadded", async (device) => {
console.log(`Device Connected: ${device.name}`);
console.log("Client currently knows about these devices:");
client.devices.forEach((device) => console.log(`- ${device.name}`));
// In Javascript, allowedMessages is a map, so we'll need to iterate its
// properties.
console.log("Sending commands");
// If we aren't working with a toy that vibrates, just return at this point.
if (device.vibrateAttributes.length == 0) {
return;
}
// Now that we know the message types for our connected device, and that our
// device handles vibration, we can send a message over!
//
// There's a couple of ways to send this message.
// 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.
try {
await device.vibrate(1.0);
} catch (e) {
console.log(e);
if (e instanceof Buttplug.ButtplugDeviceError) {
console.log("got a device error!");
}
}
await new Promise(r => setTimeout(r, 1000));
await device.stop();
const batteryAttrs = device.messageAttributes.SensorReadCmd?.filter(
(x) => x.SensorType === Buttplug.SensorType.Battery
);
if (device.hasBattery) {
console.log(`${device.name} Battery Level: ${await device.battery()}`);
}
// 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.
//
// This vibrateType variable is just used to keep us under 80
// characters for the dev guide, so don't feel that you have to reassign
// types like this. I'm just trying to make it so you don't have to
// horizontally scroll in the manual. :)
/*
var vibratorCount =
device.AllowedMessages[vibrateType].FeatureCount;
await testClientDevice.SendVibrateCmd(new [] { 1.0, 0.0 });
*/
});
client
.addListener("deviceremoved", (device) => console.log(`Device Removed: ${device.name}`));
await client.connect(connector);
// Now that everything is set up, we can scan.
await client.startScanning();
};