Skip to main content
Version: Spec v4.0

Device Information Messages

Messages that convey information about devices currently connected to the system. All of the following messages are sent Server -> Client, either in response to RequestDeviceList or on connection/disconnection of a device.


DeviceList

Description: Server reply to a client request for a device list, or sent as an event when a device is connected or disconnected.

:::tip Detecting Device Changes

In Spec V4, the DeviceAdded and DeviceRemoved messages were removed in favor of always sending the complete DeviceList. Clients are expected to maintain their own copy of the device list and diff against incoming DeviceList messages to detect which devices were added or removed. This simplifies the protocol while giving clients full flexibility in how they track device state changes.

:::

Introduced In Spec Version: 0

Last Updated In Spec Version: 4 (See Deprecated Messages for older versions.)

Fields:

  • Id (unsigned int): Message Id
  • Devices (map of indexes to device object, with each object having the following fields):
    • DeviceName (string): Descriptive name of the device, as taken from the base device configuration file.
    • DeviceIndex (unsigned integer): Index used to identify the device when sending Device Messages.
      • This is a repeat of the map key
      • Device indexes are stable only while the device appears in the latest DeviceList. Servers may reuse a removed device's index for a later connection, including a reconnection of the same physical device. Clients should discard cached feature data and subscriptions when an index disappears from DeviceList, and treat a later device with that index as a new device.
    • DeviceMessageTimingGap (unsigned integer): Minimum gap between output command dispatches to this device, in milliseconds, enforced by the server in Spec V4+. This applies to OutputCmd hardware dispatch only; InputCmd, StopCmd, and non-device lifecycle messages are not delayed. If multiple OutputCmd messages target the same device feature within the timespan defined here, the server may coalesce them and send only the latest command for that feature on the next dispatch trigger. The server still returns an Ok or Error response for every client message it accepts, even if an earlier output command is superseded before it reaches the device. StopCmd bypasses this gap and should clear any pending output command within its selection. This prevents issues with device communication busses with the possibility of buffer backup (like BLE), where devices would stop responding or update with significant delays (e.g., 30+ seconds) when commands were sent faster than the Bluetooth ConnectionInterval allowed. This relieves developers of having to regulate input from users or tune their clients. If this is set to 0, it means there is no maximum update rate imposed by this field.
    • DeviceDisplayName (optional, string): User provided display name for a device. Useful for cases where a users may have multiple of the same device connected. Optional field, not required to be included in message. Missing value means that no device display name is set, and device name should be used.
    • DeviceFeatures (map of indexes to feature objects, with each object having the following fields)
      • FeatureDescription (string): Text descriptor for a feature.
      • FeatureIndex (unsigned 32-bit integer): Index that should be used to refer to the feature in messages like OutputCmd, InputCmd, etc...
        • This is a repeat of the map key.
      • Output (optional, Object): Represents outputs that are part of this feature. This field is omitted when a feature has no outputs. A feature must include Output, Input, or both. A map of OutputType to information objects. If a feature lists multiple output types, this means that the feature can be controlled through different contexts. For instance, a feature having both Position and HwPositionWithDuration output types means that the feature can move instantaneously to a goal position, or can move to the goal position over a certain amount of time.
        • [OutputType] (OutputType as String): OutputType is used as a key here, so this would be something like Vibrate, Position, etc... Valid types are listed in the OutputCmd page IMPORTANT: Fields for this will change based on the key value. See below for which fields are valid per output type.
          • Value (Signed 32-bit integer range): Range of the value this output type can be set to. It is assumed that once a value is set, it will not be reset until OutputCmd is called again for the same feature. This can be used as a 2-dimensional value, for instance, a rotation feature that has direction may have a range of [-x, x] to denote that it can rotate in 2 different directions.
            • Valid for Output Types: All
          • Duration (Unsigned 32-bit integer range, in milliseconds): Range of duration values, in milliseconds, for output types that use time
            • Valid for Output Types: HwPositionWithDuration
      • Input (optional, Object): Represents inputs that may be part of this feature. This field is omitted when a feature has no inputs. A map of InputType to information objects.
        • [InputType] (InputType as String): InputType is used as a key here, so this field would be something like "Battery", "Pressure", etc...
          • Command (array of string: ["Read", "Subscribe", "Unsubscribe"]): Some combination of "Read" and/or "Subscribe".
          • Value (Range, array of 2 signed 32-bit integer values): Range of values that may be received from the input, if known.

