> ## Documentation Index
> Fetch the complete documentation index at: https://docs.researchanddesire.com/llms.txt
> Use this file to discover all available pages before exploring further.

# BLE Protocol

> Control your OSSM wirelessly using Bluetooth Low Energy (BLE) from any compatible device or application

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.

<Info>
  BLE provides low-latency wireless control with automatic reconnection and state synchronization.
</Info>

## 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](#uuid-namespace-structure) for easy expansion and discovery.

## Characteristic reference

### Command characteristics (writable)

Use these characteristics to send commands and configure the OSSM.

#### Primary command characteristic

| Property   | Value                                  |
| ---------- | -------------------------------------- |
| UUID       | `522b443a-4f53-534d-1000-420badbabe69` |
| Properties | READ, WRITE                            |
| Purpose    | Send commands to control OSSM behavior |

**Command format**

```
set:<parameter>:<value>
go:<state>
```

**Available commands**

| Command                 | Parameter | Value Range          | Description                                                               |
| ----------------------- | --------- | -------------------- | ------------------------------------------------------------------------- |
| `set:speed:<value>`     | speed     | 0-100                | Set stroke speed percentage                                               |
| `set:stroke:<value>`    | stroke    | 0-100                | Set stroke length percentage                                              |
| `set:depth:<value>`     | depth     | 0-100                | Set penetration depth percentage                                          |
| `set:sensation:<value>` | sensation | 0-100                | Set sensation intensity percentage                                        |
| `set:pattern:<value>`   | pattern   | 0-6                  | Set stroke pattern (see [patterns](#pattern-information-characteristics)) |
| `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, time | pos: 0-100, time: ms | Send a position command in streaming mode (experimental)                  |

**Response format**

| Response                  | Meaning                                        |
| ------------------------- | ---------------------------------------------- |
| `ok:<original_command>`   | Command executed successfully                  |
| `fail:<original_command>` | Command failed (check format or current state) |

<Tip>
  Always wait for the response before sending another command. Commands are processed sequentially.
</Tip>

#### Speed knob configuration characteristic

| Property   | Value                                                               |
| ---------- | ------------------------------------------------------------------- |
| UUID       | `522b443a-4f53-534d-1010-420badbabe69`                              |
| Properties | READ, WRITE                                                         |
| Purpose    | Configure whether the physical speed knob limits BLE speed commands |

**Configuration values**

| Value             | Description                              |
| ----------------- | ---------------------------------------- |
| `true`, `1`, `t`  | Speed knob acts as upper limit (default) |
| `false`, `0`, `f` | Speed knob and BLE speed are independent |

<Tabs>
  <Tab title="Knob as limit (default)">
    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.
  </Tab>

  <Tab title="Independent mode">
    When set to `false`, BLE speed commands (0-100) are used directly as the speed value, ignoring the knob position.

    **Example**: BLE command `set:speed:80` → Effective speed = 80%

    <Warning>
      In independent mode, BLE commands can override the physical knob. Ensure your application implements appropriate safety controls.
    </Warning>
  </Tab>
</Tabs>

**Response format**

| Response              | Meaning                     |
| --------------------- | --------------------------- |
| `true` or `false`     | Current configuration value |
| `error:invalid_value` | Invalid input provided      |

#### Latency compensation configuration characteristic

| Property   | Value                                                         |
| ---------- | ------------------------------------------------------------- |
| UUID       | `522b443a-4f53-534d-1030-420badbabe69`                        |
| Properties | READ, WRITE                                                   |
| Purpose    | Configure whether the OSSM attempts to compensate for latency |

**Configuration values**

| Value             | Description                                |
| ----------------- | ------------------------------------------ |
| `true`, `1`, `t`  | Latency compensation is active             |
| `false`, `0`, `f` | Latency compensation is inactive (default) |

<Tabs>
  <Tab title="Enabled">
    When set to `true`, the OSSM expects to receive the exact commands and times from the funscript and that the commands are sent
    with the same delay between commands as the time value. By calculating the time between received commands and the time variable
    the OSSM is able to determine the latency introduced by BLE and correct for it with slight changes to the speed of the motion.
    If the time between commands does not match the intime variable this should not be enabled. The buffer setting is used to artificially
    add delay to all movements. This gives the OSSM time to receive the next command before the previous has been completed. This allows
    the removal of any delay introduced by late commands, as well as smoothing motion by combining movements travelling in the same direction.
    Funscript players should add this buffer value to their playback offset and include an additional setting for fine tuning playback offset
    to account for transmission time and any delay inherent to the funscript itself.
  </Tab>

  <Tab title="Disabled">
    When set to `false`, the OSSM will execute commands as they are received. Commands that arrive late will be executed late. Commands
    that arrive early will be executed when the prior command has finished.
  </Tab>
</Tabs>

**Response format**

| Response              | Meaning                     |
| --------------------- | --------------------------- |
| `true` or `false`     | Current configuration value |
| `error:invalid_value` | Invalid input provided      |

#### WiFi configuration characteristic

| Property   | Value                                                  |
| ---------- | ------------------------------------------------------ |
| UUID       | `522b443a-4f53-534d-1020-420badbabe69`                 |
| Properties | READ, WRITE                                            |
| Purpose    | Configure 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)

```json theme={null}
{
  "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**

| Response                      | Meaning                                           |
| ----------------------------- | ------------------------------------------------- |
| `ok:wifi:connected`           | Connected successfully                            |
| `ok:wifi:saved`               | Credentials saved, attempting connection          |
| `fail:wifi:invalid_format`    | Command format incorrect (missing pipe delimiter) |
| `fail:wifi:invalid_ssid`      | SSID length invalid (must be 1-32 characters)     |
| `fail:wifi:invalid_password`  | Password length invalid (must be 8-63 characters) |
| `fail:wifi:connection_failed` | Could not connect to network                      |
| `fail:wifi:save_failed`       | Failed to save credentials to NVS                 |

<Tip>
  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.
</Tip>

<Warning>
  WiFi credentials are transmitted in plain text over BLE. Ensure you trust the connection and are in a secure environment when configuring WiFi settings.
</Warning>

**Example usage**

```javascript theme={null}
// 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

| Property   | Value                                   |
| ---------- | --------------------------------------- |
| UUID       | `522b443a-4f53-534d-2000-420badbabe69`  |
| Properties | READ, NOTIFY                            |
| Purpose    | Monitor current OSSM state and settings |

**State JSON format**

```json theme={null}
{
  "state": "<state_name>",
  "speed": 0-100,
  "stroke": 0-100,
  "sensation": 0-100,
  "depth": 0-100,
  "pattern": 0-6
}
```

<AccordionGroup>
  <Accordion title="Available states">
    | State                         | Description                   |
    | ----------------------------- | ----------------------------- |
    | `idle`                        | Initializing                  |
    | `homing`                      | Homing sequence active        |
    | `homing.forward`              | Forward homing in progress    |
    | `homing.backward`             | Backward homing in progress   |
    | `menu`                        | Main menu displayed           |
    | `menu.idle`                   | Menu idle state               |
    | `simplePenetration`           | Simple penetration mode       |
    | `simplePenetration.idle`      | Simple penetration idle       |
    | `simplePenetration.preflight` | Pre-flight checks             |
    | `strokeEngine`                | Stroke engine mode            |
    | `strokeEngine.idle`           | Stroke engine idle            |
    | `strokeEngine.preflight`      | Pre-flight checks             |
    | `strokeEngine.pattern`        | Pattern selection             |
    | `streaming`                   | Streaming mode (experimental) |
    | `update`                      | Update mode                   |
    | `update.checking`             | Checking for updates          |
    | `update.updating`             | Update in progress            |
    | `update.idle`                 | Update idle                   |
    | `wifi`                        | WiFi setup mode               |
    | `wifi.idle`                   | WiFi setup idle               |
    | `help`                        | Help screen                   |
    | `help.idle`                   | Help idle                     |
    | `error`                       | Error state                   |
    | `error.idle`                  | Error idle                    |
    | `error.help`                  | Error help                    |
    | `restart`                     | Restart state                 |

    For the complete state machine implementation, see [OSSM.h](https://github.com/KinkyMakers/OSSM-hardware/blob/main/Software/src/ossm/OSSM.h) in the source repository.
  </Accordion>
</AccordionGroup>

**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

| Property   | Value                                  |
| ---------- | -------------------------------------- |
| UUID       | `522b443a-4f53-534d-3000-420badbabe69` |
| Properties | READ                                   |
| Purpose    | Get available stroke patterns          |

**Response format**

```json theme={null}
[
  { "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

| Property   | Value                                           |
| ---------- | ----------------------------------------------- |
| UUID       | `522b443a-4f53-534d-3010-420badbabe69`          |
| Properties | READ, WRITE                                     |
| Purpose    | Get descriptions for individual stroke patterns |

To retrieve a pattern description:

<Steps>
  <Step title="Write the pattern index">
    Write the index number (0-6) to the characteristic.
  </Step>

  <Step title="Read the description">
    Read the characteristic to receive the pattern description string.
  </Step>
</Steps>

**Pattern descriptions**

| Pattern          | Index | Description                                                      |
| ---------------- | ----- | ---------------------------------------------------------------- |
| Simple Stroke    | 0     | Acceleration, coasting, deceleration equally split; no sensation |
| Teasing Pounding | 1     | Speed shifts with sensation; balances faster strokes             |
| Robo Stroke      | 2     | Sensation varies acceleration; from robotic to gradual           |
| Half'n'Half      | 3     | Full and half depth strokes alternate; sensation affects speed   |
| Deeper           | 4     | Stroke depth increases per cycle; sensation sets count           |
| Stop'n'Go        | 5     | Pauses between strokes; sensation adjusts length                 |
| Insist           | 6     | Modifies length, maintains speed; sensation influences direction |

### Streaming commands (experimental)

<Warning>
  Position streaming is experimental and not recommended for general use. The protocol and behavior may change in future firmware updates.
</Warning>

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

| Property | Value                                     |
| -------- | ----------------------------------------- |
| Format   | `stream:<position>:<time>`                |
| Position | 0-100 (percentage of stroke)              |
| Time     | Milliseconds 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

<Note>
  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.
</Note>

**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

<Tip>
  For funscript playback, see the [Funscript Player](/ossm/tools/funscript-player) tool which handles timing and command generation automatically.
</Tip>

### GPIO characteristics

#### GPIO control characteristic

| Property   | Value                                                     |
| ---------- | --------------------------------------------------------- |
| UUID       | `522b443a-4f53-534d-4000-420badbabe69`                    |
| Properties | READ, WRITE                                               |
| Purpose    | Control 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 Pin | ESP32 GPIO |
| ----------- | ---------- |
| 1           | GPIO 2     |
| 2           | GPIO 15    |
| 3           | GPIO 22    |
| 4           | GPIO 33    |

**Response format:**

| Response                 | Meaning                       |
| ------------------------ | ----------------------------- |
| `ok:<pin>:<state>`       | Pin successfully set          |
| `error:invalid_format`   | Command format not recognized |
| `error:pin_out_of_range` | Pin number not 1-4            |

<Info>
  For detailed GPIO documentation including hardware integration examples, see [GPIO Control](/ossm/Software/communication/gpio).
</Info>

## Fleshy Thrust Sync emulation (testing only)

<Warning>
  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.
</Warning>

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

| Property            | Value                                  |
| ------------------- | -------------------------------------- |
| Service UUID        | `0000ffe0-0000-1000-8000-00805f9b34fb` |
| Characteristic UUID | `0000ffe1-0000-1000-8000-00805f9b34fb` |
| Properties          | READ, WRITE, NOTIFY, INDICATE          |

### Binary protocol format

FTS uses a compact binary format rather than text commands:

| Byte | Description    | Range             |
| ---- | -------------- | ----------------- |
| 0    | Position       | 0-180 (uint8)     |
| 1    | Time high byte | MSB of time in ms |
| 2    | Time low byte  | LSB 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)
```

<Note>
  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.
</Note>

### Why not recommended

* 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.

| Characteristic    | UUID   | Value                      |
| ----------------- | ------ | -------------------------- |
| Service           | `180A` | Device Information Service |
| Manufacturer Name | `2A29` | "Research And Desire"      |
| System ID         | `2A23` | `88: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

| Range   | Hex Range     | Description                       |
| ------- | ------------- | --------------------------------- |
| 0x0     | 0x0000–0x0FFF | Reserved for system messages      |
| 0x1     | 0x1000–0x1FFF | Commands and configuration        |
| 0x2     | 0x2000–0x2FFF | State information                 |
| 0x3     | 0x3000–0x3FFF | Pattern information               |
| 0x4     | 0x4000–0x4FFF | GPIO pin setting                  |
| 0x5–0xD | 0x5000–0xDFFF | Reserved for future use           |
| 0xE     | 0xE000–0xEFFF | Reserved for statistics           |
| 0xF     | 0xF000–0xFFFF | Experimental / sandbox (volatile) |

### Current characteristic assignments

<Tabs>
  <Tab title="Commands (0x1xxx)">
    ```
    522b443a-4f53-534d-1000-420badbabe69  # Primary command
    522b443a-4f53-534d-1010-420badbabe69  # Speed knob configuration
    522b443a-4f53-534d-1020-420badbabe69  # WiFi configuration
    ```
  </Tab>

  <Tab title="State (0x2xxx)">
    ```
    522b443a-4f53-534d-2000-420badbabe69  # Current state
    ```
  </Tab>

  <Tab title="Patterns (0x3xxx)">
    ```
    522b443a-4f53-534d-3000-420badbabe69  # Pattern list
    522b443a-4f53-534d-3010-420badbabe69  # Pattern description
    ```
  </Tab>

  <Tab title="GPIO (0x4xxx)">
    ```
    522b443a-4f53-534d-4000-420badbabe69  # GPIO control
    ```
  </Tab>
</Tabs>

## Connection management

### Advertising

| Setting              | Value                                           |
| -------------------- | ----------------------------------------------- |
| Device Name          | `OSSM`                                          |
| Service UUIDs        | Primary service + Device Information Service    |
| Advertising Interval | 20-40ms (optimized for reliability)             |
| Auto-restart         | Advertising resumes when all clients disconnect |

### Security

| Setting    | Value                                     |
| ---------- | ----------------------------------------- |
| Pairing    | "Just Works" (no authentication required) |
| Encryption | BLE Secure Connections enabled            |
| Bonding    | Disabled (no persistent pairing)          |

<Note>
  The OSSM uses "Just Works" pairing for ease of use. Anyone within BLE range can connect when the device is advertising.
</Note>

### 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

<Info>
  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.
</Info>

**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:

<Steps>
  <Step title="Scan for the device">
    Scan for BLE devices with the name "OSSM".

    <Check>
      Device appears in scan results.
    </Check>
  </Step>

  <Step title="Connect to the device">
    Initiate a GATT connection to the OSSM.
  </Step>

  <Step title="Discover services">
    Discover all services and characteristics on the device.

    <Check>
      Primary service UUID `522b443a-4f53-534d-0001-420badbabe69` is found.
    </Check>
  </Step>

  <Step title="Subscribe to state notifications">
    Enable notifications on the state characteristic to receive real-time updates.
  </Step>

  <Step title="Read initial state">
    Read the current state and pattern list to initialize your application.
  </Step>

  <Step title="Send commands">
    Begin sending commands to control the OSSM.
  </Step>
</Steps>

### Best practices

<CardGroup cols={2}>
  <Card title="Command handling" icon="terminal">
    * Validate command format before sending
    * Handle both `ok:` and `fail:` responses
    * Implement retry logic for critical commands
    * Monitor state changes to confirm command execution
  </Card>

  <Card title="State monitoring" icon="eye">
    * Subscribe to state characteristic notifications
    * Parse JSON state updates reliably
    * Handle state transitions appropriately
    * Implement timeout handling for missing updates
  </Card>
</CardGroup>

## Example code

<CodeGroup>
  ```javascript Web Bluetooth (JavaScript) theme={null}
  // 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);
  ```

  ```python Python (bleak) theme={null}
  import asyncio
  import json
  from bleak import BleakClient

  SERVICE_UUID = "522b443a-4f53-534d-0001-420badbabe69"
  COMMAND_UUID = "522b443a-4f53-534d-1000-420badbabe69"
  STATE_UUID = "522b443a-4f53-534d-2000-420badbabe69"
  SPEED_KNOB_UUID = "522b443a-4f53-534d-1010-420badbabe69"
  WIFI_CONFIG_UUID = "522b443a-4f53-534d-1020-420badbabe69"

  def state_callback(sender, data):
      """Handle state update notifications."""
      state = json.loads(data.decode())
      print(f"State update: {state}")

  async def connect_to_ossm():
      async with BleakClient("OSSM") as client:
          # Subscribe to state updates
          await client.start_notify(STATE_UUID, state_callback)

          # Configure speed knob behavior
          await client.write_gatt_char(
              SPEED_KNOB_UUID,
              "true".encode()  # Knob acts as upper limit
          )

          # Configure WiFi
          await client.write_gatt_char(
              WIFI_CONFIG_UUID,
              "set:wifi:MyNetwork|MyPassword123".encode()
          )

          # Check WiFi status
          wifi_status = await client.read_gatt_char(WIFI_CONFIG_UUID)
          print(f"WiFi status: {json.loads(wifi_status.decode())}")

          # Send a command
          command = "set:speed:75"
          await client.write_gatt_char(COMMAND_UUID, command.encode())

          # Keep connection alive to receive notifications
          await asyncio.sleep(10)

  asyncio.run(connect_to_ossm())
  ```
</CodeGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Connection fails">
    **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
  </Accordion>

  <Accordion title="Commands not working">
    **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
  </Accordion>

  <Accordion title="No state updates">
    **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
  </Accordion>

  <Accordion title="Invalid responses">
    **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
  </Accordion>
</AccordionGroup>

### Debug information

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