# Coyote 3.0 — Research Notes ## Device **DG-Lab Coyote 3.0** — biphasic pulse generator, 2 independent channels (A/B). - BLE name: `47L121000` - Service UUID: `0x180C` - Write characteristic: `0x150A` (commands in) - Notify characteristic: `0x150B` (responses out) - Base UUID: `0000XXXX-0000-1000-8000-00805f9b34fb` - Strength range: 0–200 per channel (amplitude ceiling) - Waveform frequency byte: 10–240 (compressed from 10–1000ms) - Waveform intensity byte: 0–100 (pulse width, relative) - Output window: 25ms; commands carry 4 ticks = 100ms of output ## How waveforms work The box outputs **biphasic pulses**. A waveform is a time-series of two parameters: 1. **Frequency** = pulse repetition period (ms). Low ms = high Hz = buzzy/tingly. High ms = low Hz = deep/thumpy. - 10ms ≈ 100Hz, 50ms ≈ 20Hz, 100ms ≈ 10Hz, 1000ms ≈ 1Hz 2. **Intensity** = pulse width (0–100, relative). Wider pulse = stronger feel. The intensity envelope is what makes a pattern pleasurable vs. sharp — slow swells/fades, not square edges. **Strength** (0–200) is the hard amplitude ceiling per channel, set separately. Do expression in the 0–100 intensity envelope; keep strength modest. ### Frequency compression (input ms → device byte 10–240) ``` 10–100 → identity 101–600 → (input - 100) / 5 + 100 601–1000 → (input - 600) / 10 + 200 ``` ### B0 frame (20 bytes, sent every 100ms) | Byte(s) | Field | |---------|-------| | 0 | `0xB0` head | | 1 | seq (high 4) + strength mode (low 4): 00=none, 01=add, 10=sub, 11=abs | | 2 | Channel A strength (0–200) | | 3 | Channel B strength (0–200) | | 4–7 | Ch A frequency ×4 (10–240) | | 8–11 | Ch A intensity ×4 (0–100) | | 12–15 | Ch B frequency ×4 | | 16–19 | Ch B intensity ×4 | Invalid value in any channel's 4-tuple → device drops all 4 for that channel. To disable a channel, send intensity `101` in one slot. ### BF frame (7 bytes, soft caps + balance) | Byte(s) | Field | |---------|-------| | 0 | `0xBF` head | | 1–2 | Ch A/B strength soft cap (0–200, persisted) | | 3–4 | Ch A/B frequency balance (0–255, 128=neutral; higher = stronger low-freq impact) | | 5–6 | Ch A/B intensity balance (0–255, 128=neutral; higher = stronger low-freq stimulation) | ⚠️ BF takes effect immediately with no response. Must re-send after every reconnect. ### B1 notification (from 0x150B) | Byte(s) | Field | |---------|-------| | 0 | `0xB1` head | | 1 | sequence number (matches the B0 that caused the change, 0 if wheel) | | 2 | Ch A actual strength | | 3 | Ch B actual strength | ## Connectivity topologies The last mile is **always BLE**. Everything upstream is an adapter. 1. **Direct BLE** — any BLE host writes B0/BF directly. No app, no internet. - Web Bluetooth (Chrome/Edge) → xToys.app, OpenDGLab-Connect - Python `bleak`, C# WinRT, Node `noble` 2. **Phone-app bridge (DG-Lab Socket mode)** — phone app pairs to box via BLE AND runs a WebSocket endpoint. Third-party terminal connects to that WS (LAN or internet) and sends commands the app forwards over BLE. - `PyDGLab-WS` (Python, 81★) implements both client and server sides - QR code in app encodes WS URL + clientId for binding 3. **OpenDGLab OpenProtocol** — protobuf protocol for OPClient ↔ device-host. Richer; meant for game/VR integrations (HL2, R.E.P.O. mods). ## Browser Bluetooth - **Web Bluetooth API** (`navigator.bluetooth.requestDevice`) — Chrome, Edge, Opera, Brave, Android Chrome. xToys and OpenDGLab-Connect use this. - **Firefox**: not supported, Mozilla refuses over fingerprinting concerns. No flag. Use app-bridge + WS instead. - **Safari/iOS**: not supported. ## Key repos | Repo | What | |------|------| | `dungeonlab-open/dglab-bluetooth-protocol` (638★) | **Official** BLE protocol docs + example waveform data (V2/V3) | | `OpenDGLab/OpenDGLab-WaveGen` (8★) | Web GUI waveform editor, exports pattern strings. Live: opendglab.github.io/OpenDGLab-WaveGen | | `OpenDGLab/OpenDGLab-OpenProtocol` (16★) | OpenProtocol spec (protobuf) | | `OpenDGLab/OpenDGLab-Core` (45★, Kotlin) | Reference implementation of BLE protocol | | `OpenDGLab/OpenDGLab-Connect` (19★, JS) | Web client using Web Bluetooth | | `OpenDGLab/OpenDGLab-Desktop` (58★, C++) | Desktop client | | `Ljzd-PRO/PyDGLab-WS` (81★, Python) | App-socket bridge library, async, well-maintained. Docs: pydglab-ws.readthedocs.io | | `Kruziikloksu/simple-custom-dg-lab-server` (Python) | Custom relay server, supports app-exported waveforms | | `huzpsb/DGLAB4J` (Java) | Coyote v3 socket protocol, Java impl | | `EcstasyEngineer/coyote-mcp` (JS) | MCP server for Coyote via app socket (LAN) | | `AngelcoMilk/DGLabPunish` (C#) | R.E.P.O. game mod with continuous waveforms | ## Where to find feel-good patterns 1. **Official example data**: `dungeonlab-open/dglab-bluetooth-protocol` — added V2/V3 波形示例数据 on 2024/10/28, under `coyote/v2` and `coyote/v3` directories 2. **Built-in named patterns**: app presets (`Flick`, `Click`, …). Enumerate via `GETWAVELIST` (OpenProtocol) or select by name (PyDGLab-WS) 3. **OpenDGLab-WaveGen**: visual editor that exports pattern strings importable in code 4. **Community**: DG-Lab Discord/subreddit, nonebot plugin repos, game mod repos share pattern strings ## Pattern generation shapes (sensation over shock) - **Slow breathing**: intensity = `50 + 40*sin(2π t / 8s)`, freq fixed ~150ms (~7Hz deep). 8s period. - **Teasing ramp**: intensity 0→80 over 5s, hold 1s, drop to 5, repeat. Freq alternating 100ms/250ms. - **Flutter**: freq 20ms (50Hz), intensity 10–30 quick pulses. Buzzy/tingly, low strength. ## Substation app (built this session) Location: this directory - .NET 8 console app, targets `net8.0-windows10.0.19041.0` - Direct BLE via WinRT (`BluetoothLEDevice`, `GattCharacteristic`) - WebSocket server on `127.0.0.1:8765` via `HttpListener` - 100ms tick loop builds B0 from shared state and writes to BLE - JSON commands: `connect`, `status`, `strength`, `wave`, `stream`, `stop`, `config`, `disconnect`, `ping` - Designed to be driven by a browser userscript talking to `ws://127.0.0.1:8765` ``` web game / userscript ──WS──> Substation ──BLE──> Coyote 3.0 127.0.0.1:8765 B0/BF frames ``` Build: `dotnet run -c Release`