Skip to main content
The OSSM uses Bluetooth Low Energy (BLE) for wireless control and monitoring. You can build client applications that connect to the OSSM to send commands and receive real-time state updates.
BLE provides low-latency wireless control with automatic reconnection and state synchronization.

Before you begin

To connect to your OSSM via BLE, ensure:
  • Your device supports Bluetooth Low Energy (BLE 4.0+)
  • The OSSM is powered on and not connected to another BLE client
  • You’re within approximately 10 meters of the device

Service architecture

The OSSM implements a custom BLE service with multiple characteristics organized into functional groups.

Primary service UUID

522b443a-4f53-534d-0001-420badbabe69
Characteristics are organized by namespace ranges for easy expansion and discovery.

Characteristic reference

Command characteristics (writable)

Use these characteristics to send commands and configure the OSSM.

Primary command characteristic

PropertyValue
UUID522b443a-4f53-534d-1000-420badbabe69
PropertiesREAD, WRITE
PurposeSend commands to control OSSM behavior
Command format
set:<parameter>:<value>
go:<state>
Available commands
CommandParameterValue RangeDescription
set:speed:<value>speed0-100Set stroke speed percentage
set:stroke:<value>stroke0-100Set stroke length percentage
set:depth:<value>depth0-100Set penetration depth percentage
set:sensation:<value>sensation0-100Set sensation intensity percentage
set:pattern:<value>pattern0-6Set stroke pattern (see patterns)
go:simplePenetration--Switch to simple penetration mode from the menu
go:strokeEngine--Switch to stroke engine mode from the menu
go:streaming--Switch to streaming mode (experimental) from the menu
go:menu--Return to main menu from any mode
stream:<pos>:<time>pos, timepos: 0-100, time: msSend a position command in streaming mode (experimental)
Response format
ResponseMeaning
ok:<original_command>Command executed successfully
fail:<original_command>Command failed (check format or current state)
Always wait for the response before sending another command. Commands are processed sequentially.

Speed knob configuration characteristic

PropertyValue
UUID522b443a-4f53-534d-1010-420badbabe69
PropertiesREAD, WRITE
PurposeConfigure whether the physical speed knob limits BLE speed commands
Configuration values
ValueDescription
true, 1, tSpeed knob acts as upper limit (default)
false, 0, fSpeed knob and BLE speed are independent
When set to true, BLE speed commands (0-100) are treated as a percentage of the current physical knob position.Example: Knob at 50%, BLE command set:speed:80 → Effective speed = 40%This mode provides a hardware safety limit that users can control physically.
Response format
ResponseMeaning
true or falseCurrent configuration value
error:invalid_valueInvalid input provided

WiFi configuration characteristic

PropertyValue
UUID522b443a-4f53-534d-1020-420badbabe69
PropertiesREAD, WRITE
PurposeConfigure WiFi credentials and check connection status
Write format
set:wifi:<ssid>|<password>
The pipe character (|) is used as a delimiter between SSID and password. This allows both SSID and password to contain colons. Read format (JSON)
{
  "connected": true,
  "ssid": "NetworkName",
  "ip": "192.168.1.100",
  "rssi": -45
}
When not connected, the ssid field will contain the last saved SSID (if any), ip will be empty, and rssi will be 0. Response format
ResponseMeaning
ok:wifi:connectedConnected successfully
ok:wifi:savedCredentials saved, attempting connection
fail:wifi:invalid_formatCommand format incorrect (missing pipe delimiter)
fail:wifi:invalid_ssidSSID length invalid (must be 1-32 characters)
fail:wifi:invalid_passwordPassword length invalid (must be 8-63 characters)
fail:wifi:connection_failedCould not connect to network
fail:wifi:save_failedFailed to save credentials to NVS
WiFi credentials are persisted to non-volatile storage (NVS) and will be retained across device reboots. The device will attempt to auto-connect using saved credentials on startup.
WiFi credentials are transmitted in plain text over BLE. Ensure you trust the connection and are in a secure environment when configuring WiFi settings.
Example usage
// Configure WiFi
const wifiCommand = "set:wifi:MyNetwork|MyPassword123";
await commandChar.writeValue(new TextEncoder().encode(wifiCommand));

// Read the response
const response = await commandChar.readValue();
console.log(new TextDecoder().decode(response)); // "ok:wifi:connected"

