buttplug-go
June 12, 2026 · View on GitHub
The (unofficial) Go implementation of the Buttplug Intimate Hardware Control Protocol (v4) client — a 1:1 port of the official Python buttplug-py library. This was translated with the assistance of generative AI tools, but has been thoroughly tested with Intiface Central's simulations.
Installation
go get github.com/hirusha-adi/buttplug-go
Quick Start
-
Install and start Intiface Central — This is the server that connects to your devices.
-
Connect and control devices:
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/hirusha-adi/buttplug-go"
)
func main() {
ctx := context.Background()
client := buttplug.NewClient("My App")
if err := client.Connect(ctx, "ws://127.0.0.1:12345"); err != nil {
log.Fatal(err)
}
if err := client.StartScanning(ctx); err != nil {
log.Fatal(err)
}
time.Sleep(5 * time.Second)
_ = client.StopScanning(ctx)
for _, device := range client.Devices() {
fmt.Printf("Found: %s\n", device.Name())
if device.HasOutput(buttplug.OutputTypeVibrate) {
_ = device.RunOutput(ctx, buttplug.DeviceOutputCommand{
OutputType: buttplug.OutputTypeVibrate,
Value: 0.5,
})
time.Sleep(2 * time.Second)
_ = device.Stop(ctx, true, true)
}
}
_ = client.Disconnect(ctx)
}
Features
- Simple API: Unified
RunOutput()method for all output types - Full Protocol Support: Implements Buttplug protocol v4
- Idiomatic Go: Context-based I/O and strong typing throughout
- Event Callbacks: Get notified when devices connect/disconnect
Device Control
// Check device capabilities and send commands
if device.HasOutput(buttplug.OutputTypeVibrate) {
_ = device.RunOutput(ctx, buttplug.DeviceOutputCommand{
OutputType: buttplug.OutputTypeVibrate,
Value: 0.75,
})
}
if device.HasOutput(buttplug.OutputTypeRotate) {
_ = device.RunOutput(ctx, buttplug.DeviceOutputCommand{
OutputType: buttplug.OutputTypeRotate,
Value: 0.5,
})
}
if device.HasOutput(buttplug.OutputTypePositionWithDuration) {
duration := 500
_ = device.RunOutput(ctx, buttplug.DeviceOutputCommand{
OutputType: buttplug.OutputTypePositionWithDuration,
Value: 1.0,
Duration: &duration,
})
}
// Read sensors
if device.HasInput(buttplug.InputTypeBattery) {
battery, err := device.Battery(ctx)
if err == nil {
fmt.Printf("Battery: %.0f%%\n", battery*100)
}
}
// Stop device
_ = device.Stop(ctx, true, true)
Event Handling
// Set up callbacks before connecting
client.OnDeviceAdded = func(d *buttplug.ButtplugDevice) {
fmt.Printf("Connected: %s\n", d.Name())
}
client.OnDeviceRemoved = func(d *buttplug.ButtplugDevice) {
fmt.Printf("Disconnected: %s\n", d.Name())
}
client.OnScanningFinished = func() {
fmt.Println("Scan complete")
}
client.OnServerDisconnect = func() {
fmt.Println("Server disconnected!")
}
// Callbacks can start goroutines for async-style handling
client.OnDeviceAdded = func(device *buttplug.ButtplugDevice) {
if device.HasOutput(buttplug.OutputTypeVibrate) {
go func() {
_ = device.RunOutput(ctx, buttplug.DeviceOutputCommand{
OutputType: buttplug.OutputTypeVibrate,
Value: 0.25,
})
}()
}
}
Examples
See the examples/ directory for more detailed examples:
application— Complete application workflowconnection— Connecting to a serverdevice_control— Vibrate, rotate, and position commandsdevice_control_simulated_stroker— Simulated stroker controldevice_enumeration— Discovering devicesdevice_info— Inspecting device featuressensors— Battery and signal strengtherrors— Error handling
Ported from the official Python examples in buttplug-py:
| Example | Run |
|---|---|
| application | go run ./examples/application |
| connection | go run ./examples/connection |
| device_control | go run ./examples/device_control |
| device_control_simulated_stroker | go run ./examples/device_control_simulated_stroker |
| device_enumeration | go run ./examples/device_enumeration |
| device_info | go run ./examples/device_info |
| errors | go run ./examples/errors |
| sensors | go run ./examples/sensors |
To run examples from a clone of this repo (from the repository root):
go run ./examples/application
go run ./examples/device_control
All examples expect Intiface Central running at ws://127.0.0.1:12345.
Tests
Unit tests live alongside the library source as *_test.go files in the repository root. They exercise the public API and protocol message handling without requiring a live Buttplug server.
| Test file | Coverage |
|---|---|
client_test.go | Client creation, event callbacks, and disconnected-state guards |
device_test.go | Device and feature capability helpers |
message_sorter_test.go | Request/response message correlation |
messages_test.go | Protocol message serialization and parsing |
Run the full test suite from the repository root:
go test ./...
Other useful commands:
# Verbose output
go test -v ./...
# Run tests matching a name pattern
go test -run TestClient ./...
# Run with race detection
go test -race ./...
Package layout
Python (buttplug-py) | Go (buttplug-go) |
|---|---|
buttplug.client | client.go |
buttplug.device | device.go |
buttplug.feature | feature.go |
buttplug.command | command.go |
buttplug.connector | connector.go |
buttplug.enums | enums.go |
buttplug.errors | errors.go |
buttplug._messages | internal/messages/ |
buttplug._utils | internal/utils/ |
API notes
- Python
async/awaitmaps to Gocontext.Contexton all I/O methods. - Event callbacks (
OnDeviceAdded, etc.) are synchronous functions; set them before callingConnect. ButtplugClient/NewButtplugClientaliases are provided for parity with the Python naming.
Requirements
- Go 1.22+
- Intiface Central or another Buttplug server
Documentation
- Buttplug Developer Guide
- Protocol Specification
- Intiface Central — recommended Buttplug server
- Device compatibility list
Support
- Discord — Community chat and support
- GitHub Issues — Bug reports and feature requests
- Contributing
- Patreon / GitHub Sponsors — Support Buttplug development
License
This project is licensed under the BSD 3-Clause License. See LICENSE.
The Go client is a port of buttplug-py; the Buttplug protocol is maintained by Nonpolynomial Labs.