TCP Protocol
The SensorChaos agent exposes a JSON-Lines TCP server on port 19847. Every command is a single JSON object terminated by a newline (\n). The agent responds with exactly one JSON object per command, also newline-terminated.
This means you can drive sensor injection from any language that can open a TCP connection — Python, Go, Ruby, shell scripts, or a custom test harness.
Client Agent (port 19847) │ │ │── {"cmd":"ping"}\n ──────────────► │ │◄─── {"ok":true,"agent":"SensorChaos","version":"0.1.0"}\n ─│ │ │ │── {"cmd":"location","lat":25.197,"lng":55.274,...}\n ──────►│ │◄─── {"ok":true}\n ──────────────────────────────────────── │Connection
Section titled “Connection”| Platform | How to reach port 19847 |
|---|---|
| Android emulator | adb forward tcp:19847 tcp:19847 (CLI does this automatically) |
| Android real device | Same ADB forward over USB |
| iOS Simulator | 127.0.0.1:19847 directly (shares Mac loopback) |
| iOS real device | idb forward 19847 19847 |
Commands
Section titled “Commands”Handshake and version check. Use this to confirm the agent is alive and read its version before sending injection commands.
{ "cmd": "ping"}Response:
{ "ok": true, "agent": "SensorChaos", "version": "0.1.0"}capabilities
Section titled “capabilities”Query which injectors are available on the connected device. wifi and cell are reserved for later development and always return false.
{ "cmd": "capabilities"}barometer is present on iOS only. Android agents omit it.
Response:
{ "ok": true, "capabilities": { "location": true, "sensor": true, "barometer": true, "wifi": false, "cell": false }}status
Section titled “status”Read the agent’s current injection state — last injected values per injector type. Useful for debugging and live diff UIs.
{ "cmd": "status"}location is null when signal loss is active or no fix has been sent yet.
satellites is present on Android only.
Response:
{ "ok": true, "status": { "injecting": true, "location": { "lat": 25.197, "lng": 55.274, "accuracy_m": 5, "satellites": 12 }, "sensors": { "accelerometer": [ 0.1, 9.7, 0.3 ], "gyroscope": [ 0, 0, 0 ] } }}location
Section titled “location”Inject a GPS fix into the app. Overrides the location returned to all LocationManager / CLLocationManager listeners.
{ "cmd": "location", "lat": 25.197, "lng": 55.274, "accuracy_m": 5, "satellites": 12, "altitude_m": 10, "speed_mps": 8.3, "bearing_deg": 92}| Field | Type | Required | Description |
|---|---|---|---|
lat | number | null | Yes | Latitude in decimal degrees. null triggers signal loss. |
lng | number | null | Yes | Longitude in decimal degrees. null triggers signal loss. |
accuracy_m | number | null | Yes | Reported accuracy radius in metres. |
satellites | integer | No | Satellite count (Android only). |
altitude_m | number | No | Altitude in metres above sea level. |
speed_mps | number | No | Speed in metres per second. |
bearing_deg | number | No | Bearing in degrees (0–360). |
Signal loss: send "lat": null, "lng": null, "accuracy_m": null to simulate GPS unavailability. The agent stops delivering location updates until the next fix.
Response:
{ "ok": true}sensor
Section titled “sensor”Inject IMU or barometer values. The agent replaces the real sensor readings delivered to the app’s SensorEventListener / CMMotionManager handlers.
{ "cmd": "sensor", "type": "accelerometer", "values": [ 0.1, 9.7, 0.3 ], "accuracy": 3}| Field | Type | Required | Description |
|---|---|---|---|
type | "accelerometer" | "gyroscope" | "magnetometer" | "barometer" | Yes | Sensor type — see table below. |
values | number[] | Yes | Sensor reading (see units per type below). |
accuracy | integer | No | Android SensorManager.SENSOR_STATUS_* constant (0–3). Default: 3 (high). |
Sensor types and units:
type | values | Units |
|---|---|---|
accelerometer | [x, y, z] | m/s² |
gyroscope | [x, y, z] | rad/s |
magnetometer | [x, y, z] | µT |
barometer | [pressure_hpa] | hPa |
Response:
{ "ok": true}wifi (reserved)
Section titled “wifi (reserved)”Inject Wi-Fi scan results (reserved — not yet implemented).
{ "cmd": "wifi", "results": [ { "ssid": "CafeNet", "bssid": "aa:bb:cc:dd:ee:ff", "rssi": -65, "frequency": 2412 } ]}| Field | Type | Required | Description |
|---|---|---|---|
results | WifiResult[] | null | Yes | Scan results to inject, or null to clear the override and resume real scans. |
Response:
{ "ok": true, "ignored": true}cell (reserved)
Section titled “cell (reserved)”Inject cell tower data (reserved — not yet implemented).
{ "cmd": "cell", "towers": [ { "mcc": 234, "mnc": 20, "lac": 1234, "cid": 56789, "rssi": -80 } ]}| Field | Type | Required | Description |
|---|---|---|---|
towers | CellTower[] | null | Yes | Tower list to inject, or null to clear the override. |
Response:
{ "ok": true, "ignored": true}Response format
Section titled “Response format”Every command gets exactly one response line:
{"ok": true}{"ok": false, "error": "unknown command: foo"}{"ok": true, "ignored": true}| Field | Always present | Description |
|---|---|---|
ok | Yes | true on success, false on error |
error | When ok: false | Human-readable error message |
ignored | When command was skipped | true when the command is recognised but not yet implemented (forward-compat) |
Examples
Section titled “Examples”import socket, json
def send(sock, cmd): sock.sendall((json.dumps(cmd) + "\n").encode()) return json.loads(sock.makefile().readline())
with socket.create_connection(("127.0.0.1", 19847)) as s: print(send(s, {"cmd": "ping"}))
# Inject a spoofed GPS fix send(s, { "cmd": "location", "lat": 25.197, "lng": 55.274, "accuracy_m": 5.0, "satellites": 12 })
# Simulate signal loss send(s, {"cmd": "location", "lat": None, "lng": None, "accuracy_m": None})package main
import ( "bufio" "encoding/json" "fmt" "net")
func main() { conn, _ := net.Dial("tcp", "127.0.0.1:19847") defer conn.Close() r := bufio.NewReader(conn)
send := func(cmd any) map[string]any { b, _ := json.Marshal(cmd) fmt.Fprintf(conn, "%s\n", b) line, _ := r.ReadString('\n') var resp map[string]any json.Unmarshal([]byte(line), &resp) return resp }
fmt.Println(send(map[string]any{"cmd": "ping"})) send(map[string]any{ "cmd": "location", "lat": 25.197, "lng": 55.274, "accuracy_m": 5.0, })}import { SensorChaosClient } from "@sensorchaos/client";
const client = new SensorChaosClient();await client.connect();
await client.ping();await client.location({ lat: 25.197, lng: 55.274, accuracy_m: 5.0, satellites: 12,});
// Signal lossawait client.location({ lat: null, lng: null, accuracy_m: null });
client.disconnect();# One-shot: inject a GPS fixecho '{"cmd":"location","lat":25.197,"lng":55.274,"accuracy_m":5.0}' \ | nc -q1 127.0.0.1 19847
# Read current statusecho '{"cmd":"status"}' | nc -q1 127.0.0.1 19847Version negotiation
Section titled “Version negotiation”Always send ping first and check version in the response. The CLI requires agent version ≥ 0.1.0. If you’re building your own client, reject agents below your minimum supported version to avoid silent protocol mismatches.
{"ok": true, "agent": "SensorChaos", "version": "0.1.0"}Version follows semver. Breaking protocol changes increment the minor version.