// Check WiFi status
const wifiConfigChar = await service.getCharacteristic(
  "522b443a-4f53-534d-1020-420badbabe69"
);
const statusValue = await wifiConfigChar.readValue();
const status = JSON.parse(new TextDecoder().decode(statusValue));
console.log(status);
// { "connected": true, "ssid": "MyNetwork", "ip": "192.168.1.100", "rssi": -45 }

State characteristics (read-only)

Subscribe to these characteristics to monitor the OSSM’s current state.

Current state characteristic

PropertyValue
UUID522b443a-4f53-534d-2000-420badbabe69
PropertiesREAD, NOTIFY
PurposeMonitor current OSSM state and settings
State JSON format
{
  "state": "<state_name>",
  "speed": 0-100,
  "stroke": 0-100,
  "sensation": 0-100,
  "depth": 0-100,
  "pattern": 0-6
}
StateDescription
idleInitializing
homingHoming sequence active
homing.forwardForward homing in progress
homing.backwardBackward homing in progress
menuMain menu displayed
menu.idleMenu idle state
simplePenetrationSimple penetration mode
simplePenetration.idleSimple penetration idle
simplePenetration.preflightPre-flight checks
strokeEngineStroke engine mode
strokeEngine.idleStroke engine idle
strokeEngine.preflightPre-flight checks
strokeEngine.patternPattern selection
streamingStreaming mode (experimental)
updateUpdate mode
update.checkingChecking for updates
update.updatingUpdate in progress
update.idleUpdate idle
wifiWiFi setup mode
wifi.idleWiFi setup idle
helpHelp screen
help.idleHelp idle
errorError state
error.idleError idle
error.helpError help
restartRestart state
For the complete state machine implementation, see OSSM.h in the source repository.
Notification behavior
  • State changes trigger immediate notifications
  • Periodic notifications every 1000ms when no state change occurs
  • Notifications stop when no clients are connected

Pattern information characteristics

Pattern list characteristic

PropertyValue
UUID522b443a-4f53-534d-3000-420badbabe69
PropertiesREAD
PurposeGet available stroke patterns
Response format
[
  { "name": "Simple Stroke", "idx": 0 },
  { "name": "Teasing Pounding", "idx": 1 },
  { "name": "Robo Stroke", "idx": 2 },
  { "name": "Half'n'Half", "idx": 3 },
  { "name": "Deeper", "idx": 4 },
  { "name": "Stop'n'Go", "idx": 5 },
  { "name": "Insist", "idx": 6 }
]

Pattern description characteristic

PropertyValue
UUID522b443a-4f53-534d-3010-420badbabe69
PropertiesREAD, WRITE
PurposeGet descriptions for individual stroke patterns
To retrieve a pattern description:
1

Write the pattern index

Write the index number (0-6) to the characteristic.
2

Read the description

Read the characteristic to receive the pattern description string.
Pattern descriptions
PatternIndexDescription
Simple Stroke0Acceleration, coasting, deceleration equally split; no sensation
Teasing Pounding1Speed shifts with sensation; balances faster strokes
Robo Stroke2Sensation varies acceleration; from robotic to gradual
Half’n’Half3Full and half depth strokes alternate; sensation affects speed
Deeper4Stroke depth increases per cycle; sensation sets count
Stop’n’Go5Pauses between strokes; sensation adjusts length
Insist6Modifies length, maintains speed; sensation influences direction

Streaming commands (experimental)

Position streaming is experimental and not recommended for general use. The protocol and behavior may change in future firmware updates.
When in streaming mode (go:streaming), the OSSM accepts real-time position commands that enable synchronized playback with external content such as funscripts.

Stream position command

PropertyValue
Formatstream:<position>:<time>
Position0-100 (percentage of stroke)
TimeMilliseconds to reach the target position
Example commands
stream:0:200      # Move to 0% (retracted) in 200ms
stream:100:150    # Move to 100% (extended) in 150ms
stream:50:300     # Move to 50% (mid-stroke) in 300ms
How it works
  1. Enter streaming mode with go:streaming
  2. The OSSM homes to position 0 (fully retracted)
  3. Send stream:<pos>:<time> commands to control motion
  4. The firmware calculates the required speed to reach the target position within the specified time
  5. Motion uses maximum acceleration for responsive feel
Position 0 represents fully retracted (home), and position 100 represents fully extended. The time parameter indicates how long the motion should take, allowing the OSSM to calculate appropriate speed for smooth playback.
Requirements
  • Firmware version 3.0 or later
  • OSSM must be in streaming mode (state: streaming or streaming.idle)
  • Commands sent via the primary command characteristic
