Python Client
Write and read Elodin DB telemetry from any plain Python process with `elodin.db`.
The elodin Python wheel ships a first-class Elodin DB client: elodin.db.
It lets any Python process — a hardware-in-the-loop rig, a third-party
simulator bridge, a flight-test logger, a notebook — start or connect to
Elodin DB, write telemetry, and read it back. No Elodin simulation required.
This page is a guided tour; the full API surface is documented in the
Python API reference, and a runnable
end-to-end demo lives at examples/db-client in the repository
(uv run python examples/db-client/main.py).
Quick start
# Embedded server (tests, notebooks, single-box logging) — or connect to an
# external `elodin-db run` instance instead.
=
=
=
, =
= # pyarrow.Table
Everything written this way is a normal Elodin DB citizen: plottable live in
the Elodin Editor (elodin editor 127.0.0.1:2240, with .labeled(...) names
appearing as axis labels), queryable via :sql in elodin-db lua, and
replicable with follow mode.
Writing telemetry
Schema declaration
A writer is declared once with the component names, dtypes, shapes, and optional element labels:
=
All primitive types are supported: f32/f64, i8..i64, u8..u64, bool_.
Every write emits exactly one Impeller2 Table packet: a shared i64
timestamp followed by each field's values — one packet per tick, not one per
component. All declared fields are required on every write; use one writer
per rate group (e.g. one 110 Hz IMU writer, one 50 Hz control writer).
Never block a control loop
write blocks until the row is handed to the socket and raises on failure.
write_nowait never blocks and never raises for transport reasons — rows are
shed according to the queue policy when the database is unreachable or the
bounded queue is full:
=
# microseconds-cheap
# rows shed so far
# "Connected" | "Disconnected"
# most recent transport error or DB rejection
Writers reconnect automatically with the component-metadata + vtable
handshake replayed. Server-side rejections (e.g. re-registering a component
with a different shape) surface asynchronously via writer.last_error.
For nanosecond sources, declare the writer with timestamp="ns" and pass
timestamp_ns= to writes; the database stores microseconds.
For one-off scripts there is also client.send(name, values, timestamp_us) —
a convenience single-component f64 write.
Reading telemetry
# Discovery
# {name: ComponentInfo(prim_type, shape, element_names, ...)}
# first data timestamp (µs)
# Latest value (starts a background real-time subscription on first call).
# Values keep their true dtype and shape.
= # Sample(name, timestamp_us, values)
# Historical range [start_us, stop_us) — paginated internally, returns numpy
, =
# Live stream (rows of the requested components as they arrive)
# Fixed-rate replay of recorded data
...
# SQL over the same socket; component names map to snake_case table names
= # "drone_vio_position"
=
client.stream(...) returns an iterator usable as a context manager;
iteration ends when the stream is closed or its connection fails. start
accepts "earliest", "latest", or an integer microsecond timestamp.
Message logs (events)
Discrete events with variable-length payloads use the message-log API.
Payload encoding is a v1 convenience: bytes pass through untouched, str
is UTF-8, anything else is JSON.
= # [(timestamp_us, payload)]
# live, new messages only
...
Live message streams wake on new data and may coalesce bursts to the latest
message per wake. Use get_msgs for lossless historical reads.
Example: drive a 3D viewport from a flight controller
Anything that writes a world_pos-shaped component — 7 f64s holding a
scalar-last quaternion (q0, q1, q2, q3) followed by an (x, y, z) position
— can place a GLB model in an Editor viewport. Stream your attitude solution
under a namespaced entity, e.g. drone.world_pos:
=
=
# in the estimator loop (e.g. attitude determination on the flight controller):
Then a KDL schematic (drone.kdl) loads a GLB model at that pose and keeps
the camera on it:
coordinate frame=ENU
timeline
viewport name="chase" pos="drone.world_pos + (0,0,0,0, -6, 0, 2)" look_at="drone.world_pos" show_grid=#true
object_3d drone.world_pos {
glb path="assets/drone.glb" scale=1.0
}
Open the Editor against the database with the schematic:
The object_3d element accepts any EQL expression that evaluates to a
world_pos-like value, so the same pattern works for estimator ghosts
(drone.ekf_pos vs drone.vio_pos), targets, or any other entity your
process publishes. See the schematic reference for
the full viewport/object_3d syntax.
Lifecycle & troubleshooting
Server,Client,TableWriter, and both stream types are context managers;close()is idempotent.- Timestamps in the public API are microseconds (
int), the database's native resolution. - Set
ELODIN_DB_LOG=debug(anytracingfilter works) to surface the embedded server's and client's diagnostics from anelodin.db-using process.