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.
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
- DeviceMessageTimingGap (unsigned integer): Minimum gap between device commands, in milliseconds, enforced by the server in Spec V4+. If multiple messages are sent within the timespan defined here, the server drops earlier messages and only sends the latest command on the next trigger (a debug-level warning is logged, but no error is returned). 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 minimum update rate.
- 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
ValueCmd,SensorReadCmd, etc...- This is a repeat of the map key.
- Output (Object, may be null): Represents an outputs that are part of this feature. 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 itme.
- [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
- 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.
- [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.
- Input (Object, may be null): Represents a sensor that may be part of this feature. 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 sensor, if known.
- [InputType] (InputType as String): InputType is used as a key here, so this field
would be something like "Battery", "Pressure", etc...
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)
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": {
"Vibrate": {
"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]
}
},
"Input": {
"Position": {
"Value": [[0, 100]],
"Command": ["Read", "Subscribe"]
}
}
},
"2": {
"FeatureIndex": 2,
"FeatureDescription": "Bluetooth Radio RSSI",
"Input": {
"RSSI": {
"Value": [[-10, -100]],
"Command": ["Read"]
}
}
}
}
}
}
}
}
]