For funscript playback, see the Funscript Player tool which handles timing and command generation automatically.

GPIO characteristics

GPIO control characteristic

PropertyValue
UUID522b443a-4f53-534d-4000-420badbabe69
PropertiesREAD, WRITE
PurposeControl GPIO output pins for accessories and integrations
Write commands in the format <pin>:<state> where pin is 1-4 and state is high/low or 1/0. Pin mapping:
Logical PinESP32 GPIO
1GPIO 2
2GPIO 15
3GPIO 22
4GPIO 33
Response format:
ResponseMeaning
ok:<pin>:<state>Pin successfully set
error:invalid_formatCommand format not recognized
error:pin_out_of_rangePin number not 1-4
For detailed GPIO documentation including hardware integration examples, see GPIO Control.

Fleshy Thrust Sync emulation (testing only)

This feature is for development testing only and is not recommended for use. It requires a special firmware build and may be removed in future versions.
The OSSM firmware can optionally emulate the Fleshy Thrust Sync (FTS) BLE protocol for compatibility testing with applications like faptap.net. This feature is disabled by default and requires compiling firmware with the PRETEND_TO_BE_FLESHY_THRUST_SYNC flag.

FTS service

PropertyValue
Service UUID0000ffe0-0000-1000-8000-00805f9b34fb
Characteristic UUID0000ffe1-0000-1000-8000-00805f9b34fb
PropertiesREAD, WRITE, NOTIFY, INDICATE

Binary protocol format

FTS uses a compact binary format rather than text commands:
ByteDescriptionRange
0Position0-180 (uint8)
1Time high byteMSB of time in ms
2Time low byteLSB of time in ms
Position mapping: 0 = fully retracted, 180 = fully extended Time format: 16-bit unsigned integer in big-endian (network byte order)

Example

To move to position 90 (50% extended) in 250ms:
Byte 0: 0x5A (90 decimal - position)
Byte 1: 0x00 (250 >> 8 = 0)
Byte 2: 0xFA (250 & 0xFF = 250)
The FTS emulation uses the same underlying streaming mechanism as the native stream:pos:time command. The OSSM must be in streaming mode for commands to take effect.
  • The FTS protocol is a third-party specification not controlled by the OSSM project
  • Protocol changes in FTS-compatible applications may break compatibility
  • The native OSSM streaming protocol (stream:pos:time) is preferred for new integrations
  • This feature exists primarily for testing compatibility with existing FTS ecosystems

Device information service

The OSSM implements the standard BLE Device Information Service for identification.
CharacteristicUUIDValue
Service180ADevice Information Service
Manufacturer Name2A29”Research And Desire”
System ID2A2388:1A:14:FF:FE:34:29:63

UUID namespace structure

The OSSM uses a structured UUID namespace for organized expansion.

Service UUID

0x0001 = Service UUID

Namespace ranges

RangeHex RangeDescription
0x00x0000–0x0FFFReserved for system messages
0x10x1000–0x1FFFCommands and configuration
0x20x2000–0x2FFFState information
0x30x3000–0x3FFFPattern information
0x40x4000–0x4FFFGPIO pin setting
0x5–0xD0x5000–0xDFFFReserved for future use
0xE0xE000–0xEFFFReserved for statistics
0xF0xF000–0xFFFFExperimental / sandbox (volatile)

Current characteristic assignments

522b443a-4f53-534d-1000-420badbabe69  # Primary command
522b443a-4f53-534d-1010-420badbabe69  # Speed knob configuration
522b443a-4f53-534d-1020-420badbabe69  # WiFi configuration

Connection management

Advertising

SettingValue
Device NameOSSM
Service UUIDsPrimary service + Device Information Service
Advertising Interval20-40ms (optimized for reliability)
Auto-restartAdvertising resumes when all clients disconnect

Security

SettingValue
Pairing”Just Works” (no authentication required)
EncryptionBLE Secure Connections enabled
BondingDisabled (no persistent pairing)
The OSSM uses “Just Works” pairing for ease of use. Anyone within BLE range can connect when the device is advertising.

Disconnection safety

When a BLE connection is lost unexpectedly, the OSSM automatically ramps down speed to prevent runaway operation. Ramp-down behavior:
  1. Connection lost detected
  2. 1 second delay — allows for brief signal dropouts without triggering
  3. 2 second ramp — speed decreases from current value to zero using ease-in-out-sine curve
  4. Device continues at zero speed until reconnected or manually stopped
