Skip to main content

Buttplug Client/Server Ping

Ping timing is a property of a Buttplug session negotiated when a client connects to a server. Ping is a Buttplug protocol specific negotiated keep-alive, with the server dictating the expected ping time to the client. A ping time of 0 denotes "no ping expected", while any number above that is the expected maximum amount of time in milliseconds between pings.

Ping exists to try ensuring some basic level of safety for usage if a client application locks up, remote connection is interrupted, or other horrible scenarios occur that stop ping messages from being transmitted. If a client does not send a ping message within the alloted time, the server is expected to disconnect and stop all devices that are currently active.

Keeping in line with the knowledge that reference, and most likely, all implementations of Buttplug are neither real-time constrained nor safety-guaranteed, the Ping system is more of a vaguely hopeful mitigation than a secure requirement. Your milage may vary. Don't die.

For some connectors, like the Websocket connector, there may already be ping built into that protocol, at which point Buttplug Server Ping may be redundant.

In reference library implementations of the Client, ping negotiation is handled opaquely by the client API. It is assumed that if the client's event loop fails to send a ping, the program has most likely locked up or crashed, and therefore everything should be shut down. Therefore, no code samples are provided for this. It should just work.

On ping failure in the client APIs, you should either receive some sort of event or callback denoting the error. The event or callback arguments will contain an error with an error class type of ERROR_PING. Any subsequent calls to server commands (device search/commands, etc) will fail from this point on.

using Buttplug.Client;
using Buttplug.Client.Connectors.WebsocketConnector;
using System;
using System.Threading.Tasks;

namespace PingTimeoutExample
{
internal 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()
{
// Let's go back to our websocket connector now, to discuss what the
// lifetime of a connection looks like.
//
// We'll create a connector, but this time we're going to
// include a maximum ping timeout of 1 second on the server side (this
// will need to be configured in Intiface Central).
var connector = new ButtplugWebsocketConnector(new Uri("ws://127.0.0.1:12345"));

var client = new ButtplugClient("Example Client");

// Just because the Client takes care of sending the ping message for
// you doesn't mean that a connection is always perfect. It could be
// that some code you write blocks the thread that the timer is
// sending on, or sometimes the client's connection to the server can
// be severed. In these cases, the client has events we can listen to
// so we know when either we pinged out, or the server was disconnected.
client.PingTimeout += (aObj, aEventArgs) =>
Console.WriteLine("Buttplug ping timeout!");
client.ServerDisconnect += (aObj, aEventArgs) =>
Console.WriteLine("Buttplug disconnected!");

// Let's go ahead and connect.
await client.ConnectAsync(connector);
Console.WriteLine("Client connected");

// If you'd like more information on what's going on, uncomment these 2 lines.

// client.Log += (aObj, aMsg) => Console.WriteLine(aMsg.Message.LogMessage);
// await client.RequestLogAsync(ButtplugLogLevel.Debug);

// If we just sit here and wait, the client and server will happily
// ping each other internally, so we shouldn't see anything printed
// outside of the "hit key to continue" message. Our wait function is
// async, so the event loop still spins and the timer stays happy.
await WaitForKey();

// Now we'll kill the timer. You should see both a ping timeout and a
// disconnected message from the event handlers we set up above.
Console.WriteLine("Stopping ping timer");
await WaitForKey();

// At this point we should already be disconnected, so we'll just
// show ourselves out.
}

private static void Main()
{
RunExample().Wait();
}
}
}