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.

image

Installation

go get github.com/hirusha-adi/buttplug-go

Quick Start

  1. Install and start Intiface Central — This is the server that connects to your devices.

  2. 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 workflow
  • connection — Connecting to a server
  • device_control — Vibrate, rotate, and position commands
  • device_control_simulated_stroker — Simulated stroker control
  • device_enumeration — Discovering devices
  • device_info — Inspecting device features
  • sensors — Battery and signal strength
  • errors — Error handling

Ported from the official Python examples in buttplug-py:

ExampleRun
applicationgo run ./examples/application
connectiongo run ./examples/connection
device_controlgo run ./examples/device_control
device_control_simulated_strokergo run ./examples/device_control_simulated_stroker
device_enumerationgo run ./examples/device_enumeration
device_infogo run ./examples/device_info
errorsgo run ./examples/errors
sensorsgo 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 fileCoverage
client_test.goClient creation, event callbacks, and disconnected-state guards
device_test.goDevice and feature capability helpers
message_sorter_test.goRequest/response message correlation
messages_test.goProtocol 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.clientclient.go
buttplug.devicedevice.go
buttplug.featurefeature.go
buttplug.commandcommand.go
buttplug.connectorconnector.go
buttplug.enumsenums.go
buttplug.errorserrors.go
buttplug._messagesinternal/messages/
buttplug._utilsinternal/utils/

API notes

  • Python async/await maps to Go context.Context on all I/O methods.
  • Event callbacks (OnDeviceAdded, etc.) are synchronous functions; set them before calling Connect.
  • ButtplugClient / NewButtplugClient aliases are provided for parity with the Python naming.

Requirements

Documentation

Support

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.