The ease-in-out-sine curve provides smooth deceleration that feels natural and reduces mechanical stress. If speed was already zero at disconnect, no ramp occurs.
Client considerations:
  • Implement connection monitoring to detect disconnects quickly
  • Consider automatic reconnection logic
  • Local controls (potentiometer, encoder) remain active during and after disconnect
  • Users can manually stop via the speed knob or long-press for emergency stop

Client implementation guide

Connection flow

Follow these steps to establish a connection and begin controlling your OSSM:
1

Scan for the device

Scan for BLE devices with the name “OSSM”.
Device appears in scan results.
2

Connect to the device

Initiate a GATT connection to the OSSM.
3

Discover services

Discover all services and characteristics on the device.
Primary service UUID 522b443a-4f53-534d-0001-420badbabe69 is found.
4

Subscribe to state notifications

Enable notifications on the state characteristic to receive real-time updates.
5

Read initial state

Read the current state and pattern list to initialize your application.
6

Send commands

Begin sending commands to control the OSSM.

Best practices

Command handling

  • Validate command format before sending
  • Handle both ok: and fail: responses
  • Implement retry logic for critical commands
  • Monitor state changes to confirm command execution

State monitoring

  • Subscribe to state characteristic notifications
  • Parse JSON state updates reliably
  • Handle state transitions appropriately
  • Implement timeout handling for missing updates

Example code

// Connect to OSSM
const device = await navigator.bluetooth.requestDevice({
  filters: [{ name: "OSSM" }],
  optionalServices: ["522b443a-4f53-534d-0001-420badbabe69"],
});

const server = await device.gatt.connect();
const service = await server.getPrimaryService(
  "522b443a-4f53-534d-0001-420badbabe69"
);

// Get characteristics
const commandChar = await service.getCharacteristic(
  "522b443a-4f53-534d-1000-420badbabe69"
);
const stateChar = await service.getCharacteristic(
  "522b443a-4f53-534d-2000-420badbabe69"
);
const speedKnobConfigChar = await service.getCharacteristic(
  "522b443a-4f53-534d-1010-420badbabe69"
);
const wifiConfigChar = await service.getCharacteristic(
  "522b443a-4f53-534d-1020-420badbabe69"
);
const patternsChar = await service.getCharacteristic(
  "522b443a-4f53-534d-3000-420badbabe69"
);

// Subscribe to state updates
await stateChar.startNotifications();
stateChar.addEventListener("characteristicvaluechanged", (event) => {
  const state = JSON.parse(new TextDecoder().decode(event.target.value));
  console.log("State update:", state);
});

// Configure speed knob behavior (true = knob as limit, false = independent)
await speedKnobConfigChar.writeValue(new TextEncoder().encode("true"));

// Configure WiFi
await wifiConfigChar.writeValue(new TextEncoder().encode("set:wifi:MyNetwork|MyPassword123"));

// Check WiFi status
const wifiStatus = await wifiConfigChar.readValue();
console.log("WiFi:", JSON.parse(new TextDecoder().decode(wifiStatus)));

// Send a command
const command = "set:speed:75";
await commandChar.writeValue(new TextEncoder().encode(command));

// Read available patterns
const patterns = await patternsChar.readValue();
const patternList = JSON.parse(new TextDecoder().decode(patterns));
console.log("Available patterns:", patternList);

Troubleshooting

Symptoms: Unable to discover or connect to the OSSM.Solutions:
  • Ensure the OSSM is powered on and within range (~10 meters)
  • Check that no other device is currently connected to the OSSM
  • Restart the OSSM to reset the BLE stack
  • Try moving closer to the device
Symptoms: Commands return fail: or have no effect.Solutions:
  • Verify the command format matches the specification exactly
  • Check that the OSSM is in a state that accepts commands (e.g., strokeEngine or simplePenetration)
  • Use go:strokeEngine or go:simplePenetration first if in menu state
  • Read the current state to understand which commands are valid
Symptoms: State characteristic never updates after subscribing.Solutions:
  • Verify notification subscription was successful
  • Check that your BLE library supports notifications
  • Ensure you’re reading notifications from the correct characteristic UUID
  • Try disconnecting and reconnecting
Symptoms: Receiving unexpected or malformed data.Solutions:
  • Ensure you’re decoding responses as UTF-8 text
  • Verify JSON parsing handles the state format correctly
  • Check for encoding issues in your BLE library

Debug information

Enable ESP32 logging at DEBUG level for detailed protocol information. Monitor BLE connection status, MTU changes, and state machine transitions.