Hardware Integration
Plexus can receive telemetry from any device that can make an HTTP request.
| Path | Best for |
|---|---|
| Python SDK | Raspberry Pi, Jetson, Linux-based boards |
| HTTP (Microcontrollers) | ESP32, Arduino, bare-metal, any platform |
Both paths use the same source_id to identify a device. The SDK adds retries
with backoff, an on-disk buffer for points it couldn't send, and a live
WebSocket connection on Pro. It sends one message per send() call —
for fast sensors, use px.batch().
More on sending from your own hardware:
plexus.company/integrations/devices.
Services that already speak Prometheus can send with remote_write
(details).
Plans and limits
| Free | Pro | |
|---|---|---|
| Price | $0 | $199 a month |
| Devices | Up to 3 (connections count toward the 3) | No device limit |
| Readings kept | 7 days | You choose per device, up to 3 years |
| Events kept | 7 days | 3 years |
| Card required | No | Yes |
| Ingest | HTTP POST /ingest only | HTTP and live WebSocket |
| Storage for readings | — | 50 GB included, then $0.10 per GB-month |
| Video streaming and recording | No | Yes. Recorded video is $0.50 per GB-month |
On Free, a 4th device is refused: creating it in the app returns 402, and the
gateway drops points sent for it (HTTP /ingest counts them in dropped). The
devices you already have keep working.
On Free, the device WebSocket refuses the connection with
streaming_requires_plan: "Live connections need Pro. On Free, send telemetry
with POST /ingest, or upgrade in Settings → Subscription." The Python SDK then
falls back to HTTP by itself, so px.send() keeps working. Video needs the
WebSocket, so it needs Pro.
Alerts and monitors, on-call calls, runs, sharing, embedding, the Terminal,
recordings, file import and inviting teammates also come with Pro. On Free,
the app's API answers these with 402 and the error UPGRADE_REQUIRED.
Commands travel over the live WebSocket, so they
need Pro too. Upgrade in Settings → Subscription; prices are on the
pricing page.
Specs, Tips and Limits
Metric + Event Limits
| Maximum | |
|---|---|
| Messages per source | 2,000/sec, with bursts of up to 500 |
| Messages per device WebSocket | 2,000/sec |
| Points per batch | 10,000 |
| Tags per point | 16 |
| Metric name / tag key / tag value length | 256 characters |
| Event value (plain string) | 256 characters |
| Event value (JSON object or array) | 4,096 bytes |
| HTTP body | 5 MB |
| WebSocket message | 1 MB |
Rate limits count messages (batches), not points. One send_batch() with 500
points costs the same as one send() with one point, so batching buys
headroom. The per-source limit applies to HTTP and WebSocket alike. When it
drops an HTTP batch, the request still returns 200 — check the response:
count is what was accepted and dropped is what was thrown away.
Metrics and events share the same rate limit — px.event() calls draw from
the same budget as px.send().
Video
Recommended: 1280×720, quality 85, ≤20 FPS. Good image quality with comfortable headroom under the 1 MB frame limit.
Maximum: 1920×1080 at quality 85 (~500–800 KB per frame).
Each frame has a hard limit of 1 MB — frames above ~750 KB are adaptively
re-encoded by the SDK before send. The gateway accepts up to 500 frames a
second from one device. Below that, the frame rate is whatever the viewer
requests via max_fps (0 = unlimited). The dashboard UI requests at most
60 FPS and defaults to 10.
Video needs Pro. It is relayed live: the gateway drops unwatched frames and stores nothing. Video is saved only when someone presses Record on a camera in the app. One recording lasts at most 4 hours. There is no monthly hour cap; recorded video is stored at $0.50 per GB-month. Recordings are listed on the device's Recordings tab.
Time handling
The SDK timestamps each point when you call send(), using the device's
clock. On Pro it also reads the server's time once, when the live
connection opens, and shifts the timestamps it generates by the difference.
On Free it uses the device's clock as it is.
If your device has a reliable real-time clock and you need data timestamped at the moment of measurement rather than send, pass an explicit value in Unix seconds:
import time
px.send(metric="battery.voltage", value=12.4, timestamp=time.time())The most important case is source-side buffering. If your device queues
readings locally — during a connectivity gap, for example — and flushes them
later, omitting timestamp would mark every point with the flush time, losing
the original measurement timeline. Capture the timestamp at the moment of
measurement and include it on send:
# At measurement time
reading_time = time.time()
# Later, when flushing the buffer
px.send_batch(
points=[
("battery.voltage", 12.4),
("sensor.temperature_c", 23.5),
("motor.rpm", 1200),
],
timestamp=reading_time, # Unix seconds — when these were measured
)On the HTTP path, values below 1e12 are treated as seconds and converted to
milliseconds automatically. Values at or above 1e12 are passed through as-is.
Metric naming
Use <subsystem>.<metric> for all metric names:
battery.voltage motor.rpm sensor.temperature_c
imu.accel_x gps.latitude system.uptime_sConsistent naming makes it easy to query a subsystem (battery.*), build
dashboards, and set alerts without renaming metrics later.
source_id format
source_id must be lowercase and match [a-z0-9][a-z0-9._-]* — letters,
digits, dots, hyphens, and underscores, starting with a letter or digit.