Skip to main content

Error Handling in History Reads

Learn how to handle errors and edge cases when reading historical data.

Common Errors

BadNodeIdUnknown

Variable doesn't exist on the server.

// In a Catch node or after History Read
if (msg.statusCode === "BadNodeIdUnknown") {
node.warn(`Variable not found: ${msg.nodeId}`);
msg.payload = [];
return msg;
}

BadHistoryOperationUnsupported

Server doesn't support historical data access.

if (msg.statusCode === "BadHistoryOperationUnsupported") {
node.error("Server does not support history reads");
msg.payload = { error: "History not available" };
return msg;
}

BadTimestampsToReturnInvalid

Invalid time range specified.

if (msg.statusCode === "BadTimestampsToReturnInvalid") {
node.warn("Invalid time range - using defaults");
msg.startTime = "1 hour ago";
msg.endTime = "now";
// Retry the history read
}

Validation Flow

[ Input ] → [ Validate ] → [ History Read ] → [ Check Results ] → [ Output ]
↓ ↓ ↓
[ Error 1 ] [ Error 2 ] [ Error 3 ]

Validate (Function):

// Check required fields
if (!msg.nodeId && !msg.topic) {
node.error("NodeId is required");
return null;
}

// Validate time range
const start = new Date(msg.startTime || "1 hour ago");
const end = new Date(msg.endTime || "now");

if (start > end) {
node.error("Start time must be before end time");
return null;
}

return msg;

Check Results (Function):

// Check for errors in response
if (msg.statusCode && msg.statusCode !== "Good") {
node.warn(`History read failed: ${msg.statusCode}`);
msg.payload = [];
return [null, msg]; // Route to error output
}

// Check for empty results
if (!msg.payload || msg.payload.length === 0) {
node.warn("No historical data available");
msg.payload = [];
return [null, msg]; // Route to empty data handler
}

return [msg, null]; // Route to success output

Retry Logic

[ History Read ] → [ Check Error ] → [ Retry Counter ] → [ History Read ]
↓ ↓
[ Success ] [ Give Up ]

Retry Counter (Function):

// Initialize retry count
msg.retryCount = msg.retryCount || 0;

if (msg.statusCode !== "Good" && msg.retryCount < 3) {
msg.retryCount++;
node.warn(`Retry attempt ${msg.retryCount}`);

// Wait before retry
setTimeout(() => {
node.send(msg);
}, 1000 * msg.retryCount);

return null;
} else if (msg.retryCount >= 3) {
node.error("Max retries reached");
msg.payload = { error: "Failed after 3 retries" };
return msg;
}

return msg;

Handling Empty Results

// After History Read node
if (!msg.payload || msg.payload.length === 0) {
// Provide default or notify user
msg.payload = [{
value: null,
sourceTimestamp: new Date().toISOString(),
statusCode: "NoData"
}];

node.warn(`No data found for ${msg.nodeId} in specified range`);
}

return msg;

Quality Code Checking

// Filter out bad quality data
msg.payload = msg.payload.filter(point => {
return point.statusCode === "Good" ||
point.statusCode.startsWith("Good");
});

if (msg.payload.length === 0) {
node.warn("All data points had bad quality");
}

return msg;

Graceful Degradation

[ Primary History ] → [ Check ] → [ Use Primary ]
↓ ↑
[ Failed ] |
↓ |
[ Backup Strategy ] → [ Format ] ───┘

Backup Strategy (Function):

// If primary history fails, use estimated values
if (msg.primaryFailed) {
node.warn("Using estimated data - history unavailable");

// Generate estimated points based on last known value
const lastValue = flow.get('lastKnownValue') || 0;
const points = [];

for (let i = 0; i < 10; i++) {
points.push({
value: lastValue,
sourceTimestamp: new Date(Date.now() - i * 60000).toISOString(),
statusCode: "Estimated"
});
}

msg.payload = points;
}

return msg;

Complete Error Handling Example

// Comprehensive error handler
try {
// Validate input
if (!msg.nodeId) {
throw new Error("NodeId is required");
}

// Check response
if (msg.statusCode !== "Good") {
throw new Error(`History read failed: ${msg.statusCode}`);
}

// Validate data
if (!Array.isArray(msg.payload) || msg.payload.length === 0) {
node.warn("No historical data available");
msg.payload = [];
msg.dataAvailable = false;
} else {
msg.dataAvailable = true;
}

// Filter bad quality
msg.payload = msg.payload.filter(p =>
p.statusCode === "Good"
);

return msg;

} catch (error) {
node.error(error.message);
msg.error = error.message;
msg.payload = [];
return msg;
}

Tips

  • Always check msg.statusCode after history reads
  • Implement retry logic for transient failures
  • Provide fallback data when history is unavailable
  • Log errors for troubleshooting
  • Filter bad quality data points
  • Handle empty results gracefully
  • Validate inputs before sending requests