#include <Arduino.h>
#include <ArduinoJson.h>
// =================================
// ENHANCED SIGNALSLOT CLASS (FIXED)
// =================================
class AdvancedSignalSlot {
private:
struct Signal {
String name;
void (*slots[10])();
int slotCount;
// NEW: Priority levels for slots
int priorities[10];
// NEW: Timing constraints
unsigned long minInterval; // Minimum time between emissions
unsigned long lastEmitted;
// NEW: Condition checks
bool (*condition)(); // Function that must return true to emit
// NEW: Statistics
unsigned long emitCount;
unsigned long totalExecutionTime;
};
struct ParameterizedSlot {
String signalName;
void (*callback)(const String&); // Slot that accepts parameters
bool active;
};
struct ScheduledEmission {
String signalName;
String parameters; // For parameterized scheduled emissions
unsigned long executeAt;
bool repeat;
unsigned long interval;
bool active;
};
Signal signals[20];
int signalCount;
ParameterizedSlot paramSlots[15];
int paramSlotCount;
ScheduledEmission scheduledEmissions[10];
int scheduledCount;
// NEW: Signal chaining
struct SignalChain {
String trigger;
String chainedSignals[5];
int chainCount;
unsigned long delays[5]; // Delay before emitting each chained signal
};
SignalChain chains[10];
int chainCount;
// NEW: Event callback with priority and filtering
void (*onEvent)(const String&, int priority);
// NEW: Performance monitoring
bool performanceMonitoring;
unsigned long totalSignals;
unsigned long totalExecutionTime;
// Wildcard slots (similar to original implementation)
void (*wildcardSlots[10])();
int wildcardSlotCount;
public:
AdvancedSignalSlot() : signalCount(0), paramSlotCount(0), scheduledCount(0),
chainCount(0), onEvent(nullptr),
performanceMonitoring(false), totalSignals(0), totalExecutionTime(0),
wildcardSlotCount(0), recordingIndex(0), isRecording(false), recordingStartTime(0) {}
// =================================
// BASIC FUNCTIONALITY (COMPATIBLE WITH ORIGINAL)
// =================================
// Set the user-defined callback for event handling
void setEventCallback(void (*callback)(const String&, int priority = 1)) {
onEvent = callback;
}
// Basic connect (maintains compatibility)
bool connect(const String& signalName, void (*slot)()) {
return connectWithPriority(signalName, slot, 5); // Default priority 5
}
// Basic emit (maintains compatibility)
void emit(const String& signalName) {
emitSignal(signalName, "");
}
// =================================
// 1. PRIORITY-BASED SLOT EXECUTION
// =================================
bool connectWithPriority(const String& signalName, void (*slot)(), int priority = 5) {
// Handle wildcard connections
if (signalName == "wildcard") {
if (wildcardSlotCount < 10) {
wildcardSlots[wildcardSlotCount++] = slot;
return true;
} else {
emitEvent("Error: Maximum wildcard slots reached.", 2);
return false;
}
}
for (int i = 0; i < signalCount; i++) {
if (signals[i].name == signalName) {
if (signals[i].slotCount < 10) {
// Insert slot in priority order (higher priority first)
int insertPos = signals[i].slotCount;
for (int j = 0; j < signals[i].slotCount; j++) {
if (priority > signals[i].priorities[j]) {
insertPos = j;
break;
}
}
// Shift existing slots down
for (int j = signals[i].slotCount; j > insertPos; j--) {
signals[i].slots[j] = signals[i].slots[j-1];
signals[i].priorities[j] = signals[i].priorities[j-1];
}
// Insert new slot
signals[i].slots[insertPos] = slot;
signals[i].priorities[insertPos] = priority;
signals[i].slotCount++;
return true;
}
emitEvent("Error: Maximum slots reached for signal " + signalName, 2);
return false;
}
}
// Create new signal
if (signalCount < 20) {
signals[signalCount].name = signalName;
signals[signalCount].slots[0] = slot;
signals[signalCount].priorities[0] = priority;
signals[signalCount].slotCount = 1;
signals[signalCount].minInterval = 0;
signals[signalCount].lastEmitted = 0;
signals[signalCount].condition = nullptr;
signals[signalCount].emitCount = 0;
signals[signalCount].totalExecutionTime = 0;
signalCount++;
return true;
}
emitEvent("Error: Maximum signals reached.", 2);
return false;
}
// =================================
// 2. PARAMETERIZED SIGNALS & SLOTS
// =================================
bool connectParameterized(const String& signalName, void (*slot)(const String&)) {
if (paramSlotCount < 15) {
paramSlots[paramSlotCount].signalName = signalName;
paramSlots[paramSlotCount].callback = slot;
paramSlots[paramSlotCount].active = true;
paramSlotCount++;
return true;
}
emitEvent("Error: Maximum parameterized slots reached.", 2);
return false;
}
void emitWithParameters(const String& signalName, const String& parameters) {
emitSignal(signalName, parameters);
}
// =================================
// 3. TIMING CONSTRAINTS & RATE LIMITING
// =================================
void setSignalConstraints(const String& signalName, unsigned long minInterval, bool (*condition)() = nullptr) {
for (int i = 0; i < signalCount; i++) {
if (signals[i].name == signalName) {
signals[i].minInterval = minInterval;
signals[i].condition = condition;
return;
}
}
emitEvent("Warning: Signal '" + signalName + "' not found for constraints.", 1);
}
// =================================
// 4. SCHEDULED EMISSIONS
// =================================
bool scheduleEmission(const String& signalName, unsigned long delayMs, bool repeat = false, unsigned long intervalMs = 0, const String& parameters = "") {
if (scheduledCount < 10) {
scheduledEmissions[scheduledCount].signalName = signalName;
scheduledEmissions[scheduledCount].parameters = parameters;
scheduledEmissions[scheduledCount].executeAt = millis() + delayMs;
scheduledEmissions[scheduledCount].repeat = repeat;
scheduledEmissions[scheduledCount].interval = intervalMs;
scheduledEmissions[scheduledCount].active = true;
scheduledCount++;
return true;
}
emitEvent("Error: Maximum scheduled emissions reached.", 2);
return false;
}
void processScheduledEmissions() {
unsigned long now = millis();
for (int i = 0; i < scheduledCount; i++) {
if (scheduledEmissions[i].active && now >= scheduledEmissions[i].executeAt) {
if (scheduledEmissions[i].parameters.length() > 0) {
emitWithParameters(scheduledEmissions[i].signalName, scheduledEmissions[i].parameters);
} else {
emit(scheduledEmissions[i].signalName);
}
if (scheduledEmissions[i].repeat && scheduledEmissions[i].interval > 0) {
scheduledEmissions[i].executeAt = now + scheduledEmissions[i].interval;
} else {
scheduledEmissions[i].active = false;
}
}
}
}
// Record signal for playback (internal method)
void recordSignal(const String& signalName, const String& parameters = "") {
if (isRecording && recordingIndex < 50) {
recordingBuffer[recordingIndex].signalName = signalName;
recordingBuffer[recordingIndex].parameters = parameters;
recordingBuffer[recordingIndex].timestamp = millis();
recordingBuffer[recordingIndex].relativeTime = millis() - recordingStartTime;
recordingIndex++;
}
}
// =================================
// 5. SIGNAL CHAINING
// =================================
bool addSignalChain(const String& trigger, const String chainedSignals[], const unsigned long delays[], int count) {
if (chainCount < 10 && count <= 5) {
chains[chainCount].trigger = trigger;
chains[chainCount].chainCount = count;
for (int i = 0; i < count; i++) {
chains[chainCount].chainedSignals[i] = chainedSignals[i];
chains[chainCount].delays[i] = delays[i];
}
chainCount++;
return true;
}
emitEvent("Error: Cannot add signal chain.", 2);
return false;
}
// =================================
// 6. PERFORMANCE MONITORING & ANALYTICS
// =================================
void enablePerformanceMonitoring(bool enable = true) {
performanceMonitoring = enable;
if (enable) {
emitEvent("Performance monitoring enabled.", 0);
}
}
void printPerformanceStats() {
Serial.println("\n๐ ========== PERFORMANCE STATISTICS ==========");
Serial.println("Total signals emitted: " + String(totalSignals));
Serial.println("Total execution time: " + String(totalExecutionTime) + " microseconds");
if (totalSignals > 0) {
Serial.println("Average execution time: " + String(totalExecutionTime / totalSignals) + " ยตs per signal");
}
Serial.println("\n๐ก Signal-specific stats:");
for (int i = 0; i < signalCount; i++) {
Serial.println(" " + signals[i].name + ":");
Serial.println(" Emitted: " + String(signals[i].emitCount) + " times");
Serial.println(" Slots: " + String(signals[i].slotCount));
if (signals[i].emitCount > 0) {
Serial.println(" Avg execution: " + String(signals[i].totalExecutionTime / signals[i].emitCount) + " ยตs");
}
}
Serial.println("================================================\n");
}
// =================================
// 7. DEBUGGING & INTROSPECTION
// =================================
void printSystemState() {
Serial.println("\n๐ ========== SYSTEM STATE ==========");
Serial.println("Registered signals: " + String(signalCount));
Serial.println("Parameterized slots: " + String(paramSlotCount));
Serial.println("Scheduled emissions: " + String(scheduledCount));
Serial.println("Signal chains: " + String(chainCount));
Serial.println("Wildcard slots: " + String(wildcardSlotCount));
Serial.println("\n๐ก Active signals:");
for (int i = 0; i < signalCount; i++) {
Serial.println(" " + signals[i].name + " (" + String(signals[i].slotCount) + " slots)");
for (int j = 0; j < signals[i].slotCount; j++) {
Serial.println(" Priority " + String(signals[i].priorities[j]));
}
}
Serial.println("=====================================\n");
}
private:
// =================================
// INTERNAL HELPER METHODS
// =================================
void emitSignal(const String& signalName, const String& parameters) {
bool signalFound = false;
// Find and execute regular signal slots
for (int i = 0; i < signalCount; i++) {
if (signals[i].name == signalName) {
signalFound = true;
unsigned long now = millis();
// Check timing constraints
if (signals[i].minInterval > 0 &&
(now - signals[i].lastEmitted) < signals[i].minInterval) {
emitEvent("Rate limited: " + signalName, 1);
return;
}
// Check condition constraints
if (signals[i].condition && !signals[i].condition()) {
emitEvent("Condition failed: " + signalName, 1);
return;
}
unsigned long startTime = performanceMonitoring ? micros() : 0;
// Execute slots in priority order
for (int j = 0; j < signals[i].slotCount; j++) {
signals[i].slots[j]();
}
signals[i].lastEmitted = now;
signals[i].emitCount++;
if (performanceMonitoring) {
unsigned long execTime = micros() - startTime;
signals[i].totalExecutionTime += execTime;
totalExecutionTime += execTime;
totalSignals++;
}
break;
}
}
// Execute parameterized slots
for (int i = 0; i < paramSlotCount; i++) {
if (paramSlots[i].signalName == signalName && paramSlots[i].active) {
paramSlots[i].callback(parameters);
}
}
// Call all wildcard slots
for (int i = 0; i < wildcardSlotCount; i++) {
wildcardSlots[i]();
}
// Record the signal if recording is active
recordSignal(signalName, parameters);
// Trigger chained signals
triggerChainedSignals(signalName);
// Emit event if the signal was not found and no wildcards
if (!signalFound && wildcardSlotCount == 0) {
emitEvent("Warning: Signal '" + signalName + "' not found.", 1);
}
}
void triggerChainedSignals(const String& triggerSignal) {
for (int i = 0; i < chainCount; i++) {
if (chains[i].trigger == triggerSignal) {
for (int j = 0; j < chains[i].chainCount; j++) {
if (chains[i].delays[j] == 0) {
emit(chains[i].chainedSignals[j]);
} else {
scheduleEmission(chains[i].chainedSignals[j], chains[i].delays[j]);
}
}
}
}
}
// Helper to call the event callback
void emitEvent(const String& message, int priority = 1) {
if (onEvent != nullptr) {
onEvent(message, priority);
}
}
public:
// =================================
// 8. SIGNAL RECORDING & PLAYBACK
// =================================
struct SignalRecord {
String signalName;
String parameters;
unsigned long timestamp;
unsigned long relativeTime; // Time relative to recording start
};
private:
SignalRecord recordingBuffer[50];
int recordingIndex;
bool isRecording;
unsigned long recordingStartTime;
public:
void startRecording() {
isRecording = true;
recordingIndex = 0;
recordingStartTime = millis();
Serial.println("๐น Signal recording started at " + String(recordingStartTime));
emitEvent("Signal recording started", 0);
}
void stopRecording() {
isRecording = false;
Serial.println("โน๏ธ Signal recording stopped. Recorded " + String(recordingIndex) + " signals");
emitEvent("Signal recording stopped. Count: " + String(recordingIndex), 0);
// Print recording summary
if (recordingIndex > 0) {
Serial.println("๐ Recording Summary:");
for (int i = 0; i < recordingIndex; i++) {
Serial.println(" " + String(i+1) + ". " + recordingBuffer[i].signalName +
" (+" + String(recordingBuffer[i].relativeTime) + "ms)" +
(recordingBuffer[i].parameters.length() > 0 ?
" [" + recordingBuffer[i].parameters + "]" : ""));
}
}
}
void playbackRecording(float speedMultiplier = 1.0) {
if (recordingIndex == 0) {
Serial.println("โ No recording to playback");
emitEvent("No recording available for playback", 1);
return;
}
Serial.println("โถ๏ธ Playing back " + String(recordingIndex) + " recorded signals at " + String(speedMultiplier) + "x speed...");
emitEvent("Starting playback at " + String(speedMultiplier) + "x speed", 0);
unsigned long playbackStartTime = millis();
for (int i = 0; i < recordingIndex; i++) {
// Calculate when this signal should be played based on original timing
unsigned long targetTime = playbackStartTime + (recordingBuffer[i].relativeTime / speedMultiplier);
// Wait until it's time to emit this signal
while (millis() < targetTime) {
delay(1); // Small delay to prevent busy waiting
}
Serial.println("๐ก Playback [" + String(i+1) + "/" + String(recordingIndex) + "]: " +
recordingBuffer[i].signalName);
// Emit the recorded signal
if (recordingBuffer[i].parameters.length() > 0) {
// Temporarily disable recording during playback to avoid recursive recording
bool wasRecording = isRecording;
isRecording = false;
emitWithParameters(recordingBuffer[i].signalName, recordingBuffer[i].parameters);
isRecording = wasRecording;
} else {
bool wasRecording = isRecording;
isRecording = false;
emit(recordingBuffer[i].signalName);
isRecording = wasRecording;
}
}
Serial.println("โ
Playback completed in " + String(millis() - playbackStartTime) + "ms");
emitEvent("Playback completed", 0);
}
void clearRecording() {
recordingIndex = 0;
isRecording = false;
Serial.println("๐๏ธ Recording buffer cleared");
emitEvent("Recording buffer cleared", 0);
}
int getRecordingCount() {
return recordingIndex;
}
bool isCurrentlyRecording() {
return isRecording;
}
// Export recording as JSON string
String exportRecording() {
if (recordingIndex == 0) {
return "[]";
}
String json = "[";
for (int i = 0; i < recordingIndex; i++) {
json += "{\"signal\":\"" + recordingBuffer[i].signalName + "\"";
json += ",\"timestamp\":" + String(recordingBuffer[i].timestamp);
json += ",\"relativeTime\":" + String(recordingBuffer[i].relativeTime);
if (recordingBuffer[i].parameters.length() > 0) {
json += ",\"parameters\":\"" + recordingBuffer[i].parameters + "\"";
}
json += "}";
if (i < recordingIndex - 1) json += ",";
}
json += "]";
return json;
}
private:
// =================================
// DISCONNECT METHODS (for compatibility)
// =================================
bool disconnect(const String& signalName) {
// Handle wildcard disconnection
if (signalName == "wildcard") {
wildcardSlotCount = 0;
return true;
}
for (int i = 0; i < signalCount; i++) {
if (signals[i].name == signalName) {
signals[i].slotCount = 0; // Clear all slots
return true;
}
}
emitEvent("Error: Signal '" + signalName + "' not found for disconnect.", 2);
return false;
}
bool disconnectSlot(const String& signalName, void (*slot)()) {
// Handle wildcard slot disconnection
if (signalName == "wildcard") {
for (int i = 0; i < wildcardSlotCount; i++) {
if (wildcardSlots[i] == slot) {
// Remove the slot by shifting others left
for (int j = i; j < wildcardSlotCount - 1; j++) {
wildcardSlots[j] = wildcardSlots[j + 1];
}
wildcardSlotCount--;
return true;
}
}
emitEvent("Error: Slot not found in wildcard signal.", 2);
return false;
}
for (int i = 0; i < signalCount; i++) {
if (signals[i].name == signalName) {
for (int j = 0; j < signals[i].slotCount; j++) {
if (signals[i].slots[j] == slot) {
// Remove the slot by shifting others left
for (int k = j; k < signals[i].slotCount - 1; k++) {
signals[i].slots[k] = signals[i].slots[k + 1];
signals[i].priorities[k] = signals[i].priorities[k + 1];
}
signals[i].slotCount--;
return true;
}
}
emitEvent("Error: Slot not found in signal '" + signalName + "'.", 2);
return false;
}
}
emitEvent("Error: Signal '" + signalName + "' not found.", 2);
return false;
}
};
// =================================
// EXAMPLE USAGE OF ADVANCED FEATURES
// =================================
AdvancedSignalSlot advancedSystem;
// Example slots with different priorities
void criticalSecurityResponse() {
Serial.println("๐จ CRITICAL: Security response (Priority 10)");
}
void normalSecurityResponse() {
Serial.println("๐ NORMAL: Security logging (Priority 5)");
}
void lowPriorityCleanup() {
Serial.println("๐งน LOW: Cleanup tasks (Priority 1)");
}
// Parameterized slot example
void temperatureAlert(const String& data) {
Serial.println("๐ก๏ธ Temperature alert with data: " + data);
// Parse data: could be JSON like {"temp": 35, "location": "bedroom", "trend": "rising"}
}
// Condition function example
bool isBusinessHours() {
int hour = (millis() / 3600000) % 24; // Simplified hour calculation
return (hour >= 9 && hour <= 17);
}
// Wildcard slot (responds to all signals)
void logAllEvents() {
Serial.println("๐ LOGGER: Event logged");
}
// Event handler for system messages
void systemEventHandler(const String& message, int priority) {
String priorityStr = "";
switch(priority) {
case 0: priorityStr = "INFO"; break;
case 1: priorityStr = "WARNING"; break;
case 2: priorityStr = "ERROR"; break;
default: priorityStr = "UNKNOWN"; break;
}
Serial.println("๐ง SYSTEM [" + priorityStr + "]: " + message);
}
// Example setup function
void setup() {
Serial.begin(115200);
Serial.println("๐ ========== ENHANCED SIGNALSLOT DEMO ==========");
// Set up event callback
advancedSystem.setEventCallback(systemEventHandler);
// 1. Priority-based connections
advancedSystem.connectWithPriority("security_breach", criticalSecurityResponse, 10);
advancedSystem.connectWithPriority("security_breach", normalSecurityResponse, 5);
advancedSystem.connectWithPriority("security_breach", lowPriorityCleanup, 1);
// 2. Basic connections (for compatibility)
advancedSystem.connect("temperature_normal", []() {
Serial.println("๐ก๏ธ Temperature is normal");
});
// 3. Parameterized slots
advancedSystem.connectParameterized("temperature_change", temperatureAlert);
// 4. Wildcard connection
advancedSystem.connect("wildcard", logAllEvents);
// 5. Timing constraints
advancedSystem.setSignalConstraints("security_breach", 5000); // Max once per 5 seconds
advancedSystem.setSignalConstraints("business_alert", 0, isBusinessHours); // Only during business hours
// 6. Signal chaining
String chainedSignals[] = {"lights_on", "camera_start", "notification_send"};
unsigned long delays[] = {0, 1000, 2000}; // Immediate, 1s delay, 2s delay
advancedSystem.addSignalChain("motion_detected", chainedSignals, delays, 3);
// 7. Scheduled emissions
advancedSystem.scheduleEmission("system_heartbeat", 5000, true, 10000); // Every 10s after 5s delay
// 8. Enable performance monitoring
advancedSystem.enablePerformanceMonitoring(true);
Serial.println("โ
Advanced features configured!");
advancedSystem.printSystemState();
}
void loop() {
// Process scheduled emissions
advancedSystem.processScheduledEmissions();
// Example signal emissions for demonstration
static unsigned long lastDemo = 0;
static int demoStep = 0;
if (millis() - lastDemo > 8000) { // Every 8 seconds
Serial.println("\n๐ก === DEMO STEP " + String(demoStep + 1) + " ===");
switch(demoStep) {
case 0:
Serial.println("Testing basic signal emission:");
advancedSystem.emit("temperature_normal");
break;
case 1:
Serial.println("Testing priority-based slots:");
advancedSystem.emit("security_breach");
break;
case 2:
Serial.println("Testing parameterized signal:");
advancedSystem.emitWithParameters("temperature_change", "{\"temp\":35,\"location\":\"bedroom\"}");
break;
case 3:
Serial.println("Testing signal chaining:");
advancedSystem.emit("motion_detected");
break;
case 4:
Serial.println("Testing rate limiting (should be limited):");
advancedSystem.emit("security_breach"); // This should be rate limited
break;
case 5:
Serial.println("Testing signal recording and playback:");
advancedSystem.startRecording();
delay(500);
advancedSystem.emit("temperature_normal");
delay(1000);
advancedSystem.emitWithParameters("temperature_change", "{\"temp\":28,\"trend\":\"rising\"}");
delay(1500);
advancedSystem.emit("motion_detected");
delay(500);
advancedSystem.stopRecording();
delay(2000);
Serial.println("Playing back at 2x speed:");
advancedSystem.playbackRecording(2.0);
break;
case 6:
Serial.println("Recording export (JSON):");
Serial.println(advancedSystem.exportRecording());
break;
case 7:
Serial.println("Performance statistics:");
advancedSystem.printPerformanceStats();
demoStep = -1; // Reset to start over
break;
}
demoStep++;
lastDemo = millis();
}
delay(100);
}