#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <DHT.h>
// Include our enhanced SignalSlot system
// (Assuming the AdvancedSignalSlot class is in a separate header)
// =================================
// CHICKEN INCUBATOR CONFIGURATION
// =================================
struct IncubatorConfig {
// Temperature settings (°C)
float targetChamberTemp = 37.5; // Ideal incubation temperature
float tempTolerance = 0.3; // ±0.3°C tolerance
float maxWaterTemp = 45.0; // Maximum safe water temperature
float minWaterTemp = 35.0; // Minimum effective water temperature
// Humidity settings (%)
float targetHumidity = 55.0; // Days 1-18: 55%
float hatchingHumidity = 65.0; // Days 19-21: 65%
float humidityTolerance = 3.0; // ±3% tolerance
// Turning settings
int turningIntervalHours = 4; // Turn every 4 hours (6 times/day)
int turningAngle = 45; // Degrees to turn
int turningSpeed = 30; // RPM for turning motor
int stopTurningDay = 18; // Stop turning on day 18
// Ventilation settings
int ventingTempThreshold = 38.5; // Auto-vent if chamber > 38.5°C
int ventingHumidityThreshold = 70; // Auto-vent if humidity > 70%
int ventingDurationSeconds = 30; // Vent for 30 seconds
int minVentingInterval = 300; // Min 5 minutes between venting
// Safety settings
float emergencyTempShutdown = 42.0; // Emergency shutdown temperature
float minSafeChamberTemp = 35.0; // Minimum safe chamber temperature
int maxHeatingFailureMinutes = 15; // Max time without heating before alert
int sensorReadingInterval = 30; // Read sensors every 30 seconds
// Incubation period
int incubationDays = 21; // Total incubation period
int hatchingStartDay = 19; // When to switch to hatching mode
// System settings
bool wifiAlertsEnabled = true;
bool soundAlarmsEnabled = true;
bool autoRecoveryEnabled = true;
String deviceName = "ChickenIncubator_01";
} config;
// =================================
// HARDWARE PIN DEFINITIONS
// =================================
// Temperature sensors (DS18B20)
#define WATER_IN_TEMP_PIN 4
#define WATER_OUT_TEMP_PIN 5
#define CHAMBER_TEMP_PIN 6
// Humidity sensor (DHT22)
#define DHT_PIN 7
#define DHT_TYPE DHT22
// Actuators
#define WATER_HEATER_PIN 8
#define CIRCULATION_PUMP_PIN 9
#define HUMIDIFIER_PIN 10
#define VENTILATION_FAN_PIN 11
#define TURNING_MOTOR_PIN 12
#define TURNING_DIRECTION_PIN 8
#define EMERGENCY_BUZZER_PIN 8
#define STATUS_LED_PIN 8
// Sensors
#define DOOR_SENSOR_PIN 8 // Magnetic reed switch
#define WATER_LEVEL_PIN 8 // Water level sensor
#define POWER_MONITOR_PIN 8 // Current sensor for power monitoring
// =================================
// SYSTEM STATE & VARIABLES
// =================================
#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)) {
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 incubatorSystem;
// Temperature sensors setup
OneWire waterInOneWire(WATER_IN_TEMP_PIN);
OneWire waterOutOneWire(WATER_OUT_TEMP_PIN);
OneWire chamberOneWire(CHAMBER_TEMP_PIN);
DallasTemperature waterInSensor(&waterInOneWire);
DallasTemperature waterOutSensor(&waterOutOneWire);
DallasTemperature chamberSensor(&chamberOneWire);
DHT humiditySensor(DHT_PIN, DHT_TYPE);
RTC_DS3231 rtc;
// Current system state
struct IncubatorState {
// Sensor readings
float waterInTemp = 0;
float waterOutTemp = 0;
float chamberTemp = 0;
float humidity = 0;
bool doorOpen = false;
bool waterLevelOK = true;
float powerConsumption = 0;
// Operational state
bool isHeating = false;
bool isPumping = false;
bool isHumidifying = false;
bool isVenting = false;
bool isTurning = false;
// Incubation progress
unsigned long incubationStartTime = 0;
int currentDay = 0;
bool isHatchingMode = false;
int eggsLaid = 0;
// Safety state
bool emergencyShutdown = false;
bool heatingFailure = false;
bool sensorFailure = false;
// Statistics
unsigned long totalRunningHours = 0;
unsigned long heatingHours = 0;
int turningCount = 0;
int ventingCount = 0;
float avgChamberTemp = 0;
float avgHumidity = 0;
// Last operation timestamps
unsigned long lastTurning = 0;
unsigned long lastVenting = 0;
unsigned long lastHeating = 0;
unsigned long lastSensorReading = 0;
unsigned long lastDataLog = 0;
} state;
// =================================
// SENSOR READING FUNCTIONS
// =================================
void readTemperatureSensors() {
waterInSensor.requestTemperatures();
waterOutSensor.requestTemperatures();
chamberSensor.requestTemperatures();
float newWaterInTemp = waterInSensor.getTempCByIndex(0);
float newWaterOutTemp = waterOutSensor.getTempCByIndex(0);
float newChamberTemp = chamberSensor.getTempCByIndex(0);
// Validate readings
if (newWaterInTemp != DEVICE_DISCONNECTED_C && newWaterInTemp > -50 && newWaterInTemp < 100) {
state.waterInTemp = newWaterInTemp;
} else {
incubatorSystem.emitWithParameters("sensor_failure", "{\"sensor\":\"water_in_temp\",\"value\":" + String(newWaterInTemp) + "}");
}
if (newWaterOutTemp != DEVICE_DISCONNECTED_C && newWaterOutTemp > -50 && newWaterOutTemp < 100) {
state.waterOutTemp = newWaterOutTemp;
} else {
incubatorSystem.emitWithParameters("sensor_failure", "{\"sensor\":\"water_out_temp\",\"value\":" + String(newWaterOutTemp) + "}");
}
if (newChamberTemp != DEVICE_DISCONNECTED_C && newChamberTemp > -50 && newChamberTemp < 100) {
float tempDiff = abs(state.chamberTemp - newChamberTemp);
state.chamberTemp = newChamberTemp;
// Update running average
static float tempSum = 0;
static int tempCount = 0;
tempSum += newChamberTemp;
tempCount++;
state.avgChamberTemp = tempSum / tempCount;
// Emit temperature-related signals
if (tempDiff > 0.1) { // Only emit if significant change
incubatorSystem.emitWithParameters("chamber_temp_changed",
"{\"temp\":" + String(newChamberTemp) + ",\"target\":" + String(config.targetChamberTemp) + "}");
}
// Check temperature thresholds
if (newChamberTemp > config.targetChamberTemp + config.tempTolerance) {
incubatorSystem.emitWithParameters("chamber_temp_high", "{\"temp\":" + String(newChamberTemp) + "}");
} else if (newChamberTemp < config.targetChamberTemp - config.tempTolerance) {
incubatorSystem.emitWithParameters("chamber_temp_low", "{\"temp\":" + String(newChamberTemp) + "}");
} else {
incubatorSystem.emit("chamber_temp_normal");
}
// Emergency temperature check
if (newChamberTemp > config.emergencyTempShutdown) {
incubatorSystem.emitWithParameters("emergency_temperature", "{\"temp\":" + String(newChamberTemp) + "}");
}
} else {
incubatorSystem.emitWithParameters("sensor_failure", "{\"sensor\":\"chamber_temp\",\"value\":" + String(newChamberTemp) + "}");
}
Serial.println("🌡️ Temps - Chamber: " + String(state.chamberTemp) +
"°C, Water In: " + String(state.waterInTemp) +
"°C, Water Out: " + String(state.waterOutTemp) + "°C");
}
void readHumiditySensor() {
float newHumidity = humiditySensor.readHumidity();
if (!isnan(newHumidity) && newHumidity >= 0 && newHumidity <= 100) {
float humidityDiff = abs(state.humidity - newHumidity);
state.humidity = newHumidity;
// Update running average
static float humiditySum = 0;
static int humidityCount = 0;
humiditySum += newHumidity;
humidityCount++;
state.avgHumidity = humiditySum / humidityCount;
// Determine target humidity based on incubation stage
float targetHumidity = state.isHatchingMode ? config.hatchingHumidity : config.targetHumidity;
if (humidityDiff > 1.0) { // Only emit if significant change
incubatorSystem.emitWithParameters("humidity_changed",
"{\"humidity\":" + String(newHumidity) + ",\"target\":" + String(targetHumidity) + "}");
}
// Check humidity thresholds
if (newHumidity < targetHumidity - config.humidityTolerance) {
incubatorSystem.emitWithParameters("humidity_low", "{\"humidity\":" + String(newHumidity) + "}");
} else if (newHumidity > targetHumidity + config.humidityTolerance) {
incubatorSystem.emitWithParameters("humidity_high", "{\"humidity\":" + String(newHumidity) + "}");
} else {
incubatorSystem.emit("humidity_normal");
}
} else {
incubatorSystem.emitWithParameters("sensor_failure", "{\"sensor\":\"humidity\",\"value\":" + String(newHumidity) + "}");
}
Serial.println("💧 Humidity: " + String(state.humidity) + "% (Target: " +
String(state.isHatchingMode ? config.hatchingHumidity : config.targetHumidity) + "%)");
}
void readOtherSensors() {
// Door sensor
bool doorOpen = digitalRead(DOOR_SENSOR_PIN) == HIGH;
if (doorOpen != state.doorOpen) {
state.doorOpen = doorOpen;
if (doorOpen) {
incubatorSystem.emit("door_opened");
} else {
incubatorSystem.emit("door_closed");
}
}
// Water level sensor
bool waterLevelOK = digitalRead(WATER_LEVEL_PIN) == HIGH;
if (waterLevelOK != state.waterLevelOK) {
state.waterLevelOK = waterLevelOK;
if (!waterLevelOK) {
incubatorSystem.emit("water_level_low");
} else {
incubatorSystem.emit("water_level_normal");
}
}
// Power consumption monitoring
int powerReading = analogRead(POWER_MONITOR_PIN);
state.powerConsumption = (powerReading / 1024.0) * 50.0; // Scale to watts
if (state.powerConsumption > 200) { // High power consumption alert
incubatorSystem.emitWithParameters("power_consumption_high", "{\"watts\":" + String(state.powerConsumption) + "}");
}
}
// =================================
// ACTUATOR CONTROL FUNCTIONS
// =================================
void activateHeating() {
if (!state.doorOpen && state.waterLevelOK && !state.emergencyShutdown) {
digitalWrite(WATER_HEATER_PIN, HIGH);
digitalWrite(CIRCULATION_PUMP_PIN, HIGH);
state.isHeating = true;
state.isPumping = true;
state.lastHeating = millis();
Serial.println("🔥 HEATING: Water heater and circulation pump ON");
}
}
void deactivateHeating() {
digitalWrite(WATER_HEATER_PIN, LOW);
state.isHeating = false;
Serial.println("🔥 HEATING: Water heater OFF (pump continues for circulation)");
}
void activateHumidification() {
if (!state.doorOpen && !state.emergencyShutdown) {
digitalWrite(HUMIDIFIER_PIN, HIGH);
state.isHumidifying = true;
Serial.println("💧 HUMIDIFIER: Activated");
}
}
void deactivateHumidification() {
digitalWrite(HUMIDIFIER_PIN, LOW);
state.isHumidifying = false;
Serial.println("💧 HUMIDIFIER: Deactivated");
}
void activateVentilation() {
if (!state.doorOpen && (millis() - state.lastVenting) > (config.minVentingInterval * 1000)) {
digitalWrite(VENTILATION_FAN_PIN, HIGH);
state.isVenting = true;
state.lastVenting = millis();
state.ventingCount++;
Serial.println("🌬️ VENTILATION: Fan activated for " + String(config.ventingDurationSeconds) + " seconds");
// Schedule automatic deactivation
incubatorSystem.scheduleEmission("venting_complete", config.ventingDurationSeconds * 1000);
}
}
void deactivateVentilation() {
digitalWrite(VENTILATION_FAN_PIN, LOW);
state.isVenting = false;
Serial.println("🌬️ VENTILATION: Fan deactivated");
}
void performEggTurning() {
if (!state.doorOpen && !state.emergencyShutdown && state.currentDay < config.stopTurningDay) {
if ((millis() - state.lastTurning) > (config.turningIntervalHours * 3600000UL)) {
state.isTurning = true;
state.lastTurning = millis();
state.turningCount++;
Serial.println("🔄 TURNING: Starting egg turning cycle #" + String(state.turningCount));
// Turn clockwise
digitalWrite(TURNING_DIRECTION_PIN, HIGH);
analogWrite(TURNING_MOTOR_PIN, config.turningSpeed * 255 / 100); // Convert RPM to PWM
delay(2000); // Turn for 2 seconds
// Stop motor
analogWrite(TURNING_MOTOR_PIN, 0);
delay(1000); // Wait 1 second
// Turn counter-clockwise back to center
digitalWrite(TURNING_DIRECTION_PIN, LOW);
analogWrite(TURNING_MOTOR_PIN, config.turningSpeed * 255 / 100);
delay(2000); // Turn back for 2 seconds
// Stop motor
analogWrite(TURNING_MOTOR_PIN, 0);
state.isTurning = false;
Serial.println("🔄 TURNING: Cycle complete");
incubatorSystem.emitWithParameters("turning_complete",
"{\"count\":" + String(state.turningCount) + ",\"day\":" + String(state.currentDay) + "}");
}
}
}
void triggerEmergencyShutdown() {
state.emergencyShutdown = true;
// Turn off all heating elements
digitalWrite(WATER_HEATER_PIN, LOW);
digitalWrite(HUMIDIFIER_PIN, LOW);
// Activate emergency cooling
digitalWrite(VENTILATION_FAN_PIN, HIGH);
// Sound alarm
if (config.soundAlarmsEnabled) {
digitalWrite(EMERGENCY_BUZZER_PIN, HIGH);
}
// Flash status LED
for (int i = 0; i < 10; i++) {
digitalWrite(STATUS_LED_PIN, HIGH);
delay(200);
digitalWrite(STATUS_LED_PIN, LOW);
delay(200);
}
Serial.println("🚨 EMERGENCY SHUTDOWN ACTIVATED!");
}
void resetEmergencyState() {
if (state.emergencyShutdown && config.autoRecoveryEnabled) {
if (state.chamberTemp < config.targetChamberTemp + 1.0) { // Safe to restart
state.emergencyShutdown = false;
digitalWrite(EMERGENCY_BUZZER_PIN, LOW);
digitalWrite(STATUS_LED_PIN, HIGH);
Serial.println("✅ EMERGENCY STATE RESET - System recovery");
incubatorSystem.emit("system_recovered");
}
}
}
// =================================
// INCUBATION MANAGEMENT
// =================================
void updateIncubationProgress() {
if (state.incubationStartTime > 0) {
unsigned long elapsed = millis() - state.incubationStartTime;
int newDay = (elapsed / (24UL * 3600UL * 1000UL)) + 1; // Days since start
if (newDay != state.currentDay) {
state.currentDay = newDay;
Serial.println("📅 INCUBATION DAY " + String(state.currentDay) + " of " + String(config.incubationDays));
incubatorSystem.emitWithParameters("incubation_day_changed",
"{\"day\":" + String(state.currentDay) + ",\"total\":" + String(config.incubationDays) + "}");
// Check if entering hatching mode
if (state.currentDay >= config.hatchingStartDay && !state.isHatchingMode) {
state.isHatchingMode = true;
incubatorSystem.emit("hatching_mode_started");
}
// Check if incubation period is complete
if (state.currentDay > config.incubationDays) {
incubatorSystem.emit("incubation_complete");
}
}
}
}
void startIncubation() {
state.incubationStartTime = millis();
state.currentDay = 1;
state.isHatchingMode = false;
state.turningCount = 0;
state.ventingCount = 0;
// Reset statistics
state.totalRunningHours = 0;
state.heatingHours = 0;
Serial.println("🐣 INCUBATION STARTED!");
Serial.println("📅 Expected hatch date: " + String(config.incubationDays) + " days from now");
incubatorSystem.emit("incubation_started");
// Schedule first turning
incubatorSystem.scheduleEmission("turning_required", config.turningIntervalHours * 3600000UL);
}
// =================================
// SIGNAL SLOT CONNECTIONS
// =================================
void setupSignalSlots() {
// Set up event callback
incubatorSystem.setEventCallback([](const String& message, int priority) {
String priorityStr = priority == 0 ? "INFO" : priority == 1 ? "WARNING" : "ERROR";
Serial.println("🔧 SYSTEM [" + priorityStr + "]: " + message);
});
// =================================
// TEMPERATURE CONTROL CONNECTIONS
// =================================
incubatorSystem.connectWithPriority("chamber_temp_low", activateHeating, 8);
incubatorSystem.connectWithPriority("chamber_temp_normal", deactivateHeating, 5);
incubatorSystem.connectWithPriority("chamber_temp_high", activateVentilation, 7);
incubatorSystem.connectWithPriority("emergency_temperature", triggerEmergencyShutdown, 10);
// =================================
// HUMIDITY CONTROL CONNECTIONS
// =================================
incubatorSystem.connect("humidity_low", activateHumidification);
incubatorSystem.connect("humidity_normal", deactivateHumidification);
incubatorSystem.connect("humidity_high", activateVentilation);
// =================================
// VENTILATION CONTROL
// =================================
incubatorSystem.connect("venting_complete", deactivateVentilation);
// =================================
// TURNING SYSTEM CONNECTIONS
// =================================
incubatorSystem.connect("turning_required", performEggTurning);
// Schedule next turning after each completion
incubatorSystem.connectParameterized("turning_complete", [](const String& data) {
if (state.currentDay < config.stopTurningDay) {
incubatorSystem.scheduleEmission("turning_required", config.turningIntervalHours * 3600000UL);
}
});
// =================================
// SAFETY & MONITORING CONNECTIONS
// =================================
incubatorSystem.connectWithPriority("door_opened", []() {
Serial.println("🚪 DOOR OPENED - Pausing operations");
deactivateHeating();
deactivateHumidification();
}, 9);
incubatorSystem.connect("door_closed", []() {
Serial.println("🚪 DOOR CLOSED - Resuming operations");
// Normal operation will resume based on sensor readings
});
incubatorSystem.connectWithPriority("water_level_low", []() {
Serial.println("💧 LOW WATER LEVEL - Stopping heating");
deactivateHeating();
if (config.soundAlarmsEnabled) {
digitalWrite(EMERGENCY_BUZZER_PIN, HIGH);
delay(1000);
digitalWrite(EMERGENCY_BUZZER_PIN, LOW);
}
}, 9);
incubatorSystem.connectParameterized("sensor_failure", [](const String& data) {
Serial.println("⚠️ SENSOR FAILURE: " + data);
state.sensorFailure = true;
// Implement sensor failure recovery logic
});
// =================================
// INCUBATION STAGE CONNECTIONS
// =================================
incubatorSystem.connect("hatching_mode_started", []() {
Serial.println("🐣 HATCHING MODE STARTED!");
Serial.println(" - Humidity increased to " + String(config.hatchingHumidity) + "%");
Serial.println(" - Egg turning STOPPED");
Serial.println(" - Ventilation reduced");
// Stop turning motor permanently
analogWrite(TURNING_MOTOR_PIN, 0);
});
incubatorSystem.connect("incubation_complete", []() {
Serial.println("🎉 INCUBATION PERIOD COMPLETE!");
Serial.println("🐣 Chicks should be hatching now!");
// Gentle shutdown of systems
deactivateHeating();
deactivateHumidification();
// Keep minimal ventilation
digitalWrite(VENTILATION_FAN_PIN, LOW);
});
// =================================
// DATA LOGGING & MONITORING
// =================================
// Log all important events
incubatorSystem.connect("wildcard", []() {
// Update running hours
state.totalRunningHours = millis() / 3600000UL;
if (state.isHeating) {
static unsigned long lastHeatingUpdate = 0;
if (millis() - lastHeatingUpdate > 3600000UL) { // Update every hour
state.heatingHours++;
lastHeatingUpdate = millis();
}
}
});
// Periodic data logging
incubatorSystem.scheduleEmission("data_log_required", 300000, true, 300000); // Every 5 minutes
incubatorSystem.connect("data_log_required", []() {
Serial.println("📊 DATA LOG:");
Serial.println(" Day: " + String(state.currentDay) + "/" + String(config.incubationDays));
Serial.println(" Chamber: " + String(state.chamberTemp) + "°C (Avg: " + String(state.avgChamberTemp) + "°C)");
Serial.println(" Humidity: " + String(state.humidity) + "% (Avg: " + String(state.avgHumidity) + "%)");
Serial.println(" Water: In=" + String(state.waterInTemp) + "°C, Out=" + String(state.waterOutTemp) + "°C");
Serial.println(" Turns: " + String(state.turningCount) + ", Vents: " + String(state.ventingCount));
Serial.println(" Power: " + String(state.powerConsumption) + "W");
Serial.println(" Runtime: " + String(state.totalRunningHours) + "h (Heating: " + String(state.heatingHours) + "h)");
});
// =================================
// RECOVERY & MAINTENANCE
// =================================
incubatorSystem.connect("system_recovered", []() {
// Restart normal operations after emergency
Serial.println("🔄 RESTARTING NORMAL OPERATIONS");
state.sensorFailure = false;
state.heatingFailure = false;
});
// Periodic system health check
incubatorSystem.scheduleEmission("health_check_required", 600000, true, 600000); // Every 10 minutes
incubatorSystem.connect("health_check_required", []() {
// Check for heating failure
if (state.isHeating && (millis() - state.lastHeating) > (config.maxHeatingFailureMinutes * 60000UL)) {
incubatorSystem.emit("heating_system_failure");
}
// Check sensor freshness
if ((millis() - state.lastSensorReading) > 120000) { // 2 minutes without sensor reading
incubatorSystem.emit("sensor_timeout");
}
// Attempt recovery from emergency state
resetEmergencyState();
Serial.println("❤️ HEALTH CHECK: System OK");
});
}
// =================================
// SETUP & MAIN LOOP
// =================================
void setup() {
Serial.begin(115200);
//EEPROM.init(sizeof(IncubatorConfig) + sizeof(IncubatorState));
Serial.println("🐣 ========== CHICKEN INCUBATOR SYSTEM ==========");
Serial.println("🔧 System initializing...");
// Initialize hardware pins
pinMode(WATER_HEATER_PIN, OUTPUT);
pinMode(CIRCULATION_PUMP_PIN, OUTPUT);
pinMode(HUMIDIFIER_PIN, OUTPUT);
pinMode(VENTILATION_FAN_PIN, OUTPUT);
pinMode(TURNING_MOTOR_PIN, OUTPUT);
pinMode(TURNING_DIRECTION_PIN, OUTPUT);
pinMode(EMERGENCY_BUZZER_PIN, OUTPUT);
pinMode(STATUS_LED_PIN, OUTPUT);
pinMode(DOOR_SENSOR_PIN, INPUT_PULLUP);
pinMode(WATER_LEVEL_PIN, INPUT_PULLUP);
// Initialize all outputs to OFF
digitalWrite(WATER_HEATER_PIN, LOW);
digitalWrite(CIRCULATION_PUMP_PIN, LOW);
digitalWrite(HUMIDIFIER_PIN, LOW);
digitalWrite(VENTILATION_FAN_PIN, LOW);
digitalWrite(EMERGENCY_BUZZER_PIN, LOW);
digitalWrite(STATUS_LED_PIN, HIGH); // Status LED ON indicates system ready
analogWrite(TURNING_MOTOR_PIN, 0);
// Initialize sensors
waterInSensor.begin();
waterOutSensor.begin();
chamberSensor.begin();
humiditySensor.begin();
if (!rtc.begin()) {
Serial.println("⚠️ RTC not found! Using millis() for timing.");
}
// Set up signal-slot system
setupSignalSlots();
// Enable performance monitoring for optimization
incubatorSystem.enablePerformanceMonitoring(true);
Serial.println("✅ Hardware initialized");
Serial.println("🔗 Signal-slot system ready");
Serial.println("📊 Performance monitoring enabled");
Serial.println("🚀 INCUBATOR READY FOR OPERATION!");
Serial.println("\n📋 CONFIGURATION:");
Serial.println(" Target Temp: " + String(config.targetChamberTemp) + "°C ±" + String(config.tempTolerance) + "°C");
Serial.println(" Target Humidity: " + String(config.targetHumidity) + "% (Days 1-18), " + String(config.hatchingHumidity) + "% (Days 19-21)");
Serial.println(" Turning: Every " + String(config.turningIntervalHours) + " hours until day " + String(config.stopTurningDay));
Serial.println(" Incubation Period: " + String(config.incubationDays) + " days");
// Wait for user to start incubation
Serial.println("\n🐣 Send 'START' command to begin incubation...");
}
void loop() {
// Process scheduled emissions (turning, data logging, health checks)
incubatorSystem.processScheduledEmissions();
// Read sensors periodically
static unsigned long lastSensorRead = 0;
if (millis() - lastSensorRead > (config.sensorReadingInterval * 1000)) {
readTemperatureSensors();
readHumiditySensor();
readOtherSensors();
state.lastSensorReading = millis();
lastSensorRead = millis();
}
// Update incubation progress
updateIncubationProgress();
// Handle serial commands
if (Serial.available()) {
String command = Serial.readStringUntil('\n');
command.trim();
command.toUpperCase();
if (command == "START") {
startIncubation();
} else if (command == "STOP") {
Serial.println("🛑 INCUBATION STOPPED BY USER");
state.incubationStartTime = 0;
deactivateHeating();
deactivateHumidification();
analogWrite(TURNING_MOTOR_PIN, 0);
} else if (command == "STATUS") {
printSystemStatus();
} else if (command == "STATS") {
incubatorSystem.printPerformanceStats();
} else if (command == "EMERGENCY") {
triggerEmergencyShutdown();
} else if (command == "RESET") {
resetEmergencyState();
} else if (command == "TURN") {
performEggTurning();
} else if (command == "VENT") {
activateVentilation();
} else if (command == "RECORD_START") {
incubatorSystem.startRecording();
} else if (command == "RECORD_STOP") {
incubatorSystem.stopRecording();
} else if (command == "PLAYBACK") {
incubatorSystem.playbackRecording();
} else if (command == "HELP") {
printHelpCommands();
} else {
Serial.println("❓ Unknown command: " + command + " (Type HELP for commands)");
}
}
delay(100); // Small delay for system stability
}
// =================================
// ADDITIONAL UTILITY FUNCTIONS
// =================================
void printSystemStatus() {
Serial.println("\n📊 ========== SYSTEM STATUS ==========");
// Incubation Status
if (state.incubationStartTime > 0) {
Serial.println("🐣 INCUBATION: Day " + String(state.currentDay) + " of " + String(config.incubationDays));
Serial.println(" Mode: " + String(state.isHatchingMode ? "HATCHING" : "INCUBATING"));
unsigned long remaining = (config.incubationDays * 24UL * 3600UL * 1000UL) - (millis() - state.incubationStartTime);
int remainingDays = remaining / (24UL * 3600UL * 1000UL);
Serial.println(" Time remaining: " + String(remainingDays) + " days");
} else {
Serial.println("🐣 INCUBATION: Not started");
}
// Environmental Conditions
Serial.println("\n🌡️ TEMPERATURE:");
Serial.println(" Chamber: " + String(state.chamberTemp) + "°C (Target: " + String(config.targetChamberTemp) + "°C)");
Serial.println(" Water In: " + String(state.waterInTemp) + "°C");
Serial.println(" Water Out: " + String(state.waterOutTemp) + "°C");
Serial.println(" Average: " + String(state.avgChamberTemp) + "°C");
Serial.println("\n💧 HUMIDITY:");
float targetHumidity = state.isHatchingMode ? config.hatchingHumidity : config.targetHumidity;
Serial.println(" Current: " + String(state.humidity) + "% (Target: " + String(targetHumidity) + "%)");
Serial.println(" Average: " + String(state.avgHumidity) + "%");
// System Status
Serial.println("\n⚙️ SYSTEMS:");
Serial.println(" Heating: " + String(state.isHeating ? "ON" : "OFF"));
Serial.println(" Circulation: " + String(state.isPumping ? "ON" : "OFF"));
Serial.println(" Humidifier: " + String(state.isHumidifying ? "ON" : "OFF"));
Serial.println(" Ventilation: " + String(state.isVenting ? "ON" : "OFF"));
Serial.println(" Turning: " + String(state.isTurning ? "ACTIVE" : "IDLE"));
// Safety Status
Serial.println("\n🛡️ SAFETY:");
Serial.println(" Door: " + String(state.doorOpen ? "OPEN" : "CLOSED"));
Serial.println(" Water Level: " + String(state.waterLevelOK ? "OK" : "LOW"));
Serial.println(" Emergency: " + String(state.emergencyShutdown ? "ACTIVE" : "NORMAL"));
Serial.println(" Sensor Failure: " + String(state.sensorFailure ? "YES" : "NO"));
// Statistics
Serial.println("\n📈 STATISTICS:");
Serial.println(" Running Time: " + String(state.totalRunningHours) + " hours");
Serial.println(" Heating Time: " + String(state.heatingHours) + " hours");
Serial.println(" Turning Count: " + String(state.turningCount));
Serial.println(" Venting Count: " + String(state.ventingCount));
Serial.println(" Power Consumption: " + String(state.powerConsumption) + "W");
Serial.println("=====================================\n");
}
void printHelpCommands() {
Serial.println("\n📋 ========== AVAILABLE COMMANDS ==========");
Serial.println("🐣 INCUBATION CONTROL:");
Serial.println(" START - Begin incubation cycle");
Serial.println(" STOP - Stop incubation (emergency)");
Serial.println(" STATUS - Show complete system status");
Serial.println(" STATS - Show performance statistics");
Serial.println("\n🔧 MANUAL CONTROL:");
Serial.println(" TURN - Manual egg turning");
Serial.println(" VENT - Manual ventilation cycle");
Serial.println(" EMERGENCY - Trigger emergency shutdown");
Serial.println(" RESET - Reset from emergency state");
Serial.println("\n📹 RECORDING & DEBUGGING:");
Serial.println(" RECORD_START - Start recording all signals");
Serial.println(" RECORD_STOP - Stop recording");
Serial.println(" PLAYBACK - Replay recorded signals");
Serial.println("\n❓ HELP - Show this help menu");
Serial.println("==========================================\n");
}
// =================================
// ADDITIONAL ROBUST FEATURES
// =================================
// WiFi and remote monitoring capability
void setupWiFiMonitoring() {
if (config.wifiAlertsEnabled) {
WiFi.begin("YourWiFiSSID", "YourWiFiPassword");
// Connect parameterized slots for WiFi alerts
incubatorSystem.connectParameterized("emergency_temperature", [](const String& data) {
sendWiFiAlert("EMERGENCY", "Temperature too high: " + data);
});
incubatorSystem.connectParameterized("water_level_low", [](const String& data) {
sendWiFiAlert("WARNING", "Water level low - refill immediately");
});
incubatorSystem.connectParameterized("incubation_day_changed", [](const String& data) {
sendWiFiAlert("INFO", "Incubation progress: " + data);
});
}
}
void sendWiFiAlert(const String& level, const String& message) {
if (WiFi.status() == WL_CONNECTED) {
// Send HTTP POST to your notification service
// This could be IFTTT, Telegram, email, etc.
Serial.println("📱 WIFI ALERT [" + level + "]: " + message);
// Implementation depends on your notification service
}
}
// Advanced data logging to SD card
void setupDataLogging() {
// Connect data logging to wildcard for comprehensive logging
incubatorSystem.connect("wildcard", []() {
static unsigned long lastLog = 0;
if (millis() - lastLog > 60000) { // Log every minute
String logEntry = createLogEntry();
// Save to SD card
Serial.println("💾 LOG: " + logEntry);
lastLog = millis();
}
});
}
String createLogEntry() {
// Create JSON log entry
String log = "{";
log += "\"timestamp\":" + String(millis()) + ",";
log += "\"day\":" + String(state.currentDay) + ",";
log += "\"chamberTemp\":" + String(state.chamberTemp) + ",";
log += "\"waterInTemp\":" + String(state.waterInTemp) + ",";
log += "\"waterOutTemp\":" + String(state.waterOutTemp) + ",";
log += "\"humidity\":" + String(state.humidity) + ",";
log += "\"heating\":" + String(state.isHeating ? "true" : "false") + ",";
log += "\"humidifying\":" + String(state.isHumidifying ? "true" : "false") + ",";
log += "\"venting\":" + String(state.isVenting ? "true" : "false") + ",";
log += "\"doorOpen\":" + String(state.doorOpen ? "true" : "false") + ",";
log += "\"waterLevelOK\":" + String(state.waterLevelOK ? "true" : "false") + ",";
log += "\"powerConsumption\":" + String(state.powerConsumption);
log += "}";
return log;
}
// Predictive maintenance system
void setupPredictiveMaintenance() {
// Track system performance for predictive alerts
incubatorSystem.scheduleEmission("maintenance_check", 24UL * 3600UL * 1000UL, true, 24UL * 3600UL * 1000UL); // Daily
incubatorSystem.connect("maintenance_check", []() {
// Check heating efficiency
float heatingEfficiency = (state.heatingHours > 0) ? (state.totalRunningHours / state.heatingHours) : 0;
if (heatingEfficiency > 0.8) { // More than 80% heating time indicates inefficiency
Serial.println("⚠️ MAINTENANCE: Heating system may need attention - efficiency low");
}
// Check turning mechanism
int expectedTurns = (state.currentDay < config.stopTurningDay) ? (state.currentDay * (24 / config.turningIntervalHours)) : 0;
if (state.turningCount < expectedTurns * 0.9) { // Less than 90% of expected turns
Serial.println("⚠️ MAINTENANCE: Turning mechanism may have issues");
}
// Check sensor stability
static float lastChamberTemp = 0;
if (abs(state.chamberTemp - lastChamberTemp) > 2.0) { // Large temperature swings
Serial.println("⚠️ MAINTENANCE: Temperature sensor or control may need calibration");
}
lastChamberTemp = state.chamberTemp;
Serial.println("🔧 MAINTENANCE CHECK: System performance analyzed");
});
}
// Backup power monitoring
void setupPowerBackup() {
incubatorSystem.connectParameterized("power_consumption_high", [](const String& data) {
Serial.println("⚡ HIGH POWER CONSUMPTION: " + data);
// Could trigger backup power system or load shedding
});
// Monitor for power failures
incubatorSystem.scheduleEmission("power_check", 30000, true, 30000); // Every 30 seconds
incubatorSystem.connect("power_check", []() {
static unsigned long lastPowerCheck = millis();
unsigned long gap = millis() - lastPowerCheck;
if (gap > 35000) { // Gap longer than expected indicates power interruption
Serial.println("⚡ POWER INTERRUPTION DETECTED - Gap: " + String(gap) + "ms");
incubatorSystem.emit("power_failure_detected");
}
lastPowerCheck = millis();
});
}