Local HSI Mock Data Generator & Broadcaster for SDK Development
Synheart CLI generates HSI-compatible sensor data streams that mimic phone + wearable sources, eliminating dependency on physical devices during development.
Changelog: see CHANGELOG.md.
- 🎯 Mock HSI Data Streams: Generate realistic sensor data without physical devices
- 🔄 Multiple Scenarios: Baseline, focus session, stress spike, and more
- 🌐 WebSocket Broadcasting: Real-time data streaming on localhost
- 📼 Record & Replay: Capture sessions for reproducible testing
- 🎲 Deterministic Mode: Use seeds for consistent, repeatable data generation
- 🛠️ Developer Friendly: Simple CLI, clear documentation, easy SDK integration
- Go 1.24 or later
Install the CLI to make it available system-wide:
git clone https://github.com/synheart-ai/synheart-cli
cd synheart-cli
make installThis installs the binary to $GOPATH/bin (typically $HOME/go/bin).
Ensure Go's bin directory is in your PATH:
Add this line to your ~/.zshrc, ~/.bashrc, or ~/.bash_profile:
export PATH="$PATH:$HOME/go/bin"Then reload your shell configuration:
source ~/.zshrc # or ~/.bashrcVerify installation:
synheart versionGenerate and load completions for your shell:
# Zsh
mkdir -p ~/.zsh/completions
synheart completion zsh > ~/.zsh/completions/_synheart# Bash
mkdir -p ~/.bash_completion.d
synheart completion bash > ~/.bash_completion.d/synheartOr let make install handle it:
make install INSTALL_COMPLETIONS=zshTo build without installing globally:
make buildTo build with release metadata (injects version + git commit into synheart version):
make build VERSION=0.0.1The binary will be available at bin/synheart. Run it with:
./bin/synheart versionThese flags apply to most commands:
--format text|json(default:text) — JSON is supported byversion,doctor,mock list-scenarios, andmock describe--no-color— Disable colored output-q, --quiet— Suppress non-essential output-v, --verbose— Verbose logging--pprof— Enable pprof HTTP server (live endpoints at/debug/pprof)--pprof-addr <host:port>— Address for pprof HTTP server (default:127.0.0.1:6060)--cpu-profile <path>— Write CPU profile to file (stops on exit)--mem-profile <path>— Write heap profile to file on exit--trace-profile <path>— Write execution trace to file--block-profile-rate <n>— Enable block profiling with given sampling rate (0 to disable)--mutex-profile-fraction <n>— Enable mutex profiling with given fraction (0 to disable)
Start the mock server with default settings:
synheart mock startThis will:
- Generate baseline scenario data
- Start WebSocket server on
ws://127.0.0.1:8787/hsi - Broadcast events to all connected clients
Connect to the stream from your SDK:
const ws = new WebSocket('ws://localhost:8787/hsi');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(data);
};Start generating and broadcasting HSI events.
# Basic usage
synheart mock start
# With specific scenario
synheart mock start --scenario stress_spike
# Deterministic output with seed
synheart mock start --scenario focus_session --seed 42
# Custom port and duration
synheart mock start --port 9000 --duration 5m
# Record to file while broadcasting
synheart mock start --scenario workout --out workout.ndjsonFlags:
--host- Host to bind to (default:127.0.0.1)--port- Port to listen on (default:8787)--scenario- Scenario to run (default:baseline)--duration- Duration to run (e.g.,5m,1h)--rate- Global tick rate (default:50hz)--seed- Random seed for deterministic output--out- Record events to file (NDJSON format)
Record mock data to an NDJSON file.
synheart mock record --scenario workout --duration 15m --out workout.ndjsonFlags:
--scenario- Scenario to run (default:baseline)--duration- Duration to record (default:5m)--out- Output file (required)--seed- Random seed for deterministic output--rate- Global tick rate (default:50hz)
Replay events from a recorded file.
# Basic replay
synheart mock replay --in workout.ndjson
# Faster playback
synheart mock replay --in workout.ndjson --speed 2.0
# Loop continuously
synheart mock replay --in workout.ndjson --loopFlags:
--in- Input file to replay (required)--speed- Playback speed multiplier (default:1.0)--loop- Loop playback continuously--host- Host to bind to (default:127.0.0.1)--port- Port to listen on (default:8787)
List all available scenarios.
synheart mock list-scenariosShow detailed information about a scenario.
synheart mock describe stress_spikeCheck environment and print connection examples.
synheart doctorValidates:
- Scenarios directory exists
- Default port availability
- Provides SDK connection examples
Print version information.
synheart versionNormal day idle with minor variance. Suitable for testing basic SDK functionality.
Reduced motion, stable heart rate, screen on patterns. Simulates a 30-minute focused work session.
Sudden heart rate increase, HRV drop, EDA spike followed by recovery. 8-minute scenario for testing stress detection.
All events follow the HSI-compatible envelope format:
{
"schema_version": "hsi.input.v1",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"ts": "2025-12-26T20:05:12.123Z",
"source": {
"type": "wearable",
"id": "mock-watch-01",
"side": "left"
},
"session": {
"run_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"scenario": "focus_session",
"seed": 42
},
"signal": {
"name": "ppg.hr_bpm",
"unit": "bpm",
"value": 72.4,
"quality": 0.93
},
"meta": {
"sequence": 12093
}
}ppg.hr_bpm- Heart rate (beats per minute)ppg.hrv_rmssd_ms- Heart rate variability (milliseconds)accel.xyz_mps2- 3D acceleration (m/s²)gyro.xyz_rps- 3D gyroscope (rad/s)temp.skin_c- Skin temperature (Celsius)eda.us- Electrodermal activity (microsiemens)
screen.state- Screen on/off stateapp.activity- App foreground/background activitymotion.activity- Motion activity (still/walk/run)
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8787/hsi');
ws.on('message', (data) => {
const event = JSON.parse(data);
console.log(`${event.signal.name}: ${event.signal.value}`);
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});import websocket
import json
def on_message(ws, message):
event = json.loads(message)
print(f"{event['signal']['name']}: {event['signal']['value']}")
def on_error(ws, error):
print(f"Error: {error}")
ws = websocket.WebSocketApp(
"ws://localhost:8787/hsi",
on_message=on_message,
on_error=on_error
)
ws.run_forever()package main
import (
"encoding/json"
"log"
"github.com/gorilla/websocket"
)
type Event struct {
Signal struct {
Name string `json:"name"`
Value interface{} `json:"value"`
} `json:"signal"`
}
func main() {
conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:8787/hsi", nil)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Fatal(err)
}
var event Event
json.Unmarshal(message, &event)
log.Printf("%s: %v", event.Signal.Name, event.Signal.Value)
}
}use tokio_tungstenite::{connect_async, tungstenite::Message};
use futures_util::StreamExt;
use serde_json::Value;
#[tokio::main]
async fn main() {
let (mut socket, _) = connect_async("ws://localhost:8787/hsi")
.await
.expect("Failed to connect");
while let Some(msg) = socket.next().await {
if let Ok(Message::Text(text)) = msg {
let event: Value = serde_json::from_str(&text).unwrap();
println!("{}: {}",
event["signal"]["name"].as_str().unwrap(),
event["signal"]["value"]
);
}
}
}make buildmake testmake clean- SDK Development: Test HSI SDKs without physical wearables
- CI/CD: Automated testing with deterministic data
- Demos: Scripted scenarios that look realistic
- QA: Reproducible test cases with seeded data
- Integration Testing: Validate data pipelines locally
Recordings use NDJSON (Newline Delimited JSON) format - one JSON object per line. This format is:
- Streaming-friendly
- Easy to parse
- Human-readable
- Compatible with standard tools
Example:
{"schema_version":"hsi.input.v1","event_id":"...","ts":"2025-12-26T20:05:12.123Z",...}
{"schema_version":"hsi.input.v1","event_id":"...","ts":"2025-12-26T20:05:12.143Z",...}
{"schema_version":"hsi.input.v1","event_id":"...","ts":"2025-12-26T20:05:12.163Z",...}
- Binds to
localhost(127.0.0.1) by default - Only generates synthetic data, never real user data
- Use
--host 0.0.0.0with caution (exposes on LAN)
If port 8787 is already in use:
synheart mock start --port 9000Ensure the scenarios/ directory is in:
- Current working directory
- Same directory as the executable
Use synheart doctor to check.
Ensure the server is running and check firewall settings.
Contributions welcome! Please open an issue or PR.
See RFC document for planned Phase 2 and Phase 3 features:
- Additional scenarios (workout, sleep, commute)
- HTTP SSE and UDP transports
- Control plane for runtime scenario switching
- Custom scenario loading
- Protobuf support
Apache License 2.0. See LICENSE.
This project is provided under an open-source license. Certain underlying systems, methods, and architectures described or implemented herein may be covered by one or more pending patent applications.
Nothing in this repository grants any license, express or implied, to any patents or patent applications, except as provided by the applicable open-source license.