Explore to Monitor Pattern
Learn how to use the Explore node to discover an object's structure and feed it to a Monitor node for continuous real-time updates as a JSON structure.
Overview
This tutorial demonstrates a powerful pattern: use the Explore node to automatically discover all variables under an object, then pass that structure to a Monitor node to get continuous updates whenever any value changes.
Use Case
You want to monitor an entire object (like a Motor, Sensor, or Controller) without manually listing every variable. The Explore node discovers the structure once, and the Monitor node keeps it updated continuously.
Prerequisites
- Node-RED with @opcua/for-node-red installed
- An active OPC UA connection to a server
- An object NodeId to explore (e.g., a Motor or Sensor object)
Step-by-Step Tutorial
Step 1: Create the Basic Flow
Create a flow with these nodes:
[Inject] → [Explore] → [Monitor] → [Debug]
Step 2: Configure the Inject Node
Set up an inject node to trigger the exploration:
- Payload: timestamp
- Topic: (leave empty)
- Repeat: none (one-time trigger)
- Once after: 0.1 seconds (optional, for auto-start)
Step 3: Configure the Explore Node
Configure the Explore node to discover the object structure:
- Connection: Select your OPC UA connection
- Input NodeId: Enter the object's NodeId
- Example:
ns=3;s=MyMotor - Or use:
ns=3;i=1234
- Example:
- Output Type: Select "Value"
- This returns the full subtree with values
- Browse Filter: (leave default)
- Or select "Variables" to skip method nodes
Why "Value" output type?
- It provides a complete JSON structure with both NodeIds and current values
- The Monitor node can accept this structure directly
- Perfect for initial discovery before continuous monitoring
Step 4: Add a Function Node (Optional but Recommended)
Insert a function node between Explore and Monitor to verify and prepare the structure:
// Log the explored structure
node.warn("Explored structure:");
node.warn(JSON.stringify(msg.payload, null, 2));
// Pass through to Monitor
return msg;
This helps you see what was discovered before monitoring begins.
Step 5: Configure the Monitor Node
Set up the Monitor node to receive the explored structure:
- Connection: Same connection as Explore node
- Input Type: Select "JSON Structure"
- Queue Size: 10 (default)
- Sampling Interval: 1000 ms (adjust as needed)
- Discard Oldest: checked
- Monitor Type: DataValue (or Value if you prefer simpler output)
Important: The Monitor node automatically accepts the Explore output format when using JSON Structure input type.
Step 6: Configure the Debug Node
Set up the debug output:
- Output: complete msg object (or just msg.payload)
- To: debug window
- Name: "Monitor Updates"
Complete Flow Example
[
{
"id": "inject1",
"type": "inject",
"name": "Trigger Exploration",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 140,
"y": 100,
"wires": [["explore1"]]
},
{
"id": "explore1",
"type": "opcua-explore",
"name": "Explore Motor",
"connection": "connection1",
"nodeId": "ns=3;s=MyMotor",
"outputType": "Value",
"x": 320,
"y": 100,
"wires": [["function1"]]
},
{
"id": "function1",
"type": "function",
"name": "Log Structure",
"func": "node.warn('Explored structure:');\nnode.warn(JSON.stringify(msg.payload, null, 2));\nreturn msg;",
"outputs": 1,
"x": 490,
"y": 100,
"wires": [["monitor1"]]
},
{
"id": "monitor1",
"type": "opcua-monitor",
"name": "Monitor Motor",
"connection": "connection1",
"inputType": "jsonStructure",
"monitorType": "DataValue",
"samplingInterval": 1000,
"queueSize": 10,
"discardOldest": true,
"x": 670,
"y": 100,
"wires": [["debug1"]]
},
{
"id": "debug1",
"type": "debug",
"name": "Monitor Updates",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"x": 860,
"y": 100,
"wires": []
}
]
Understanding the Data Flow
1. Explore Output
When you trigger the flow, the Explore node outputs a structure like:
{
"Speed": {
"nodeId": "ns=3;s=MyMotor.Speed",
"value": 1500
},
"Temperature": {
"nodeId": "ns=3;s=MyMotor.Temperature",
"value": 65.5
},
"Status": {
"nodeId": "ns=3;s=MyMotor.Status",
"value": "Running"
}
}
2. Monitor Input
The Monitor node receives this structure and automatically:
- Extracts all NodeIds
- Creates a subscription for each variable
- Maintains the JSON structure format
3. Monitor Output
Whenever any value changes, you receive an updated structure:
{
"Speed": {
"value": 1520,
"statusCode": {
"value": 0,
"description": "Good"
},
"sourceTimestamp": "2025-11-24T10:30:45.123Z",
"serverTimestamp": "2025-11-24T10:30:45.125Z"
},
"Temperature": {
"value": 65.5,
"statusCode": {
"value": 0,
"description": "Good"
},
"sourceTimestamp": "2025-11-24T10:30:45.123Z",
"serverTimestamp": "2025-11-24T10:30:45.125Z"
},
"Status": {
"value": "Running",
"statusCode": {
"value": 0,
"description": "Good"
},
"sourceTimestamp": "2025-11-24T10:30:45.123Z",
"serverTimestamp": "2025-11-24T10:30:45.125Z"
}
}
Note: Only variables that changed are included in each update (unless all values change simultaneously).
Advanced Pattern: Periodic Re-exploration
For dynamic servers where the object structure might change, add periodic re-exploration:
[Inject Every Hour] → [Explore] → [Monitor]
Configure the Inject node:
- Repeat: interval
- Every: 1 hour (or as needed)
This refreshes the monitored structure periodically, catching any new variables added to the object.
Handling Complex Objects
Nested Objects
If your object has nested sub-objects, adjust the Explore settings:
// Explore configuration for complex object
{
"nodeId": "ns=3;s=ComplexDevice",
"outputType": "Value",
"browseFilter": "Variables" // Skip method nodes
}
The Monitor output maintains the nested structure:
{
"Motor": {
"Speed": {
"value": 1500,
"statusCode": { "value": 0 }
},
"Temperature": {
"value": 65.5,
"statusCode": { "value": 0 }
}
},
"Pump": {
"Pressure": {
"value": 250,
"statusCode": { "value": 0 }
},
"FlowRate": {
"value": 100,
"statusCode": { "value": 0 }
}
}
}
Large Objects
For objects with many variables (50+):
- Filter the Explore output before monitoring:
// Function node to filter only important variables
const filtered = {};
// Only monitor variables matching certain criteria
for (const [key, value] of Object.entries(msg.payload)) {
// Example: Only monitor numeric values
if (typeof value.value === 'number') {
filtered[key] = value;
}
}
msg.payload = filtered;
return msg;
- Adjust Monitor settings:
- Increase queue size: 20-50
- Increase sampling interval: 2000-5000 ms
- Use Value instead of DataValue for less data
Use Cases
1. Equipment Dashboard
Monitor an entire machine in real-time:
- Explore: Machine object (motors, sensors, actuators)
- Monitor: Continuous updates
- Dashboard: Display all parameters with auto-refresh
2. Data Logging
Capture all object changes for history:
- Explore: Process unit
- Monitor: All variable changes
- Database: Store changes with timestamps
3. Alarm Monitoring
Track alarm objects automatically:
- Explore: Alarm folder
- Monitor: All alarm states
- Filter: Process only active alarms
4. Dynamic Configuration
Handle servers with changing structures:
- Explore: Equipment list (periodic)
- Monitor: All discovered equipment
- Adapt: Automatically monitor new equipment when added
Optimizing Performance
Sampling Interval
Choose appropriate sampling rates:
- Fast changes (100-500 ms): Motor speeds, pressures
- Medium changes (1000-2000 ms): Temperatures, flow rates
- Slow changes (5000+ ms): Status, configuration values
Queue Size
Adjust based on update frequency:
- High frequency: Queue size 20-50
- Medium frequency: Queue size 10 (default)
- Low frequency: Queue size 5
Deadband Filtering
Add deadband filtering for analog values:
// Function node after Monitor
const filtered = {};
const deadband = 0.5; // 0.5% change required
for (const [key, data] of Object.entries(msg.payload)) {
const current = data.value;
const previous = context.get(key) || current;
const change = Math.abs((current - previous) / previous * 100);
if (change >= deadband) {
filtered[key] = data;
context.set(key, current);
}
}
msg.payload = filtered;
return msg;
Troubleshooting
No Updates Received
Problem: Monitor node doesn't output anything after initial setup.
Solutions:
- Verify Explore output contains valid NodeIds
- Check Monitor node shows "subscribed" status
- Ensure values are actually changing on the server
- Check connection is active
Too Many Updates
Problem: Receiving updates too frequently.
Solutions:
- Increase sampling interval (e.g., 2000 ms)
- Add deadband filtering
- Filter Explore output to fewer variables
- Use Value output type instead of DataValue
Structure Not Preserved
Problem: Monitor output doesn't maintain the JSON structure.
Solutions:
- Verify Monitor input type is set to "JSON Structure"
- Check Explore output type is "Value"
- Ensure no modification between Explore and Monitor nodes
Memory Issues
Problem: Node-RED becomes slow or unresponsive.
Solutions:
- Reduce number of monitored variables
- Decrease queue size to 5-10
- Increase sampling interval
- Filter Explore output before monitoring
Best Practices
- Start with Exploration: Always verify the Explore output before connecting to Monitor
- Use Function Nodes: Add logging to understand the data flow
- Filter Early: Remove unnecessary variables before monitoring
- Optimize Settings: Adjust sampling interval and queue size based on your needs
- Monitor Performance: Watch Node-RED memory and CPU usage
- Handle Errors: Add error handling for disconnections
- Document NodeIds: Keep track of which objects you're monitoring
Next Steps
- Explore Output Types - Learn about different output formats
- Monitor JSON Structure - Deep dive into JSON structure monitoring
- Monitor Subtree - Alternative approach using subtree monitoring
- Explore Tips - Best practices for exploration