Developer Docs
Connect your ESP32, Arduino, or Raspberry Pi to live Android sensor streams via the SensorCast WebSocket API.
Quick Start — ESP32 Arduino
The sketch below connects to a SensorCast stream as a subscriber. It uses the Links2004/arduinoWebSockets library. Install it from the Arduino Library Manager before uploading.
- Server:
wss://api.sensorcast.app - Socket.IO path:
/socket.io/(default) - Namespace:
/stream/:username - After connect: emit
rolewith"subscriber"within 5 seconds - Server replies with a
connectedevent - Send a
heartbeatevent every <30 s or the server drops you as a zombie - When the publisher leaves the server emits
publisher_disconnected
#include <Arduino.h>
#include <WiFi.h>
#include <SocketIOclient.h> // Links2004/arduinoWebSockets
const char* SSID = "your-wifi";
const char* PASS = "your-password";
const char* SC_HOST = "api.sensorcast.app";
const uint16_t SC_PORT = 443;
const char* USERNAME = "alice"; // SensorCast username of the publisher
SocketIOclient socketIO;
unsigned long lastHeartbeat = 0;
void onEvent(socketIOmessageType_t type, uint8_t* payload, size_t length) {
if (type == sIOtype_EVENT) {
String msg = String((char*)payload, length);
// Parse event name from Socket.IO payload ["name","data"]
if (msg.startsWith("["connected"")) {
Serial.println("[SC] Connected as subscriber");
} else if (msg.startsWith("["frame"")) {
// Extract the frame string between the outer quotes
int start = msg.indexOf(","") + 2;
int end = msg.lastIndexOf(""]");
if (start > 1 && end > start) {
String frame = msg.substring(start, end);
Serial.print("[FRAME] ");
Serial.println(frame);
}
} else if (msg.startsWith("["publisher_disconnected"")) {
Serial.println("[SC] Publisher left — waiting for reconnect");
}
}
}
void setup() {
Serial.begin(115200);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("WiFi connected");
// Connect to namespace /stream/<username>
String ns = "/stream/";
ns += USERNAME;
socketIO.beginSSL(SC_HOST, SC_PORT, "/socket.io/?EIO=4", ns.c_str());
socketIO.onEvent(onEvent);
// Announce subscriber role immediately after connection
socketIO.sendEVENT("["role","subscriber"]");
}
void loop() {
socketIO.loop();
// Send heartbeat every 20 seconds to stay alive
if (millis() - lastHeartbeat > 20000) {
socketIO.sendEVENT("["heartbeat",{}]");
lastHeartbeat = millis();
}
}
Python / Raspberry Pi
Install the async-free client: pip install "python-socketio[client]"
"""
SensorCast subscriber — Python / Raspberry Pi
pip install "python-socketio[client]" requests
"""
import time
import socketio
SENSORCAST_HOST = "https://api.sensorcast.app"
USERNAME = "alice" # SensorCast username of the publisher
sio = socketio.Client()
@sio.event
def connect():
print("[SC] Socket connected, announcing subscriber role…")
sio.emit("role", "subscriber")
@sio.on("connected")
def on_connected(data):
print(f"[SC] Server accepted role: {data}")
@sio.on("frame")
def on_frame(data):
print(f"[FRAME] {data}")
@sio.on("publisher_disconnected")
def on_pub_gone():
print("[SC] Publisher disconnected — waiting…")
@sio.event
def disconnect():
print("[SC] Disconnected from server")
def heartbeat_loop():
"""Keep-alive: send heartbeat every 20 s or get kicked after 30 s."""
while True:
time.sleep(20)
if sio.connected:
sio.emit("heartbeat")
import threading
threading.Thread(target=heartbeat_loop, daemon=True).start()
namespace = f"/stream/{USERNAME}"
sio.connect(SENSORCAST_HOST, namespaces=[namespace], socketio_path="/socket.io/")
sio.wait()
Private Streams
When a publisher marks their stream as private, subscribers must pass the stream key in the Socket.IO auth object at connection time. You can find the stream key in the SensorCast dashboard under Stream Settings.
# For private streams, pass streamKey in the auth dict:
sio.connect(
SENSORCAST_HOST,
namespaces=[namespace],
socketio_path="/socket.io/",
auth={"streamKey": "YOUR_STREAM_KEY"},
)
Frame Formats
The Android app lets the publisher choose one of four formats. Your subscriber code should handle whichever format the publisher selects — or agree on one in advance.
Accelerometer;x,y,z:0.1230,-0.4560,9.81001707494400123;Accelerometer;x,y,z:0.1230,-0.4560,9.81001707494400123,Accelerometer,x,y,z,0.1230,-0.4560,9.8100{"sensor":"Accelerometer","type":1,"timestamp":1707494400123,"accuracy":3,"values":{"x":0.12,"y":-0.45,"z":9.81}}Available Sensors
These sensors are available on most Android devices. Exact field names depend on the mapping configured in the Android app; defaults are shown below.
| Sensor | Fields | Unit |
|---|---|---|
| Accelerometer | x, y, z | m/s² |
| Gyroscope | x, y, z | rad/s |
| Magnetometer | x, y, z | µT |
| Linear Acceleration | x, y, z | m/s² |
| Gravity | x, y, z | m/s² |
| GPS | latitude, longitude, altitude, speed, accuracy | °, m, m/s |
| Light | lux | lx |
| Pressure | pressure | hPa |
| Temperature | temperature | °C |
| Humidity | humidity | % |
| Proximity | distance | cm |
| Step Counter | steps | count |
| Heart Rate | bpm | bpm |
Orientation Angles
Pitch, Roll, Yaw via Rotation Vector
Euler angles (pitch / roll / yaw) derived from TYPE_ROTATION_VECTOR will be exposed as a dedicated sensor field in an upcoming release. Until then, use the Rotation Vector sensor and compute Euler angles client-side:
# Python — convert quaternion [x, y, z, w] to Euler angles
import math
def quat_to_euler(x, y, z, w):
pitch = math.atan2(2*(w*x + y*z), 1 - 2*(x*x + y*y))
roll = math.asin(2*(w*y - z*x))
yaw = math.atan2(2*(w*z + x*y), 1 - 2*(y*y + z*z))
return math.degrees(pitch), math.degrees(roll), math.degrees(yaw)Connection Troubleshooting
Disconnected immediately after connecting
Why: You did not emit the role event within 5 seconds. The server enforces a strict 5 s timeout.
Fix: Emit ["role","subscriber"] (or sendEVENT as shown in the sketch) as the very first action inside your connect handler — before any delays.
Server emits error: "Stream not found"
Why: The username in the namespace does not match any registered SensorCast account.
Fix: Double-check the username spelling. The namespace is case-sensitive: /stream/Alice is different from /stream/alice.
Disconnected after 30 seconds of no data
Why: The zombie-connection detector removes subscribers that have not sent a heartbeat in 30 s.
Fix: Send a heartbeat event at least once every 20–25 seconds (the example code uses 20 s).
Server emits "publisher_disconnected" then closes socket
Why: The Android publisher left. The server closes all subscribers immediately.
Fix: Implement reconnect logic — listen for publisher_disconnected, wait a few seconds, then reconnect to the namespace.
error: "Invalid stream key for private stream"
Why: The stream is private and the key you passed is wrong or missing.
Fix: Copy the stream key from the SensorCast dashboard and pass it in the auth object on connect.
Frames arrive but the format is unexpected
Why: The publisher changed the frame format in the Android app while you were connected.
Fix: Detect the format at runtime: JSON starts with {, CSV is all commas, COMPACT uses ;.