/** * @file auto.h * @brief Auto mode control routines for the GreenHouse Controller. * * This module implements the automatic control logic for the greenhouse. * It handles: * - Managing the mixing tank fill pump based on tank levels. * - Adjusting pH by activating the pH pump and mixer pump. * - Controlling plant watering by comparing soil moisture readings * against per-plant setpoints. * * The auto mode functions use one-shot logging to reduce Serial output spam. * Additionally, they ensure that plant watering is not performed unless the mixer * tank is full, pH balanced, and has sufficient fluid. * * Per-plant configuration is stored in the global array 'plantConfigs', which is defined in config.h. * * @author * James C. Alexander * @date * 2025-02-03 */ #ifndef AUTO_H #define AUTO_H #include "config.h" #include "pumps.h" #include "sensors.h" #include "ultrasonic.h" // --------------------------------------------------------------------- // File-Scope Global State Variables for Auto Mode // --------------------------------------------------------------------- // These static variables persist across multiple function calls within this module. /** * @brief Flag used to log the entry into auto mode only once per cycle. */ static bool autoModeLogged = false; /** * @brief Flag indicating whether the mixer tank pH has been balanced in the current fill cycle. */ static bool phBalanced = false; /** * @brief One-shot logging arrays for per-plant moisture thresholds. * * These arrays store the last logged low and high thresholds for each plant. */ static int lastLow[3] = { -1, -1, -1 }; static int lastHigh[3] = { -1, -1, -1 }; /** * @brief Stores the last state of the fill pump. * * (Active-low: LOW means pump is ON; HIGH means pump is OFF.) */ static bool lastMixPumpState = HIGH; /** * @brief Stores the last known state of each plant's pump. * * The size of the array is determined by the number of moisture sensor pins. */ static bool lastPlantPumpState[sizeof(MOISTURE_PINS) / sizeof(MOISTURE_PINS[0])] = { HIGH, HIGH, HIGH }; /** * @brief Flag to ensure a one-shot log message is printed when watering is skipped. */ static bool wateringSkippedLogged = false; // --------------------------------------------------------------------- // Auto Mode State Variables (Global) // --------------------------------------------------------------------- // These variables are declared as extern in config.h. extern bool autoMode; // Global auto mode flag. extern unsigned long lastPHCheckTime; // Timestamp for the last pH check (in ms). // --------------------------------------------------------------------- // Per-Plant Configuration Note // --------------------------------------------------------------------- // The per-plant watering configuration is stored in the global array: // extern PlantWateringConfig plantConfigs[3]; // which is defined in config.h and in the main sketch. The structure is defined as: // // struct WateringSetpoints { // float soilMoistureLow; // float soilMoistureHigh; // }; // // struct PlantWateringConfig { // WateringSetpoints setpoints[4]; // Indexes: 0 = seedling, 1 = vegetative, // // 2 = flowering, 3 = drying. // uint8_t currentMode; // Active watering mode (0–3). // }; // // The default configuration is provided via DEFAULT_PLANT_CONFIGS in config.h. // --------------------------------------------------------------------- // applyWateringMode() // --------------------------------------------------------------------- /** * @brief Log the active moisture thresholds for a plant if they have changed. * * For the given plant (indexed by plantIndex), this function reads the active watering mode * (plantConfigs[plantIndex].currentMode) and obtains the corresponding low and high moisture thresholds. * If these thresholds differ from the last logged values, it logs them to Serial and updates the * lastLow and lastHigh arrays. * * @param plantIndex Zero-based index of the plant. */ inline void applyWateringMode(size_t plantIndex) { // Determine the active watering mode. uint8_t mode = plantConfigs[plantIndex].currentMode; int newLow = (int)plantConfigs[plantIndex].setpoints[mode].soilMoistureLow; int newHigh = (int)plantConfigs[plantIndex].setpoints[mode].soilMoistureHigh; // Log only if the thresholds have changed. if (newLow != lastLow[plantIndex] || newHigh != lastHigh[plantIndex]) { Serial.print(F("Plant ")); Serial.print(plantIndex + 1); Serial.print(F(" active thresholds: Low=")); Serial.print(newLow); Serial.print(F(", High=")); Serial.println(newHigh); lastLow[plantIndex] = newLow; lastHigh[plantIndex] = newHigh; } } // --------------------------------------------------------------------- // manageMixingTank() // --------------------------------------------------------------------- /** * @brief Controls the fill pump for the mixing tank. * * This function turns the fill pump ON if the mixer tank level is below the refill threshold * and the feeder tank has sufficient water, and it resets the pH balanced flag to indicate that * a new fill cycle has begun. The fill pump is turned OFF when the mixer tank reaches full level * or the feeder tank is too low. */ inline void manageMixingTank() { bool newState = lastMixPumpState; // Activate fill pump if mixer tank is below the refill threshold and feeder tank is adequate. if (mixerLevel <= mixerRefillLevel && feederLevel > feederLevelSetpoint) { newState = LOW; // Active-low: LOW means pump ON. phBalanced = false; // Start a new fill cycle; pH will need balancing. } // Deactivate fill pump if mixer tank is full or feeder tank is low. if (mixerLevel >= mixerFullLevel || feederLevel <= feederLevelSetpoint) { newState = HIGH; // Turn pump OFF. } // If there is a change in pump state, update the output and log the change. if (newState != lastMixPumpState) { if (newState == LOW) { Serial.println(F("Mixing Tank: Starting refill...")); } else { Serial.println(F("Mixing Tank: Full or feeder empty - Stopping fill pump")); } digitalWrite(PUMP_PINS[3], newState); lastMixPumpState = newState; } } // --------------------------------------------------------------------- // adjustPH() // --------------------------------------------------------------------- /** * @brief Adjusts the pH of the mixing tank once per fill cycle. * * If the mixer tank is full and the pH has not yet been balanced in the current cycle, * this function waits for the pH settling period and then reads the pH sensor. * If the pH exceeds the target maximum, it activates the pH pump followed by the mixer pump, * and logs the adjustment process. Once complete, the pH is marked as balanced. */ inline void adjustPH() { // Only check and adjust pH if the mixer tank is full. if (mixerLevel < mixerFullLevel) return; if (phBalanced) return; // pH already balanced in this cycle. if (millis() - lastPHCheckTime < phSettlingTime * 1000UL) return; // Wait for the settling period. float currentPH = readPHSensor(); if (currentPH > phTargetMax) { Serial.println(F("pH too high - Initiating pH balance routine...")); digitalWrite(PUMP_PINS[4], LOW); // Activate pH pump. delay(phPumpDuration * 1000UL); digitalWrite(PUMP_PINS[4], HIGH); // Deactivate pH pump. digitalWrite(PUMP_PINS[5], LOW); // Activate mixer pump. delay(mixingPumpDuration * 1000UL); digitalWrite(PUMP_PINS[5], HIGH); // Deactivate mixer pump. Serial.println(F("pH adjusted - Chemical added, mixing complete.")); } else { Serial.println(F("pH acceptable; no adjustment needed.")); } lastPHCheckTime = millis(); // Update the pH check timestamp. phBalanced = true; // Mark the current fill cycle as pH balanced. } // --------------------------------------------------------------------- // managePlantWatering() // --------------------------------------------------------------------- /** * @brief Controls plant watering based on moisture sensor readings. * * This function checks whether the conditions are right for watering: * - The mixing tank pH is balanced. * - The fill pump is off. * - The mixer tank is not empty. * * If any condition is not met, watering is skipped with a one-shot log message. * Otherwise, for each plant, the function reads the moisture sensor value, * compares it against the active thresholds for that plant, and controls the * corresponding pump accordingly. */ inline void managePlantWatering() { // Skip watering if the mixer tank is not ready. if (!phBalanced || lastMixPumpState == LOW || mixerLevel <= 0) { if (!wateringSkippedLogged) { Serial.println(F("Skipping plant watering: Mixer tank not ready (pH unbalanced, filling, or empty).")); wateringSkippedLogged = true; } return; } else { wateringSkippedLogged = false; } // Process watering for each plant. for (size_t i = 0; i < sizeof(MOISTURE_PINS) / sizeof(MOISTURE_PINS[0]); i++) { applyWateringMode(i); int moistureValue = analogRead(MOISTURE_PINS[i]); bool newPumpState = lastPlantPumpState[i]; // Retrieve the active thresholds for this plant based on its current watering mode. uint8_t mode = plantConfigs[i].currentMode; int thresholdLow = (int)plantConfigs[i].setpoints[mode].soilMoistureLow; int thresholdHigh = (int)plantConfigs[i].setpoints[mode].soilMoistureHigh; // Determine pump state based on moisture reading. if (moistureValue < thresholdLow) { newPumpState = LOW; // Turn pump ON (active-low). } if (moistureValue > thresholdHigh) { newPumpState = HIGH; // Turn pump OFF. } // If the pump state has changed, update the output and log the action. if (newPumpState != lastPlantPumpState[i]) { if (newPumpState == LOW) { Serial.print(F("Plant ")); Serial.print(i + 1); Serial.println(F(" - Soil too dry: Watering plant")); } else { Serial.print(F("Plant ")); Serial.print(i + 1); Serial.println(F(" - Soil moisture adequate: Stopping pump")); } digitalWrite(PUMP_PINS[i], newPumpState); lastPlantPumpState[i] = newPumpState; } } } // --------------------------------------------------------------------- // runAutoMode() // --------------------------------------------------------------------- /** * @brief Executes the auto mode control routines. * * If auto mode is enabled, this function logs the transition into auto mode once, * then sequentially: * 1. Manages the mixing tank (controlling the fill pump). * 2. Adjusts the pH (if the mixer tank is full and not yet balanced). * 3. Manages plant watering (if the mixer tank is ready). * * If auto mode is disabled, it resets the one-shot log flag. */ inline void runAutoMode() { if (!autoMode) { autoModeLogged = false; // Reset the log flag if auto mode is off. return; } if (!autoModeLogged) { Serial.println(F("Entering Auto Mode...")); autoModeLogged = true; } manageMixingTank(); adjustPH(); managePlantWatering(); } #endif // AUTO_H