efferent-ble-simulator
Deploy and operate the BLE Simulator MCP Service — covers both running the service on a local Linux/Bluetooth machine and controlling a already-running HTTP instance from any device on the LAN.
Source facts
- Repository
- dncore/efferent-ble-simulator
- Last source activity
- August 25, 2026 at 01:39
- Detected SKILL.md language
- English
- Stars
- 0
- Forks
- 0
Install options
The review-first prompt is selected by default. You can switch to a direct command or download a local copy.
Review the source files
Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.
Showing SKILL.md
SKILL.md
Source instructions · Read-only preview- name
- efferent-ble-simulator
- description
- Deploy and operate the BLE Simulator MCP Service — covers both running the service on a local Linux/Bluetooth machine and controlling a already-running HTTP instance from any device on the LAN.
- license
- MIT
- compatibility
- opencode
- version
- 1.0.0
- updated
- 2026-08-21T00:00:00.000Z
# BLE Simulator MCP Skill
This skill covers two distinct usage modes. Identify which applies before proceeding.
---
## Mode A — Host: Deploy & Run the Service on the Local Machine
Use this mode when you are working **directly on the Linux machine that has a Bluetooth adapter** and need to install, build, and start the simulator service.
### Prerequisites
- Linux with BlueZ (bluetoothd), a BLE-capable adapter
- **BlueZ ≥ 5.87** — BlueZ 5.86 has an advertisement-registration bug (`src/advertising.c` uses the wrong struct size for `MGMT_OP_ADD_EXT_ADV_DATA`, sending 14 bytes instead of 6); on kernels with strict MGMT length validation this makes every `RegisterAdvertisement` fail with `Failed to register advertisement`. Upgrade before deploying: `sudo pacman -S bluez && sudo systemctl restart bluetooth`
- Node.js ≥ 18
### First-time setup
Run once with sudo before starting the service:
```bash
npm install
npm run setup # requires sudo — installs D-Bus policy, enables bluetoothd, verifies GATT
npm run build
```
`npm run setup` (`scripts/setup.sh`) does the following automatically:
1. Installs BlueZ if missing (apt / pacman / dnf / zypper)
2. Enables and starts `bluetoothd`
3. Powers on the BLE adapter
4. Writes `/etc/dbus-1/system.d/ble-simulator.conf` — the critical step: without this policy file, `RegisterApplication()` silently fails and the device cannot be connected to or read from
5. Reloads D-Bus daemon so the policy takes effect immediately
6. Runs an end-to-end GATT registration smoke test and reports pass/fail
### Starting the service
**stdio mode** (default, for local MCP clients that launch the process directly):
```bash
npm start
# or: node dist/index.js
```
MCP client config:
```json
{
"mcpServers": {
"ble-simulator": {
"command": "node",
"args": ["/path/to/ble-simulator-node/dist/index.js"]
}
}
}
```
**HTTP mode** (exposes the service over the network so remote agents on the LAN can connect):
```bash
npm run start:http
# equivalent: MCP_TRANSPORT=http npm start
```
Custom port / bind address:
```bash
MCP_TRANSPORT=http MCP_PORT=8080 MCP_HOST=127.0.0.1 npm start
```
### Environment variables
| Variable | Default | Description |
|---|---|---|
| `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio` or `http` |
| `MCP_PORT` | `3300` | HTTP listen port |
| `SIM_SERIAL_STABLE` | unset | `1` = same-type sessions keep a stable serial (`SIM001-FTMS`), so a phone can reconnect after stop/start without forgetting the device; device-type changes still change the serial to force re-discovery. Default (unset) changes the serial every session (`SIM001-S42`) |
| `MCP_HOST` | `0.0.0.0` | HTTP bind address (`0.0.0.0` = all interfaces) |
### Symptom: device visible but not connectable
If a BLE scanner can see the simulated device name but cannot connect, or connects but sees no services, the D-Bus policy is missing. Fix:
```bash
npm run setup
```
---
### Docker Compose deployment (recommended for servers)
One command deploys the HTTP MCP service + status Dashboard + Skill file download,
with `restart: unless-stopped` for long-running operation.
```bash
git clone <repo-url> && cd ble-simulator-node
bash scripts/docker-up.sh # host prep (sudo, once) + build + start
```
What it provides:
| URL | Purpose |
|---|---|
| `http://<host>:3300/mcp` | MCP endpoint (same as `npm run start:http`) |
| `http://<host>:3330/` | Dashboard — 复刻全部 MCP 工具:状态/参数表单控制(4 种设备+动态模拟+场景)/一键快捷模板(骑行剧本·FTP 测试·爬坡训练·冲刺间歇·恢复骑行·热身课等)/交互规则/会话管理/通信日志(折叠展开) |
| `http://<host>:3330/skill/SKILL.md` | This skill file, downloadable by other users |
Management:
```bash
DASH_PORT=3331 bash scripts/docker-up.sh # 默认 3330,被占用时换端口
bash scripts/docker-up.sh status # compose ps
bash scripts/docker-up.sh logs # follow mcp logs
bash scripts/docker-up.sh down # stop containers (data volume kept)
```
Notes:
- **BLE stays on the host**: the container mounts the host D-Bus socket
(`/run/dbus/system_bus_socket`) and talks to the host's BlueZ. The host must
have BlueZ ≥ 5.87 and the D-Bus policy installed — `scripts/docker-up.sh`
runs `scripts/setup.sh` automatically on first deploy.
- **Zero-compile build**: better-sqlite3 uses a prebuilt binary (no compiler
needed). If the build is slow / fails on a restricted network, switch to a
mirror:
```bash
NPM_REGISTRY=https://registry.npmmirror.com \
BETTER_SQLITE3_BINARY_HOST_MIRROR=https://registry.npmmirror.com/-/binary/better-sqlite3 \
bash scripts/docker-up.sh
```
- Data (SQLite) persists in the `ble-data` Docker volume across rebuilds.
- Deploy assets: `deploy/docker-compose.yml`, `deploy/mcp/Dockerfile`,
`deploy/dashboard/`. Design: `docs/design/docker-deploy.md`.
## Mode B — Remote Agent: Control a Running HTTP Service on the LAN
Use this mode when the BLE Simulator is **already running in HTTP mode on another machine** (or the same machine) and you want to control it via MCP over HTTP. No Bluetooth hardware or Linux required on your side.
### Connect your MCP client
Point your MCP client at the running service:
```json
{
"mcpServers": {
"ble-simulator": {
"type": "http",
"url": "http://<device-ip>:3300/mcp"
}
}
}
```
Replace `<device-ip>` with the LAN IP of the machine running the simulator (e.g. `192.168.1.42`). The default port is `3300`.
### Available MCP Tools
| Tool | Purpose |
|---|---|
| `ble_start` | Start BLE simulation with specified device type and config |
| `ble_stop` | Stop the running BLE simulation |
| `ble_restart` | Restart with optional new config |
| `ble_status` | Query current state, active configuration and live device state (运行状态/阶段/功率/心率等) |
| `ble_configure` | Update config. **Running + same device type → hot-update in place** (keeps the phone connection, next notify applies); device-type change → full restart |
| `ble_get_config` | Get current/latest config as JSON (for form backfill / sharing with Dashboard) |
| `ble_save_config` | Save current params as a **named preset** (max 20, error beyond limit) |
| `ble_list_configs` | List saved named configs (id/name/type/updated) |
| `ble_get_config_detail` | Get a saved config's full JSON by id |
| `ble_rename_config` | Rename a saved config |
| `ble_delete_config` | Delete a saved config |
| `ble_set_interaction` | Add/replace a custom interaction rule |
| `ble_clear_interactions` | Remove all interaction rules |
| `ble_list_sessions` | List historical config sessions |
| `ble_get_session` | Get full details of a session by ID |
| `ble_get_logs` | Read persistent BLE communication logs |
### Config presets & hot update
- **Saved configs**: `ble_save_config {name, config?}` stores a named preset (max 20).
`ble_list_configs` / `ble_get_config_detail {id}` / `ble_rename_config {id,name}` /
`ble_delete_config {id}` manage them. The Dashboard「配置列表」tab shares this data.
- **Hot update**: while running, `ble_configure` with the **same device type** applies
params **in place** — the connected phone keeps its link and receives the new values
on the next notify (default 500ms). No restart, no re-pair. Device-type changes
still require a full restart (new serial forces re-discovery).
- **Stable serial**: with `SIM_SERIAL_STABLE=1`, same-type stop→start keeps a stable
serial (`SIM001-FTMS`), so a phone can reconnect without forgetting the device.
---
## Device Types
Always specify `deviceType` when starting. Four types are supported:
### `heart_rate` — Heart Rate Monitor (BLE 0x180D)
Simulates a wrist/chest heart rate sensor. Broadcasts HR measurements + battery level.
Key params (`heartRate` object):
- `baseHeartRate` — center bpm, 40–200 (default 75)
- `initialBattery` — starting battery %, 0–100 (default 85)
- `heartRateIntervalMs` — notification interval ms, 100–10000 (default 1000)
- `bodySensorLocation` — 0=Other, 1=Chest, 2=Wrist, 3=Finger, 4=Hand, 5=EarLobe, 6=Foot (default 2)
### `cycling_power` — Cycling Power Meter (BLE 0x1818)
Simulates a crank or pedal power meter. Broadcasts instantaneous power, optional wheel/crank revolution data.
Key params (`cyclingPower` object):
- `basePowerWatts` — center power output, 0–5000 (default 150)
- `cadenceRpm` — simulated cadence, 0–200 (default 80)
- `wheelCircumferenceMm` — wheel size in mm (default 2096 for 700c×25)
- `sensorLocation` — 0x05=Left Crank, 0x06=Right Crank, 0x07=Left Pedal, 0x08=Right Pedal, 0x0F=Spider
- `includeWheelRevData` — include wheel revolution data in packets (default true)
- `includeCrankRevData` — include crank revolution data in packets (default true)
- `notifyIntervalMs` — notification interval ms (default 1000)
### `csc` — Cycling Speed & Cadence Sensor (BLE 0x1816)
Simulates a speed sensor, cadence sensor, or combo sensor.
Key params (`csc` object):
- `speedKph` — simulated speed km/h, 0–150 (default 25)
- `cadenceRpm` — simulated cadence, 0–200 (default 80)
- `wheelCircumferenceMm` — wheel size in mm (default 2096)
- `hasWheel` — include wheel revolution data (default true)
- `hasCrank` — include crank revolution data (default true)
- `notifyIntervalMs` — notification interval ms (default 1000)
### `ftms` — Smart Trainer / Fitness Machine (BLE 0x1826)
Simulates a controllable indoor cycling trainer. Supports ERG mode (power target), resistance mode, and simulation mode (grade/wind).
Key params (`ftms` object):
- `speedKph` — simulated speed km/h (default 25)
- `cadenceRpm` — simulated cadence rpm (default 80)
- `powerWatts` — simulated power output W (default 150)
- `resistanceLevel` — resistance level 0–100 (default 5)
- `grade` — road gradient % −30 to +30 (default 0)
- `notifyIntervalMs` — Indoor Bike Data notification interval ms (default 1000)
- `minResistance` / `maxResistance` — resistance range (default 1–100)
- `minPower` / `maxPower` — power range W (default 0–4000)
---
## Dynamic Simulation (Cycling Devices)
Cycling devices (`cycling_power`, `csc`, `ftms`) support an optional **dynamic simulation mode** that produces human-like variations in power, cadence, and speed. When disabled (default), metrics remain static constants — the legacy behavior.
### Enabling dynamic simulation
Pass a `simulation` object inside the device-specific params:
```
ble_start({
deviceType: "ftms",
ftms: {
powerWatts: 200,
cadenceRpm: 85,
simulation: {
enabled: true,
scenario: { type: "freeride" },
riderWeightKg: 72,
bikeWeightKg: 8
}
}
})
```
### Simulation parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| `enabled` | boolean | `false` | Master switch for dynamic simulation |
| `riderWeightKg` | number | 75 | Rider weight in kg (for speed physics) |
| `bikeWeightKg` | number | 8 | Bike weight in kg |
| `crr` | number | 0.004 | Rolling resistance coefficient |
| `cdA` | number | 0.35 | Drag coefficient × frontal area (m²) |
| `fatigueFactor` | number | 0 | Power decay rate per minute (0 = no fatigue, 0.001 = 0.1%/min) |
| `cadenceCoupling` | string | `"proportional"` | How cadence responds to power changes: `proportional` / `inverse` / `independent` |
| `microPauseProbability` | number | 0.005 | Per-tick probability of a brief cadence drop to 0 (simulates coasting) |
| `autoStart` | boolean | `false` (`ride_script` 默认 `true`) | Start riding immediately without waiting for an external app to send FTMS Start. `ride_script` scenario auto-starts by default |
| `scenario` | object | `{ type: "freeride" }` | Riding scenario (see below) |
### Riding scenarios
**`steady`** — No base power changes. Natural micro-variations only.
```json
{ "type": "steady" }
```
**`freeride`** (default) — Base power randomly drifts ±20% every 60–180 seconds, simulating natural ride variation.
```json
{ "type": "freeride" }
```
**`intervals`** — Alternates between high and low power phases.
```json
{
"type": "intervals",
"highPowerFactor": 1.5,
"lowPowerFactor": 0.5,
"intervalSeconds": 30,
"restSeconds": 60,
"sets": 5
}
```
**`warmup_main_cooldown`** — Three-phase workout: linear warmup (50% → 100%), main set, linear cooldown.
```json
{
"type": "warmup_main_cooldown",
"warmupMinutes": 5,
"mainMinutes": 20,
"cooldownMinutes": 5,
"mainPowerFactor": 1.2
}
```
**`ride_script`** — Scripted ride: plays a sequence of riding actions (start / cruise / climb / sprint / coast / stop) back to back. Each phase drives target power, grade (for speed physics) and optional cadence; the engine auto-starts without an app. Omit `phases` to use the built-in default script (起步→巡航→爬坡→冲刺→下坡滑行→停车).
```json
{
"type": "ride_script",
"repeat": true,
"phases": [
{ "type": "start", "durationSeconds": 30, "targetPower": 60, "grade": 0, "cadence": 60 },
{ "type": "climb", "durationSeconds": 90, "targetPower": 260, "grade": 5, "cadence": 75 },
{ "type": "sprint", "durationSeconds": 20, "targetPower": 400, "grade": 0, "cadence": 100 },
{ "type": "stop", "durationSeconds": 20, "targetPower": 0, "grade": 0 }
]
}
```
View on GitHubThis SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub