Eimdall Documentation v0.9
Eimdall is the edge-first product intelligence and predictive maintenance platform for robotics. It runs fully air-gapped — no cloud dependency, no data leaving your infrastructure.
Edge Core and Central are proprietary binaries distributed by DATA AGILITY after a demo. The ROS 2 bridge is open source (MIT). Request access →
Two dashboards, one platform: the manufacturer dashboard shows feature adoption, user journeys and friction signals; the operator dashboard shows fleet health, anomaly timelines and failure predictions.
What Eimdall gives you
- Feature adoption analytics — know which robot capabilities are used, ignored or constantly overridden
- Usage journeys — track session flows, repeated paths and abandonment moments across your entire fleet
- Friction detection — surface retries, overrides and operator workarounds automatically
- Failure prediction — detect anomalies before they become downtime with a local ML pipeline
- LLM insights — Ollama generates recommendations, daily digests and cross-environment comparisons — all on-premise
Architecture
Eimdall is composed of two binaries that communicate via a structured report format. No raw sensor stream is ever transmitted.
Edge Core
Rust runtime on the robot. Ingests sensors, builds features, detects anomalies, stores reports in a local spool.
Central
Your server. Receives compact daily reports, stores them, serves the dashboards and runs LLM analysis.
Insights
Local Ollama instance generates natural-language analysis, predictions and recommendations on demand.
Data flow
- Your robot bridge pushes sensor readings to the Edge Core via a local HTTPS endpoint (
127.0.0.1:8787) - Edge Core aggregates readings into DailyReports — compact protobuf summaries (typically <50 KB)
- Reports are stored in an on-robot spool (up to 14 days of autonomy) and uploaded to Central when connectivity is available
- Central indexes reports and exposes them to the dashboards via a REST API
- Insights are generated on demand by triggering a local Ollama model against the indexed data
Neither the Edge Core nor Central require internet access at runtime. DNS, NTP and all external dependencies are optional.
Quickstart — first robot in 30 minutes
This guide connects a single robot to a running Central instance.
Download and install the Edge binary
You received a download link from DATA AGILITY after your demo. The archive contains the pre-compiled binary for your target platform (linux-x86_64 or linux-arm64).
# Extract the archive you received
tar -xzf eimdall-edge-core-v0.9.2-linux-x86_64.tar.gz
# Verify integrity (SHA256 provided in the email)
sha256sum eimdall-edge-core
# Install
sudo mkdir -p /opt/eimdall/bin
sudo install -m 755 eimdall-edge-core /opt/eimdall/bin/eimdall-edge
# Verify
/opt/eimdall/bin/eimdall-edge --version
Request access via the demo form on eimdall.io. A download link is sent by email after a 15-minute demo call.
Create the configuration file
Save as /etc/eimdall/edge.runtime.yaml. Replace YOUR_CENTRAL_IP.
feature_set_version: "fs-2026-04"
sensors:
- sensor_id: "body_imu"
family: "imu"
enabled: true
sample_rate_hz: 10
spool_disk:
path: "/var/lib/eimdall/spool"
ttl_days: 14
max_bytes: 1073741824 # 1 GB
upload:
central_url: "https://YOUR_CENTRAL_IP:8080"
retry_base_ms: 1000
max_retries: 8
tls_ca_cert_path: "/etc/eimdall/tls/central-ca.crt"
local_service:
enabled: true
bind: "127.0.0.1:8787"
require_pairing: true
token_file: "/etc/eimdall/eimdall-local-service.token"
sudo mkdir -p /etc/eimdall /var/lib/eimdall/spool /var/log/eimdall
echo "token-$(head -c 8 /dev/urandom | xxd -p)" | \
sudo tee /etc/eimdall/eimdall-local-service.token
Start the Edge runtime
sudo /opt/eimdall/bin/eimdall-edge \
--config /etc/eimdall/edge.runtime.yaml
# Verify
curl --cacert /etc/eimdall/tls/edge-local-ca.crt \
https://127.0.0.1:8787/v1/local/ping
# → {"status":"ok","version":"0.9.2"}
Push your first sensor reading
Use the Python bridge below or any HTTP client. See The bridge for details.
TOKEN=$(cat /etc/eimdall/eimdall-local-service.token)
curl -sk https://127.0.0.1:8787/v1/local/bridge/ingest \
-H "X-Eimdall-Bridge-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"robot_id": "robot-01",
"bridge_id": "bridge-main",
"source": "constructor_sdk",
"sensor_id": "body_imu",
"family": "imu",
"ts_unix_ms": '$(date +%s000)',
"values": {
"accel_x_g": 0.01, "accel_y_g": -0.02, "accel_z_g": 1.01,
"gyro_x_dps": 0.5, "gyro_y_dps": -0.3, "gyro_z_dps": 0.1
}
}'
View data on the Central dashboard
Open https://YOUR_CENTRAL_IP:8080/app/auth/login, log in with your constructor account. Your robot appears in Fleet overview within minutes. To force an immediate upload:
SESSION=$(curl -sk -X POST https://127.0.0.1:8787/v1/local/pair/session \
-H "X-Eimdall-Bridge-Token: $TOKEN" | jq -r .session_token)
curl -sk -X POST https://127.0.0.1:8787/v1/local/upload/trigger \
-H "Authorization: Bearer $SESSION"
The bridge
The bridge is the only code you write. It reads your robot's native SDK and pushes readings to the Edge Core's local HTTPS endpoint. Eimdall provides no SDK dependency — any HTTP client works.
Python bridge — minimal example
import requests, time
EDGE_URL = "https://127.0.0.1:8787/v1/local/bridge/ingest"
HB_URL = "https://127.0.0.1:8787/v1/local/bridge/heartbeat"
TOKEN = open("/etc/eimdall/eimdall-local-service.token").read().strip()
HEADERS = {"X-Eimdall-Bridge-Token": TOKEN, "Content-Type": "application/json"}
CA = "/etc/eimdall/tls/edge-local-ca.crt"
def push_imu(ax, ay, az, gx, gy, gz):
requests.post(EDGE_URL, headers=HEADERS, verify=CA, timeout=2, json={
"robot_id": "robot-01",
"bridge_id": "bridge-main",
"source": "constructor_sdk",
"sensor_id": "body_imu",
"family": "imu",
"ts_unix_ms": int(time.time() * 1000),
"values": {
"accel_x_g": ax, "accel_y_g": ay, "accel_z_g": az,
"gyro_x_dps": gx, "gyro_y_dps": gy, "gyro_z_dps": gz,
}
})
def send_heartbeat():
requests.post(HB_URL, headers=HEADERS, verify=CA, timeout=2, json={
"robot_id": "robot-01", "bridge_id": "bridge-main",
"ts_unix_ms": int(time.time() * 1000),
"status": "ok", "uptime_s": int(time.monotonic()), "errors_5m": 0
})
last_hb = 0
while True:
# ← replace with your robot SDK call
push_imu(0.01, -0.02, 1.01, 0.5, -0.3, 0.1)
if time.time() - last_hb > 5:
send_heartbeat()
last_hb = time.time()
time.sleep(0.1)
Ingest payload format
| Field | Type | Description |
|---|---|---|
| robot_id | string | Unique robot identifier within your tenant |
| bridge_id | string | Bridge instance name (e.g. bridge-main) |
| source | string | constructor_sdk | ros2 | custom |
| sensor_id | string | Sensor name, matches config sensors[].sensor_id |
| family | string | Sensor family — see Sensor families |
| ts_unix_ms | integer | Timestamp in milliseconds (Unix epoch) |
| values | object | Key-value map of sensor readings (family-specific) |
Sensor families
Each family defines a set of expected keys in the values payload. Unknown keys are accepted and stored as generic numerics.
| Family | Key fields | Typical use |
|---|---|---|
| imu | accel_x/y/z_g, gyro_x/y/z_dps | Body motion, vibration, fall detection |
| motor | current_a, rpm, temp_c, torque_nm | Drivetrain health, drift detection |
| battery | voltage_v, current_a, soc_pct, temp_c | Charge cycles, drain patterns |
| lidar | scan_count, obstacle_count, retry_count | Navigation friction, environment mapping |
| proximity | distance_m, trigger_count | Collision risk, retry analysis |
| camera | fps, detection_count, latency_ms | Vision pipeline health |
| audio | rms_db, keyword_count, retry_count | Voice recognition adoption and friction |
| encoder | position_ticks, velocity_rps, error_count | Odometry accuracy, wear detection |
| touch | event_count, duration_ms, retry_count | UI interaction, fallback measurement |
| generic | any numeric keys | Custom sensors, process variables |
ROS 2 integration
The eimdall_ros2_bridge package translates ROS 2 topic messages into Eimdall bridge payloads. It runs as a sidecar node alongside your ROS 2 stack with no dependency on Eimdall binaries in your ROS workspace.
The ROS 2 bridge is open source and MIT-licensed. No demo required — clone, build and adapt freely.
Install
# Clone into your ROS 2 workspace
cd ~/ros2_ws/src
git clone https://github.com/YvesData/eimdall-ros2-bridge.git
cd ~/ros2_ws
colcon build --packages-select eimdall_ros2_bridge
source install/setup.bash
Launch
ros2 launch eimdall_ros2_bridge bridge.launch.py \
robot_id:=robot-01 \
edge_url:=https://127.0.0.1:8787 \
token_file:=/etc/eimdall/eimdall-local-service.token
The bridge automatically maps standard ROS 2 message types to Eimdall sensor families: sensor_msgs/Imu → imu, sensor_msgs/BatteryState → battery, nav_msgs/Odometry → encoder, etc.
Any additional topic can be mapped to a generic sensor by editing the launch file topic map. No code change required.
Key concepts
DailyReport
The unit of data exchange between Edge and Central. A DailyReport is a compact protobuf summary built over a rolling 24-hour window. It contains aggregated feature vectors, anomaly counts, session summaries and usage patterns — no raw sensor values. Typical size: 10–50 KB per robot per day.
Anomaly scoring
The Edge Core maintains per-sensor baselines using an online learning pipeline. When a sensor reading deviates significantly from its baseline, an anomaly event is recorded with a severity level (low / medium / high / critical) and attached to the next DailyReport.
Insights
Insights are natural-language analysis results generated by a local Ollama LLM model against the indexed DailyReports. Available insight kinds:
| Kind | Description |
|---|---|
| fleet_health | Overall fleet status summary with risk breakdown |
| feature_adoption_report | Which features are used, ignored or overridden across the fleet |
| friction_emerging_usage | Detected retry patterns and unexpected usage behaviours |
| user_journey_analysis | Session flow analysis and abandonment moments |
| predictive_maintenance | Per-robot failure risk scores and maintenance recommendations |
| cross_environment_comparison | Usage differences across sites, firmware versions or clients |
| robot_anomaly | Anomaly deep-dive for a specific robot |
| daily_digest | Daily summary for a specific robot |
Multi-tenancy
A single Central instance supports multiple manufacturers (constructor tenants) and their clients (client tenants). Each robot belongs to one tenant. Clients can access only the data shared by their manufacturer — the sharing policy (none / aggregated / full) is set per-tenant by the manufacturer.
Security
Transport
All communication between Edge, bridge, and Central uses HTTPS/TLS. The local Edge service (127.0.0.1:8787) uses a self-signed certificate. The Central API supports mutual TLS (mTLS) for robot authentication.
Authentication
- Bridge → Edge: static token via
X-Eimdall-Bridge-Tokenheader - Edge → Central: mTLS client certificate (optional) + robot registration token
- Dashboard / API: JWT Bearer (HS256, 12h TTL), renewed via
/v1/auth/refresh
Data privacy
No raw sensor data is ever transmitted over the network. Only aggregated DailyReports move between Edge and Central. The LLM never sees raw values — it receives pre-computed feature summaries.
For production, generate proper TLS certificates for Central using your PKI. Self-signed certs are sufficient for development and air-gapped demos. See the admin guide for mTLS setup.
API overview
The Central REST API uses JSON throughout. All routes except /v1/auth/login require Authorization: Bearer <token>.
Base URL: https://YOUR_CENTRAL:8080
Authentication
Fleet & robots
Intelligence
Webhooks
The full API reference documents every endpoint with parameters, request/response examples and integration code for Python, JavaScript and curl.
Prometheus & Grafana integration
Eimdall exposes a Prometheus-compatible metrics endpoint on a dedicated port 9091, completely isolated from the main API. Your existing Grafana stack continues to work — Eimdall publishes what it knows, you visualise it the way you want.
Eimdall's value is product intelligence, not raw monitoring. Prometheus/Grafana gives you time-series dashboards. Eimdall gives you why — adoption patterns, predictive failure, usage friction, LLM insights. They complement each other; they do not replace each other.
Security model (4 independent layers)
The metrics endpoint is hardened by design. There is no configuration that allows a non-authenticated, non-encrypted scrape.
| Layer | Mechanism | Purpose |
|---|---|---|
| 1 — Network isolation | Dedicated port 9091, firewallable independently | Inaccessible if port is not explicitly opened |
| 2 — TLS 1.3 only | Server certificate required (EIMDALL_METRICS_TLS_CERT/KEY) | All traffic encrypted, no downgrade possible |
| 3 — mTLS (recommended) | Client CA required (EIMDALL_METRICS_CLIENT_CA) | Only Prometheus with a signed cert is accepted |
| 4 — Bearer token | Constant-time comparison (EIMDALL_METRICS_TOKEN) | Defense-in-depth, timing-attack resistant |
Environment variables
EIMDALL_METRICS_TOKEN=<secret> # Required — bearer token for Prometheus scrape EIMDALL_METRICS_TLS_CERT=/path/cert.pem # Required — server TLS certificate (PEM) EIMDALL_METRICS_TLS_KEY=/path/key.pem # Required — server TLS private key (PEM) EIMDALL_METRICS_CLIENT_CA=/path/ca.pem # Recommended — CA to verify Prometheus client cert (mTLS) EIMDALL_METRICS_PORT=9091 # Optional — default 9091
If EIMDALL_METRICS_TOKEN or TLS variables are absent, the metrics server does not start — no fallback, no plaintext endpoint.
Prometheus scrape configuration
Add the following job to your prometheus.yml:
scrape_configs:
- job_name: eimdall
scrape_interval: 30s
scheme: https
tls_config:
ca_file: /etc/prometheus/certs/eimdall-ca.pem # CA that signed Eimdall's cert
cert_file: /etc/prometheus/certs/prometheus.pem # Prometheus client cert (mTLS)
key_file: /etc/prometheus/certs/prometheus-key.pem # Prometheus client key
authorization:
type: Bearer
credentials_file: /etc/prometheus/eimdall-token # File containing the bearer token
static_configs:
- targets: ["YOUR_CENTRAL_HOST:9091"]
Generating certificates (quick start)
# 1. Create CA openssl req -x509 -newkey rsa:4096 -keyout ca-key.pem -out ca.pem \ -days 3650 -nodes -subj "/CN=eimdall-metrics-ca" # 2. Server cert for Eimdall openssl req -newkey rsa:4096 -keyout server-key.pem -out server.csr \ -nodes -subj "/CN=YOUR_CENTRAL_HOST" openssl x509 -req -in server.csr -CA ca.pem -CAkey ca-key.pem \ -CAcreateserial -out server.pem -days 365 # 3. Client cert for Prometheus (mTLS) openssl req -newkey rsa:4096 -keyout prometheus-key.pem -out prometheus.csr \ -nodes -subj "/CN=prometheus-scraper" openssl x509 -req -in prometheus.csr -CA ca.pem -CAkey ca-key.pem \ -CAcreateserial -out prometheus.pem -days 365 # 4. Generate bearer token openssl rand -hex 32 > eimdall-token
Available metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
eimdall_robot_last_seen_timestamp_seconds | gauge | robot_id, tenant_id | Unix timestamp of last ingest for this robot |
eimdall_robot_online | gauge | robot_id, tenant_id | 1 if robot was seen in the last 24 h, else 0 |
eimdall_fleet_robot_count | gauge | tenant_id | Total robots registered for the tenant |
eimdall_fleet_online_count | gauge | tenant_id | Robots seen in the last 24 h |
eimdall_fleet_critical_count | gauge | tenant_id | Robots with at least one severity ≥ 3 anomaly in 24 h |
eimdall_anomaly_events_total | counter | robot_id, tenant_id | Cumulative anomaly events for this robot |
eimdall_anomaly_severity_last | gauge | robot_id, tenant_id | Severity of the most recent anomaly (1–4) |
eimdall_daily_reports_total | counter | tenant_id | DailyReports ingested for this tenant |
eimdall_insights_total | counter | tenant_id | LLM insights generated for this tenant |
Grafana dashboard
An importable Grafana dashboard is available at monitoring/grafana-dashboard.json in the eimdall-ros2-bridge repository.
Import steps:
- In Grafana → Dashboards → Import
- Upload
grafana-dashboard.json - Select your Prometheus datasource when prompted
- Click Import
The dashboard includes: fleet KPIs (total/online/critical robots, insights, reports), online robots over time, anomaly events per hour, per-robot status table with ONLINE/OFFLINE badge, and cumulative insights/reports time series.
You can build Grafana alert rules on top of any Eimdall metric. For example: alert when eimdall_fleet_critical_count > 0 or when eimdall_robot_online{robot_id="..."} == 0 for more than 10 minutes. Eimdall's webhooks (via POST /v1/webhooks) provide a complementary push channel.
OpenTelemetry integration
Eimdall supports OpenTelemetry natively on two channels:
- Metrics — the existing Prometheus endpoint (port 9091) is scraped by the OTel Collector via its
prometheusreceiver, then forwarded as OTLP to any backend. - Traces — Eimdall Central pushes OTLP gRPC spans directly to the Collector (or any OTLP endpoint) when
EIMDALL_OTEL_ENDPOINTis set.
The OTel Collector acts as the bridge: it scrapes Eimdall metrics, receives Eimdall traces, and forwards everything to Grafana Tempo, Jaeger, Datadog, or any OTLP-compatible backend — without changing Eimdall's config.
Activating trace export
Set one environment variable before starting Eimdall Central:
EIMDALL_OTEL_ENDPOINT=http://otel-collector:4317
If the variable is absent or empty, Eimdall runs in console-only mode — no OTLP traffic, zero overhead.
Eimdall sends spans with these resource attributes:
service.name = "eimdall-central"service.version = "0.9.x"
OTel Collector configuration
A ready-to-use Collector config is available at monitoring/otel-collector-config.yaml in the eimdall-ros2-bridge repository. It wires both channels (metrics scrape + traces receive) and forwards to Grafana Tempo.
receivers:
prometheus: # scrape Eimdall metrics (port 9091, mTLS + bearer)
config:
scrape_configs:
- job_name: eimdall
scrape_interval: 30s
scheme: https
tls_config:
ca_file: /etc/otelcol/certs/eimdall-ca.pem
cert_file: /etc/otelcol/certs/collector-client.pem
key_file: /etc/otelcol/certs/collector-client-key.pem
authorization:
type: Bearer
credentials_file: /etc/otelcol/eimdall-token
static_configs:
- targets: ["EIMDALL_HOST:9091"]
otlp: # receive Eimdall traces (EIMDALL_OTEL_ENDPOINT)
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
timeout: 5s
exporters:
otlp/tempo:
endpoint: GRAFANA_TEMPO_HOST:4317
service:
pipelines:
metrics:
receivers: [prometheus]
processors: [batch]
exporters: [otlp/tempo]
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/tempo]
Running the Collector with Docker
docker run --rm \ -v $(pwd)/otel-collector-config.yaml:/etc/otelcol/config.yaml \ -v /etc/eimdall/certs:/etc/otelcol/certs \ -v /etc/eimdall/eimdall-token:/etc/otelcol/eimdall-token \ -p 4317:4317 -p 4318:4318 \ otel/opentelemetry-collector-contrib:latest
Supported OTel backends
| Backend | Signals | Exporter in Collector config |
|---|---|---|
| Grafana Tempo | Traces + Metrics | otlp/tempo |
| Jaeger | Traces | otlp/jaeger (OTLP receiver in Jaeger) |
| Datadog | Traces + Metrics | datadog exporter |
| New Relic | Traces + Metrics | otlp (New Relic OTLP endpoint) |
| Honeycomb | Traces | otlp (Honeycomb OTLP endpoint) |
| Any OTLP backend | Traces + Metrics | Standard otlp exporter |
The Collector-to-Eimdall path uses the same mTLS + bearer token as direct Prometheus scraping. The Eimdall-to-Collector trace path (OTLP gRPC) should run on a private network or be secured with TLS — configure tls: on the Collector's otlp receiver if the Collector is not co-located with Central.