|
#include <Arduino.h> |
|
#include "mbedtls/md.h" |
|
#include <string> |
|
|
|
enum class RetryStrategy { |
|
EXPONENTIAL, |
|
LINEAR, |
|
FIXED |
|
}; |
|
|
|
struct WoREvent { |
|
String device_id; |
|
String event_type; |
|
unsigned long timestamp; |
|
String action; |
|
String status; |
|
unsigned long ack_timestamp; |
|
String ack_signature; |
|
int retry_count; |
|
|
|
WoREvent(const String& dev_id, const String& evt_type, const String& act) |
|
: device_id(dev_id), event_type(evt_type), action(act), |
|
status("pending"), ack_timestamp(0), retry_count(0) { |
|
timestamp = millis(); |
|
} |
|
}; |
|
|
|
struct AckPayload { |
|
String device_id; |
|
String event_type; |
|
unsigned long original_timestamp; |
|
unsigned long ack_timestamp; |
|
String status; |
|
String action_taken; |
|
String signature; |
|
int retry_count; |
|
String notes; |
|
|
|
String toJSON() const { |
|
String json = "{"; |
|
json += "\"device_id\":\"" + device_id + "\","; |
|
json += "\"event_type\":\"" + event_type + "\","; |
|
json += "\"original_timestamp\":" + String(original_timestamp) + ","; |
|
json += "\"ack_timestamp\":" + String(ack_timestamp) + ","; |
|
json += "\"status\":\"" + status + "\","; |
|
json += "\"action_taken\":\"" + action_taken + "\","; |
|
json += "\"signature\":\"" + signature + "\","; |
|
json += "\"retry_count\":" + String(retry_count) + ","; |
|
json += "\"notes\":\"" + notes + "\""; |
|
json += "}"; |
|
return json; |
|
} |
|
}; |
|
|
|
class WoRAcknowledgment { |
|
private: |
|
String sha256(const String& input) { |
|
byte hash[32]; |
|
mbedtls_md_context_t ctx; |
|
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256; |
|
|
|
mbedtls_md_init(&ctx); |
|
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0); |
|
mbedtls_md_starts(&ctx); |
|
mbedtls_md_update(&ctx, (const unsigned char*)input.c_str(), input.length()); |
|
mbedtls_md_finish(&ctx, hash); |
|
mbedtls_md_free(&ctx); |
|
|
|
String hash_str = ""; |
|
for(int i = 0; i < 32; i++) { |
|
char hex[3]; |
|
sprintf(hex, "%02x", hash[i]); |
|
hash_str += hex; |
|
} |
|
return hash_str; |
|
} |
|
|
|
unsigned long calculateDelay(int attempt, unsigned long base_delay, |
|
unsigned long max_delay, RetryStrategy strategy) { |
|
unsigned long delay; |
|
|
|
switch(strategy) { |
|
case RetryStrategy::EXPONENTIAL: |
|
delay = base_delay * (1 << attempt); // bit shift for power of 2 |
|
break; |
|
case RetryStrategy::LINEAR: |
|
delay = base_delay * (attempt + 1); |
|
break; |
|
case RetryStrategy::FIXED: |
|
default: |
|
delay = base_delay; |
|
break; |
|
} |
|
|
|
return min(delay, max_delay); |
|
} |
|
|
|
public: |
|
AckPayload acknowledgeWoREvent(WoREvent& event, const String& device_secret, |
|
bool success = true, const String& notes = "") { |
|
// Update event status |
|
event.status = success ? "completed" : "failed"; |
|
event.ack_timestamp = millis(); |
|
|
|
// Generate acknowledgment signature |
|
String ack_data = event.device_id + ":" + event.event_type + ":" + |
|
String(event.ack_timestamp) + ":" + event.status; |
|
String sig_input = ack_data + ":" + device_secret; |
|
event.ack_signature = sha256(sig_input); |
|
|
|
// Build acknowledgment payload |
|
AckPayload payload; |
|
payload.device_id = event.device_id; |
|
payload.event_type = event.event_type; |
|
payload.original_timestamp = event.timestamp; |
|
payload.ack_timestamp = event.ack_timestamp; |
|
payload.status = event.status; |
|
payload.action_taken = event.action; |
|
payload.signature = event.ack_signature; |
|
payload.retry_count = event.retry_count; |
|
payload.notes = notes; |
|
|
|
return payload; |
|
} |
|
|
|
bool sendAcknowledgmentWithRetry( |
|
WoREvent& event, |
|
const String& device_secret, |
|
bool (*send_function)(const AckPayload&), |
|
bool success = true, |
|
const String& notes = "", |
|
int max_retries = 3, |
|
unsigned long base_delay = 1000, // milliseconds |
|
unsigned long max_delay = 30000, |
|
RetryStrategy strategy = RetryStrategy::EXPONENTIAL |
|
) { |
|
// Generate initial acknowledgment payload |
|
AckPayload ack_payload = acknowledgeWoREvent(event, device_secret, success, notes); |
|
|
|
// Attempt to send with retries |
|
for(int attempt = 0; attempt <= max_retries; attempt++) { |
|
Serial.printf("Attempt %d/%d: Sending acknowledgment...\n", |
|
attempt + 1, max_retries + 1); |
|
|
|
bool send_result = false; |
|
|
|
// Try-catch equivalent for ESP32 |
|
if(send_function != nullptr) { |
|
send_result = send_function(ack_payload); |
|
} |
|
|
|
if(send_result) { |
|
Serial.printf("✓ Acknowledgment sent successfully on attempt %d\n", |
|
attempt + 1); |
|
return true; |
|
} |
|
|
|
// If send failed and we have retries left |
|
if(attempt < max_retries) { |
|
event.retry_count++; |
|
unsigned long delay = calculateDelay(attempt, base_delay, max_delay, strategy); |
|
Serial.printf("✗ Send failed. Retrying in %lu ms...\n", delay); |
|
|
|
delay(delay); |
|
|
|
// Regenerate payload with updated retry count |
|
ack_payload = acknowledgeWoREvent(event, device_secret, success, notes); |
|
} |
|
} |
|
|
|
Serial.printf("✗ Failed to send acknowledgment after %d attempts\n", |
|
max_retries + 1); |
|
return false; |
|
} |
|
}; |
|
|
|
// Global instance |
|
WoRAcknowledgment ack_handler; |
|
|
|
// Mock send function for testing (simulates LoRa/WiFi/BLE transmission) |
|
bool mockSendFunction(const AckPayload& payload) { |
|
static int call_count = 0; |
|
call_count++; |
|
|
|
Serial.println(" → Attempting to send payload:"); |
|
Serial.println(" " + payload.toJSON()); |
|
|
|
// Simulate 40% success rate (typically succeeds on 2nd or 3rd attempt) |
|
if(call_count >= 2 && (random(100) > 60)) { |
|
Serial.println(" → Mock send successful"); |
|
return true; |
|
} else { |
|
Serial.println(" → Mock send failed (simulated network error)"); |
|
return false; |
|
} |
|
} |
|
|
|
// Replace this with your actual send function (LoRa, WiFi, BLE, etc.) |
|
bool actualSendFunction(const AckPayload& payload) { |
|
// Example for LoRa: |
|
// LoRa.beginPacket(); |
|
// LoRa.print(payload.toJSON()); |
|
// return LoRa.endPacket() == 1; |
|
|
|
// Example for WiFi/HTTP: |
|
// HTTPClient http; |
|
// http.begin("http://your-server.com/ack"); |
|
// http.addHeader("Content-Type", "application/json"); |
|
// int httpCode = http.POST(payload.toJSON()); |
|
// http.end(); |
|
// return (httpCode == 200); |
|
|
|
// Example for BLE: |
|
// pCharacteristic->setValue(payload.toJSON().c_str()); |
|
// pCharacteristic->notify(); |
|
// return true; |
|
|
|
return mockSendFunction(payload); |
|
} |
|
|
|
void setup() { |
|
Serial.begin(115200); |
|
delay(1000); |
|
|
|
Serial.println("\n=== ESP32-S3 Wake-on-Radio Acknowledgment Test ===\n"); |
|
|
|
// Create a WoR event |
|
WoREvent wor_event("VALVE_001", "valve_control", "close_valve"); |
|
|
|
Serial.printf("Received WoR event: %s for device %s\n\n", |
|
wor_event.action.c_str(), wor_event.device_id.c_str()); |
|
|
|
// Simulate performing the action (e.g., closing valve) |
|
// digitalWrite(VALVE_PIN, HIGH); |
|
// delay(500); |
|
// ... actual hardware control code here ... |
|
|
|
String device_secret = "your_device_secret_key"; |
|
|
|
// Send acknowledgment with retry logic |
|
bool result = ack_handler.sendAcknowledgmentWithRetry( |
|
wor_event, |
|
device_secret, |
|
actualSendFunction, |
|
true, // success |
|
"Valve successfully closed at 50% position", |
|
3, // max_retries |
|
1000, // base_delay (1 second) |
|
30000, // max_delay (30 seconds) |
|
RetryStrategy::EXPONENTIAL |
|
); |
|
|
|
Serial.println("\n=================================================="); |
|
Serial.printf("Final result: %s\n", result ? "SUCCESS" : "FAILED"); |
|
Serial.printf("Total retry attempts: %d\n", wor_event.retry_count); |
|
Serial.println("==================================================\n"); |
|
} |
|
|
|
void loop() { |
|
// Your main loop code here |
|
delay(10000); |
|
} |