tpt-stratum
RustFederated environmental data mesh: IoT sensor nodes gossip observations over QUIC, validate against neighbours, and serve a live HTTP API. Pure-Rust embedded storage, Ed25519-signed readings, SSE streaming. Runs on a Raspberry Pi Zero. No C dependencies, no cloud required.
Languages
tpt-stratum
A federated environmental data mesh — a peer-to-peer network of IoT sensor nodes that share, validate, and query geospatial environmental observations (temperature, humidity, pressure, and more).
Nodes gossip observations over QUIC, validate them against neighbours, store them locally, and expose an HTTP query API. The design is intentionally self-contained: no Kubernetes, no managed cloud, no C library dependencies. A Raspberry Pi Zero can run a full node.
Three commands to launch
# Install the binary
cargo install --path crates/tpt-stratum-node
# Initialise a new node (generates keypair + default config)
tpt-stratum init
# Start the node
tpt-stratum start
Or with Docker:
docker compose -f docker/docker-compose.yml up --build
What it does
- Ingest — accepts observations from local sensors (HTTP JSON, Davis weather stations, Ecowitt gateways, BME280 I2C, MQTT) or over the network
- Sign — every observation is Ed25519-signed by the originating node
- Store — pure-Rust embedded storage engine (WAL + SSTable, no RocksDB)
- Gossip — epidemic broadcast to a random fanout of peers over QUIC
- Validate — plausibility bounds + spatial IDW + temporal z-score anomaly detection
- Query — HTTP API for current conditions, time series, and real-time SSE streaming
Architecture
Sensor → Ingest Adapter → sign (Ed25519) → ObservationStore
↓
gossip broadcast (QUIC)
↓
neighbour nodes → validate
↓
HTTP API consumers
Configuration
Default config is written by tpt-stratum init to ./stratum.toml. Every key can be overridden by an environment variable prefixed TPT__:
[node]
keypair_path = "./stratum.key"
storage_path = "./data"
[network]
mesh_listen = "0.0.0.0:9001"
heartbeat_listen = "0.0.0.0:9002"
api_listen = "0.0.0.0:8080"
ingest_listen = "0.0.0.0:8081"
initial_peers = ["other-node.example.com:9001"]
[validation]
enabled = true
plausibility_weight = 0.5
spatial_weight = 0.3
temporal_weight = 0.2
[db_limits]
max_age_days = 30
disk_quota_mb = 0 # 0 = unlimited
[[sensors]]
adapter = "http_json" # accept JSON POSTs on ingest_listen
latitude = 51.5074
longitude = -0.1278
Environment variable override syntax:
TPT__NETWORK__API_LISTEN=0.0.0.0:9090 tpt-stratum start
HTTP API
| Endpoint | Description |
|---|---|
GET /health | Liveness probe → {"status":"ok"} |
GET /now?lat=&lng= | IDW-interpolated current conditions |
GET /series?lat=&lng=&from=&to=&limit=&offset= | Historical time series with pagination |
GET /stream?north=&south=&east=&west= | SSE real-time feed filtered by bounding box |
See docs/QUICKSTART.md for worked examples.
CLI
tpt-stratum init — generate keypair + default config
tpt-stratum start — start the node
tpt-stratum keygen — print a new keypair (does not persist)
tpt-stratum status — show node status (placeholder)
tpt-stratum peers — list connected peers (placeholder)
Global flags: --config, --data-dir, --verbose.
Workspace structure
Seven crates, each with a single responsibility:
| Crate | Role |
|---|---|
tpt-stratum-core | Domain types, Ed25519 crypto, protobuf schema |
tpt-stratum-storage | Custom embedded storage engine (WAL + SSTable, pure Rust) |
tpt-stratum-mesh | QUIC P2P gossip network (epidemic broadcast + backfill) |
tpt-stratum-validate | Pluggable confidence scoring pipeline |
tpt-stratum-api | Axum HTTP query API |
tpt-stratum-ingest | Sensor adapters (HTTP JSON, Davis, Ecowitt, BME280, MQTT) |
tpt-stratum-node | Binary entry point, CLI, config |
Build requirements
- Rust 1.85+ stable (
edition = "2024") - No C dependencies — pure Rust throughout
cargo build --release
cargo test
cargo clippy --all-targets --all-features
Extending the mesh
- Custom adapter — implement
IngestAdapterintpt-stratum-ingest. See docs/ADAPTERS.md. - Custom validator — implement
Validatorintpt-stratum-validateand add it to thePipeline. - API client — any HTTP client works. The SSE stream is standard
text/event-stream.
License
Apache 2.0 — see LICENSE.