301 lines
12 KiB
C
301 lines
12 KiB
C
/**
|
||
* @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
|