A Digital Twin Engine Bridging OPC UA and Apache TinkerPop Graphs
WaldOT is an innovative open-source project that seamlessly integrates industrial automation (OT - Operational Technology) with modern data analysis through graph databases. Built on Apache TinkerPop and Eclipse Milo OPC UA, it transforms OPC UA address spaces into queryable, reactive graph structures.
Traditional industrial systems expose data through OPC UA - a powerful but hierarchical protocol. WaldOT revolutionizes this by representing the entire OPC UA address space as a living graph database, where:
- OPC UA Objects become TinkerPop Vertices
- OPC UA References become Graph Edges
- OPC UA Variables become Vertex Properties
- Changes propagate in real-time in both directions
This enables industrial engineers and data scientists to work with the same data using their preferred tools: OPC UA clients for configuration, Gremlin queries for analysis.
WaldOT unlocks the full potential of Apache TinkerPop's Gremlin query language for industrial data. Instead of navigating rigid hierarchies, you can traverse complex relationships with expressive queries:
// Find all motors in alarm state connected to Line 1
g.V().has('name', 'Line1')
.out('contains')
.has('type', 'motor')
.has('status', 'alarm')
.values('name')
// Calculate average temperature across all sensors in a zone
g.V().has('zone', 'production')
.has('type', 'temperature_sensor')
.values('value')
.mean()
// Find correlation patterns between equipment failures
g.V().has('event', 'failure')
.as('failed')
.in('connectedTo')
.where(out('causedBy').as('failed'))
.path()These queries run directly on the OPC UA server, without external databases or ETL pipelines.
WaldOT includes a sophisticated rules engine that enables edge computing logic without external systems. Define rules using simple IF-THEN patterns with full access to graph traversal:
Example: Temperature Monitoring
// Condition: Check if any sensor exceeds threshold
g.V().has('type', 'sensor')
.has('temperature', gt(80))
.hasNext()
// Action: Create alarm and notify
alarmTemp = g.V().has('type','sensor').has('temperature',gt(80)).values('temperature').next();
graph.addVertex('type', 'alarm', 'temperature', alarmTemp, 'timestamp', System.currentTimeMillis());
log.error('High temperature alert: ' + alarmTemp + '°C');Rules can:
- Query the graph using Gremlin in conditions
- Modify the graph creating/updating vertices in actions
- React to events with configurable hysteresis (debouncing)
- Execute in parallel with priority-based scheduling
- Integrate with external systems via HTTP, message buses, etc.
WaldOT is designed to run on-device - directly on RTUs (Remote Terminal Units), industrial PCs, or edge gateways:
- Low latency: No cloud round-trips for rule evaluation
- Offline capable: Operates without internet connectivity
- Bandwidth efficient: Only sends aggregated/filtered data upstream
- Secure: Data processing happens within the OT network perimeter
Built on proven open standards:
- OPC UA for industrial connectivity (Eclipse Milo)
- Apache TinkerPop 3.x for graph operations
- Gremlin for query language
- JEXL for rule expressions
- Plugin architecture for custom extensions
docker pull rossonet/waldot:latest
docker run -p 4840:4840 -p 8182:8182 -p 8080:8080 rossonet/waldotAccess:
- OPC UA Server:
opc.tcp://localhost:4840 - Gremlin Console:
ws://localhost:8182/gremlin - REST API:
http://localhost:8080/api
git clone https://github.com/rossonet/waldot.git
cd waldot
./gradlew clean build
java -jar waldot-app/build/libs/waldot-app-*.jar<!-- Maven -->
<dependency>
<groupId>net.rossonet.waldot</groupId>
<artifactId>waldot-api</artifactId>
<version>0.6.1</version>
</dependency>// Gradle
implementation 'net.rossonet.waldot:waldot-api:0.6.1'Query historical patterns and correlations:
// Find equipment that failed within 24h after temperature spike
g.V().has('type', 'equipment')
.where(
out('hasEvent').has('type', 'failure').as('failure')
.V().has('type', 'temperature_sensor')
.has('value', gt(90))
.has('timestamp', within(failure.timestamp - 86400000, failure.timestamp))
)Aggregate and analyze consumption:
// Total energy consumption per production line
g.V().has('type', 'production_line')
.group()
.by('name')
.by(out('contains').values('energy_kwh').sum())Trace product genealogy:
// Find all batches that used a specific raw material lot
g.V().has('lot_number', 'LOT12345')
.in('usedIn')
.in('producedBy')
.values('batch_id')React to complex conditions:
// Rule: Detect anomalous pump behavior
// Condition:
g.V().has('id', 'pump1').next().property('vibration').value() > threshold &&
g.V().has('id', 'pump1').next().property('flow').value() < minFlow
// Action:
graph.addVertex('type', 'maintenance_request', 'equipment', 'pump1', 'priority', 'high');
// Send notification via REST/MQTT/etc.- Agent Documentation - Developer guide for AI agents and contributors
- Architecture Overview - Detailed technical documentation
- Docker Images: DockerHub - WaldOT | Zenoh Router
- Maven Central: WaldOT Artifacts
- GitHub Repository: rossonet/waldot
Here's a minimal example showing WaldOT's key features:
// 1. Initialize WaldOT graph
WaldotGraph graph = OpcFactory.getOpcGraph("file:///tmp/waldot.db", new LoggerHistoryStrategy());
// 2. Create industrial equipment model
Vertex productionLine = graph.addVertex(
"id", "line1",
"type", "production_line",
"name", "Assembly Line 1"
);
Vertex motor1 = graph.addVertex(
"id", "motor1",
"type", "motor",
"rpm", 1500,
"temperature", 45.5,
"status", "running"
);
Vertex sensor1 = graph.addVertex(
"id", "temp_sensor_1",
"type", "temperature_sensor",
"value", 45.5,
"unit", "celsius"
);
// 3. Create relationships
productionLine.addEdge("contains", motor1);
motor1.addEdge("monitors", sensor1);
// 4. Define reactive rule
Vertex rule = graph.addVertex(
"id", "overheat_rule",
"type", "rule",
"name", "Motor Overheat Detection",
"condition", "g.V().has('id','temp_sensor_1').next().property('value').value() > 75",
"action", """
temp = g.V().has('id','temp_sensor_1').next().property('value').value();
log.warn('Motor overheating detected: ' + temp + '°C');
g.V().has('id','motor1').next().property('status', 'alarm');
graph.addVertex('type','alert','equipment','motor1','temperature',temp,'timestamp',System.currentTimeMillis());
""",
"hysteresis", 5000 // 5 second debounce
);
// 5. Create compute manager
Vertex compute = graph.addVertex(
"id", "compute_manager",
"type", "compute",
"threads", 2
);
// 6. Wire rule execution
sensor1.addEdge("fire", rule); // Trigger on sensor change
compute.addEdge("execute", rule, "priority", 100); // Execution priority
// 7. Now the system is live! Query from Gremlin or OPC UA client
// Gremlin query:
graph.traversal()
.V().has('type', 'motor')
.has('status', 'alarm')
.values('name')
.toList();
// OPC UA: Browse to opc.tcp://localhost:4840 and navigate the address spaceWaldOT supports TinkerPop's graph algorithms for industrial analytics:
// Shortest path between equipment
g.V().has('name', 'Pump1')
.repeat(out().simplePath())
.until(has('name', 'Tank5'))
.path()
.by('name')
// PageRank to find most connected equipment
g.V().pageRank().by('rank').values('rank').order().desc()
// Community detection for grouping related assets
g.V().connectedComponent().by('component').group().by('component')Create custom vertex types and behaviors:
@WaldotPlugin
public class MyIndustrialPlugin implements PluginListener {
@Override
public void initialize(WaldotNamespace namespace) {
// Register custom "Conveyor" vertex type in OPC UA
createConveyorTypeNode(namespace);
}
@Override
public WaldotVertex createVertex(NodeId typeNodeId, ...) {
return new ConveyorVertex(...); // Custom logic
}
}- REST API: HTTP endpoints for external systems
- Gremlin Server: WebSocket protocol (port 8182)
- OPC UA Client: Connect to other OPC UA servers
- Message Bus: Zenoh pub/sub connector for edge-to-cloud
- JDBC: Export graph data to SQL databases
- GraphQL: Query via GraphQL over HTTP
- Edge-optimized: Runs on devices with 1GB+ RAM
- Virtual threads: Java 21 for massive concurrency
- Persistent storage: RocksDB backend for large graphs
- Streaming: Process millions of data points without memory issues
- Distributed: Cluster support via TinkerPop providers (Neo4j, JanusGraph, etc.)
We welcome contributions! Whether you're:
- Adding new plugins
- Improving documentation
- Reporting bugs
- Suggesting features
Please see AGENT.md for development guidelines.
Key contribution areas:
- Additional storage connectors (Neo4j, JanusGraph, etc.)
- Rule engine extensions (SQL-like DSL, visual editor)
- Dashboard integrations (Grafana, real-time graph visualization)
- Protocol bridges (MQTT, Modbus, BACnet)
- AI/ML model integration
- Apache TinkerPop Documentation
- OPC UA Address Space Specification
- Eclipse Milo GitHub
- Gremlin Query Language
WaldOT is released under the Apache License 2.0.
See LICENSE file for details.
Ready to bridge your OT and IT worlds? Get started with WaldOT today!