:::tip Range Semantics

All ranges in Buttplug (for both Output and Input values) are:

  • Inclusive on both ends: A range of [0, 20] means values 0 through 20 are all valid
  • Contiguous from the API user's perspective: All integer values within the range are valid; there are no gaps or discrete steps exposed at the protocol level
  • Linearly interpolated: The protocol assumes uniform distribution across the range (though actual device hardware behavior may vary)

:::

:::tip Why are the DeviceIndex and FeatureIndex repeated as map keys and object fields?

DeviceIndex and FeatureIndex are how client implementations refer to a device in InputCmd and OutputCmd messages. They are the main identifiers for Buttplug. In most client implementations we've built so far, we end up using Map<number, object> types to represent Devices and Features, mapping indexes to the related objects. However, for the objects themselves, it tends be to handy for the object to know its index when forming device control messages.

With client ergonomics in mind, we just pack device info this way to begin with, so that serialization can happen from our base storage structures, and deserialization gives us the type of data structures we usually had to build by iterating through object arrays in past versions. This also gives us the added bonus of not being able to somehow pack devices with matching IDs (which would be a massive bug anyways but now it's not even structurally possible.).

There is some awkwardness in the JSON implementation of this, as object field names cannot be numeric. These are normally converted to strings when serialized, then back to numeric types automatically when deserialized for whatever language a client may be implemented in, assuming it has a decent serde library.

For those screaming "BUT ADDED SIZE AND REDUNDANCY AND YOU COULD STILL SOMEHOW PACK KEYS THAT DON'T MATCH INTERNAL INDEX FIELDS": DeviceList messages are sent a few times a minutes, so size doesn't matter. We could screw up consistency, but once again that'd be a huge bug and there's been one server implementation for 8 years.

:::

Expected Response:

None. Server-to-Client message only.

Flow Diagram:

Serialization Example:

[
{
"DeviceList": {
"Id": 1,
"Devices": {
"0": {
"DeviceName": "Test Vibrator",
"DeviceIndex": 0,
"DeviceMessageTimingGap": 0,
"DeviceFeatures": {
"0": {
"FeatureIndex": 0,
"FeatureDescription": "Clitoral Stimulator",
"Output": {
"Vibrate": {
"Value": [0, 20]
}
}
},
"1": {
"FeatureIndex": 1,
"FeatureDescription": "Insertable Stimulator",
"Output": {
"Vibrate": {
"Value": [0, 20]
}
}
},
"2": {
"FeatureIndex": 2,
"FeatureDescription": "Rotating Head with Directional Control",
"Output": {
"Rotate": {
"Value": [-20, 20]
}
}
},
"3": {
"FeatureIndex": 3,
"FeatureDescription": "Battery",
"Input": {
"Battery": {
"Value": [[0, 100]],
"Command": ["Read"]
}
}
}
}
},
"1": {
"DeviceName": "Test Stroker",
"DeviceIndex": 1,
"DeviceMessageTimingGap": 100,
"DeviceDisplayName": "User set name",
"DeviceFeatures": {
"0": {
"FeatureIndex": 0,
"FeatureDescription": "Stroker",
"Output": {
"HwPositionWithDuration": {
"Value": [0, 100],
"Duration": [0, 100000]
},
"Position": {
"Value": [0, 100]
}
}
},
"2": {
"FeatureIndex": 2,
"FeatureDescription": "Bluetooth Radio RSSI",
"Input": {
"Rssi": {
"Value": [[-100, -10]],
"Command": ["Read"]
}
}
}
}
}
}
}
}
]