GroPro_Rich/auto.h
2025-02-10 06:04:18 +00:00

301 lines
12 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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 (03).
// };
//
// 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