Error Handling
Error Handling
Section titled “Error Handling”Comprehensive error handling and recovery strategies for robust device-side API implementation.
HTTP Status Codes
Section titled “HTTP Status Codes”Standard HTTP status codes and recommended device actions:
| Code | Status | Meaning | Device Action |
|---|---|---|---|
| 200 | OK | Request processed successfully | Continue normal operation |
| 202 | Accepted | Request queued for processing | Monitor for completion |
| 400 | Bad Request | Invalid request format | Log error, don’t retry |
| 401 | Unauthorized | Authentication failed | Refresh token, retry once |
| 403 | Forbidden | Access denied | Check permissions, log error |
| 404 | Not Found | Endpoint not found | Check API version, don’t retry |
| 409 | Conflict | Resource conflict | Resolve conflict, retry |
| 413 | Payload Too Large | Request too large | Split request, retry |
| 429 | Rate Limited | Too many requests | Backoff and retry |
| 500 | Server Error | Temporary server issue | Exponential backoff retry |
| 502 | Bad Gateway | Gateway error | Exponential backoff retry |
| 503 | Service Unavailable | Server maintenance | Queue locally, retry later |
| 504 | Gateway Timeout | Request timeout | Retry with backoff |
Retry Logic
Section titled “Retry Logic”Basic Retry Policy
Section titled “Basic Retry Policy”struct RetryPolicy { int max_attempts = 3; unsigned long initial_delay = 1000; // 1 second float backoff_multiplier = 2.0; unsigned long max_delay = 60000; // 60 seconds std::vector<int> retry_codes = {429, 500, 502, 503, 504};};
class RetryManager {private: RetryPolicy policy;
public: bool shouldRetry(int httpCode, int attempt) { if (attempt >= policy.max_attempts) { return false; }
return std::find(policy.retry_codes.begin(), policy.retry_codes.end(), httpCode) != policy.retry_codes.end(); }
unsigned long getDelayMs(int attempt) { unsigned long delay = policy.initial_delay * pow(policy.backoff_multiplier, attempt); return min(delay, policy.max_delay); }};Exponential Backoff Implementation
Section titled “Exponential Backoff Implementation”class ExponentialBackoff {private: unsigned long lastAttempt = 0; int currentAttempt = 0; RetryManager retryManager;
public: bool canRetry(int httpCode) { if (!retryManager.shouldRetry(httpCode, currentAttempt)) { reset(); return false; }
unsigned long delay = retryManager.getDelayMs(currentAttempt);
if (millis() - lastAttempt < delay) { return false; // Still in backoff period }
currentAttempt++; lastAttempt = millis(); return true; }
void reset() { currentAttempt = 0; lastAttempt = 0; }
void recordSuccess() { reset(); }};Network Error Handling
Section titled “Network Error Handling”Connection Management
Section titled “Connection Management”class ConnectionManager {private: bool networkAvailable = false; unsigned long lastConnectivityCheck = 0; int consecutiveFailures = 0;
public: bool isNetworkAvailable() { // Check connectivity every 30 seconds if (millis() - lastConnectivityCheck > 30000) { checkConnectivity(); lastConnectivityCheck = millis(); }
return networkAvailable; }
void recordConnectionFailure() { consecutiveFailures++;
// If too many failures, mark network as unavailable if (consecutiveFailures >= 5) { networkAvailable = false; triggerConnectivityCheck(); } }
void recordConnectionSuccess() { consecutiveFailures = 0; networkAvailable = true; }
private: void checkConnectivity() { // Test with multiple approaches bool wifiConnected = (WiFi.status() == WL_CONNECTED); bool dnsWorks = testDNSResolution(); bool internetAccess = testInternetConnectivity();
networkAvailable = wifiConnected && dnsWorks && internetAccess;
if (!networkAvailable) { attemptNetworkRecovery(); } }
bool testDNSResolution() { IPAddress ip; return WiFi.hostByName("google.com", ip); }
bool testInternetConnectivity() { HTTPClient http; http.begin("http://httpbin.org/status/200"); http.setTimeout(5000);
int httpCode = http.GET(); http.end();
return (httpCode == 200); }
void attemptNetworkRecovery() { Serial.println("Attempting network recovery...");
// Try WiFi reconnection if (WiFi.status() != WL_CONNECTED) { WiFi.disconnect(); delay(1000); WiFi.reconnect();
// Wait up to 10 seconds for connection int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 20) { delay(500); attempts++; } } }};Offline Queue Management
Section titled “Offline Queue Management”class OfflineQueue {private: struct QueuedRequest { String endpoint; String method; String payload; unsigned long timestamp; int priority; // 1=high, 2=medium, 3=low int attempts; };
std::vector<QueuedRequest> queue; const size_t MAX_QUEUE_SIZE = 1000; const size_t MAX_MEMORY_MB = 10;
public: void queueRequest(String endpoint, String method, String payload, int priority = 2) { // Check memory usage if (getQueueMemoryUsage() > MAX_MEMORY_MB * 1024 * 1024) { purgeOldestLowPriority(); }
// Check queue size if (queue.size() >= MAX_QUEUE_SIZE) { purgeOldestLowPriority(); }
QueuedRequest request; request.endpoint = endpoint; request.method = method; request.payload = payload; request.timestamp = millis(); request.priority = priority; request.attempts = 0;
// Insert based on priority insertByPriority(request);
Serial.printf("Queued request: %s %s (priority %d)\n", method.c_str(), endpoint.c_str(), priority); }
void processQueue(ConnectionManager& connMgr) { if (!connMgr.isNetworkAvailable()) { return; }
// Process up to 10 requests per call to avoid blocking int processed = 0;
for (auto it = queue.begin(); it != queue.end() && processed < 10;) { QueuedRequest& request = *it;
if (sendQueuedRequest(request)) { // Success - remove from queue Serial.printf("Successfully sent queued request: %s %s\n", request.method.c_str(), request.endpoint.c_str()); it = queue.erase(it); processed++; } else { // Failed - increment attempts request.attempts++;
// Remove if too many attempts if (request.attempts >= 5) { Serial.printf("Dropping request after 5 attempts: %s %s\n", request.method.c_str(), request.endpoint.c_str()); it = queue.erase(it); } else { ++it; } } }
if (processed > 0) { Serial.printf("Processed %d queued requests\n", processed); } }
size_t getQueueSize() { return queue.size(); }
float getQueueMemoryUsage() { size_t totalSize = 0; for (const auto& request : queue) { totalSize += request.payload.length(); } return totalSize; }
private: void insertByPriority(const QueuedRequest& request) { auto pos = std::upper_bound(queue.begin(), queue.end(), request, [](const QueuedRequest& a, const QueuedRequest& b) { return a.priority < b.priority; }); queue.insert(pos, request); }
void purgeOldestLowPriority() { // Remove oldest low priority items for (auto it = queue.begin(); it != queue.end();) { if (it->priority == 3) { // Low priority it = queue.erase(it); } else { ++it; } }
// If still full, remove oldest medium priority if (queue.size() >= MAX_QUEUE_SIZE) { for (auto it = queue.begin(); it != queue.end();) { if (it->priority == 2) { // Medium priority it = queue.erase(it); break; // Only remove one at a time } else { ++it; } } } }
bool sendQueuedRequest(const QueuedRequest& request) { HTTPClient https; https.begin(API_BASE_URL + request.endpoint); https.addHeader("Content-Type", "application/json"); https.addHeader("Authorization", "Bearer " + deviceToken); https.setTimeout(10000); // Longer timeout for queued requests
int httpCode; if (request.method == "POST") { httpCode = https.POST(request.payload); } else if (request.method == "PUT") { httpCode = https.PUT(request.payload); } else if (request.method == "GET") { httpCode = https.GET(); } else { return false; // Unsupported method }
https.end();
return (httpCode >= 200 && httpCode < 300); }};Authentication Error Handling
Section titled “Authentication Error Handling”Token Management
Section titled “Token Management”class TokenManager {private: String currentToken; unsigned long tokenExpiry = 0; bool refreshInProgress = false; int refreshAttempts = 0;
public: bool isTokenValid() { // Check if token exists and hasn't expired return !currentToken.isEmpty() && millis() < tokenExpiry - 300000; // 5 min buffer }
bool handleAuthError(int httpCode) { if (httpCode == 401 && !refreshInProgress) { return refreshToken(); } return false; }
String getToken() { if (!isTokenValid()) { refreshToken(); } return currentToken; }
private: bool refreshToken() { if (refreshInProgress) { return false; }
refreshInProgress = true; refreshAttempts++;
// Too many refresh attempts - need re-registration if (refreshAttempts > 5) { Serial.println("Too many token refresh attempts - re-registration required"); triggerReRegistration(); refreshInProgress = false; return false; }
// Attempt token refresh HTTPClient https; https.begin(API_BASE_URL + "/auth/refresh"); https.addHeader("Content-Type", "application/json"); https.addHeader("Authorization", "Bearer " + currentToken);
StaticJsonDocument<128> doc; doc["device_id"] = getDeviceId();
String payload; serializeJson(doc, payload);
int httpCode = https.POST(payload);
if (httpCode == 200) { String response = https.getString();
StaticJsonDocument<256> responseDoc; deserializeJson(responseDoc, response);
currentToken = responseDoc["device_token"].as<String>(); tokenExpiry = millis() + (24 * 60 * 60 * 1000); // 24 hours
// Save token to secure storage saveTokenToStorage();
refreshAttempts = 0; refreshInProgress = false;
Serial.println("Token refreshed successfully"); return true; } else { Serial.printf("Token refresh failed: HTTP %d\n", httpCode); refreshInProgress = false; return false; }
https.end(); }
void triggerReRegistration() { // Clear stored credentials clearStoredCredentials();
// Set flag for re-registration setReRegistrationRequired(true);
// Could also queue a re-registration attempt }};Data Integrity Error Handling
Section titled “Data Integrity Error Handling”Checksum Validation
Section titled “Checksum Validation”class DataIntegrityManager {public: struct ValidationResult { bool valid; String error; String correctedData; };
ValidationResult validatePayload(const String& payload) { ValidationResult result; result.valid = true;
// Parse JSON to check structure StaticJsonDocument<1024> doc; DeserializationError error = deserializeJson(doc, payload);
if (error) { result.valid = false; result.error = "Invalid JSON: " + String(error.c_str()); return result; }
// Validate required fields if (!doc.containsKey("device_id")) { result.valid = false; result.error = "Missing device_id field"; return result; }
if (!doc.containsKey("timestamp")) { result.valid = false; result.error = "Missing timestamp field"; return result; }
// Validate timestamp format String timestamp = doc["timestamp"]; if (!isValidISO8601(timestamp)) { result.valid = false; result.error = "Invalid timestamp format";
// Try to correct timestamp String correctedTimestamp = getCurrentISO8601(); doc["timestamp"] = correctedTimestamp; serializeJson(doc, result.correctedData); }
// Validate data ranges for BCG data if (doc.containsKey("bcg_data")) { JsonObject bcg = doc["bcg_data"];
if (bcg.containsKey("pulse_rate")) { int pulse = bcg["pulse_rate"]; if (pulse < 30 || pulse > 200) { result.valid = false; result.error = "Pulse rate out of range: " + String(pulse); } }
if (bcg.containsKey("respiratory_rate")) { int breathing = bcg["respiratory_rate"]; if (breathing < 5 || breathing > 40) { result.valid = false; result.error = "Respiratory rate out of range: " + String(breathing); } } }
return result; }
String calculatePayloadChecksum(const String& payload) { // Calculate SHA-256 checksum mbedtls_sha256_context ctx; mbedtls_sha256_init(&ctx); mbedtls_sha256_starts(&ctx, 0); mbedtls_sha256_update(&ctx, (const unsigned char*)payload.c_str(), payload.length());
unsigned char hash[32]; mbedtls_sha256_finish(&ctx, hash); mbedtls_sha256_free(&ctx);
// Convert to hex string String checksum; for (int i = 0; i < 32; i++) { if (hash[i] < 16) checksum += "0"; checksum += String(hash[i], HEX); }
return checksum; }};Comprehensive Error Handler
Section titled “Comprehensive Error Handler”class APIErrorHandler {private: RetryManager retryManager; ConnectionManager connectionManager; TokenManager tokenManager; OfflineQueue offlineQueue; DataIntegrityManager integrityManager;
public: struct APIResponse { bool success; int httpCode; String response; String error; bool shouldRetry; };
APIResponse sendRequest(String endpoint, String method, String payload, int priority = 2) { APIResponse result;
// Validate payload integrity auto validation = integrityManager.validatePayload(payload); if (!validation.valid) { result.success = false; result.error = "Payload validation failed: " + validation.error;
// Use corrected data if available if (validation.correctedData.length() > 0) { payload = validation.correctedData; Serial.println("Using corrected payload"); } else { result.shouldRetry = false; return result; } }
// Check network availability if (!connectionManager.isNetworkAvailable()) { Serial.println("Network unavailable - queuing request"); offlineQueue.queueRequest(endpoint, method, payload, priority); result.success = false; result.error = "Network unavailable - queued for later"; result.shouldRetry = false; return result; }
// Check token validity if (!tokenManager.isTokenValid()) { Serial.println("Token invalid - attempting refresh"); if (!tokenManager.refreshToken()) { result.success = false; result.error = "Authentication failed"; result.shouldRetry = false; return result; } }
// Attempt to send request ExponentialBackoff backoff;
do { result = attemptRequest(endpoint, method, payload);
if (result.success) { backoff.recordSuccess(); connectionManager.recordConnectionSuccess(); return result; }
// Handle specific error types if (result.httpCode == 401) { if (tokenManager.handleAuthError(result.httpCode)) { continue; // Retry with new token } }
if (result.httpCode == 413) { // Payload too large - try to split if (trySplitPayload(endpoint, method, payload, priority)) { result.success = true; return result; } }
// Record connection issues if (result.httpCode == 0 || result.httpCode >= 500) { connectionManager.recordConnectionFailure(); }
// Check if we should retry if (!backoff.canRetry(result.httpCode)) { break; }
} while (true);
// All retries failed - queue for offline processing if appropriate if (shouldQueueOffline(result.httpCode)) { offlineQueue.queueRequest(endpoint, method, payload, priority); result.error += " - queued for retry"; }
return result; }
void processOfflineQueue() { offlineQueue.processQueue(connectionManager); }
private: APIResponse attemptRequest(String endpoint, String method, String payload) { APIResponse result;
HTTPClient https; https.begin(API_BASE_URL + endpoint); https.addHeader("Content-Type", "application/json"); https.addHeader("Authorization", "Bearer " + tokenManager.getToken()); https.setTimeout(15000);
if (method == "POST") { result.httpCode = https.POST(payload); } else if (method == "PUT") { result.httpCode = https.PUT(payload); } else if (method == "GET") { result.httpCode = https.GET(); } else { result.success = false; result.error = "Unsupported method: " + method; return result; }
result.response = https.getString(); result.success = (result.httpCode >= 200 && result.httpCode < 300);
if (!result.success) { result.error = "HTTP " + String(result.httpCode) + ": " + result.response; }
https.end(); return result; }
bool shouldQueueOffline(int httpCode) { // Queue for temporary server errors, rate limiting, and network issues return (httpCode == 0 || httpCode == 429 || httpCode >= 500); }
bool trySplitPayload(String endpoint, String method, String payload, int priority) { // Implementation for splitting large payloads // This would be specific to the payload type return false; // Simplified for example }};Best Practices Summary
Section titled “Best Practices Summary”- Always Validate: Check data integrity before sending
- Implement Backoff: Use exponential backoff for retries
- Queue Offline: Store failed requests for later transmission
- Monitor Connectivity: Continuously check network status
- Handle Authentication: Automatically refresh expired tokens
- Log Everything: Comprehensive logging for debugging
- Graceful Degradation: Continue operation when possible
- Resource Management: Limit queue size and memory usage
- Priority Handling: Process critical data first
- Recovery Mechanisms: Implement automatic recovery procedures