New files
new files
This commit is contained in:
commit
474f0ec63a
300
auto.h
Normal file
300
auto.h
Normal file
@ -0,0 +1,300 @@
|
||||
/**
|
||||
* @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
|
110
bluetooth.h
Normal file
110
bluetooth.h
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @file bluetooth.h
|
||||
* @brief Bluetooth interface for configuration and status reporting.
|
||||
*
|
||||
* This module initializes the Bluetooth serial interface for remote configuration.
|
||||
* It provides functions to set up Bluetooth, process incoming Bluetooth commands,
|
||||
* and send periodic status updates (including auto mode, WiFi, and MQTT connectivity).
|
||||
*
|
||||
* The status update string is built dynamically by querying the current system state.
|
||||
*
|
||||
* @author
|
||||
* James C. Alexander
|
||||
* @date
|
||||
* 2025-02-03
|
||||
*/
|
||||
|
||||
#ifndef BLUETOOTH_H
|
||||
#define BLUETOOTH_H
|
||||
|
||||
#include <BluetoothSerial.h>
|
||||
#include "config.h"
|
||||
#include "commands.h"
|
||||
#include "custom_mqtt.h"
|
||||
#include <WiFi.h>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Global Bluetooth Serial Object
|
||||
// ---------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Bluetooth Serial interface.
|
||||
*
|
||||
* This object is used to communicate over Bluetooth, allowing remote configuration
|
||||
* and status monitoring.
|
||||
*/
|
||||
BluetoothSerial SerialBT;
|
||||
|
||||
/**
|
||||
* @brief Timestamp for the last Bluetooth status update.
|
||||
*/
|
||||
unsigned long lastBTStatusUpdate = 0;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Function Prototypes
|
||||
// ---------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Initialize the Bluetooth interface.
|
||||
*
|
||||
* Sets up the Bluetooth serial connection with a predefined device name.
|
||||
*/
|
||||
void setupBluetooth();
|
||||
|
||||
/**
|
||||
* @brief Process incoming Bluetooth commands.
|
||||
*
|
||||
* Checks if any data is available on the Bluetooth serial interface. If a command is received,
|
||||
* it trims any extraneous whitespace and passes the command to the configuration command handler.
|
||||
*/
|
||||
void handleBluetoothCommands();
|
||||
|
||||
/**
|
||||
* @brief Send periodic status updates over Bluetooth.
|
||||
*
|
||||
* If at least 1 second has elapsed since the last update, this function sends a status update
|
||||
* string via Bluetooth.
|
||||
*/
|
||||
void sendBTStatusUpdate();
|
||||
|
||||
/**
|
||||
* @brief Build a formatted system status update string.
|
||||
*
|
||||
* The status update includes the current auto mode state, WiFi connectivity, and MQTT connectivity.
|
||||
*
|
||||
* @return A formatted String containing the system status.
|
||||
*/
|
||||
String getStatusUpdate() {
|
||||
String status = "AutoMode: ";
|
||||
status += autoMode ? "ON" : "OFF";
|
||||
status += ", WiFi: ";
|
||||
status += (WiFi.status() == WL_CONNECTED) ? "Connected" : "Disconnected";
|
||||
status += ", MQTT: ";
|
||||
status += (client.connected()) ? "Connected" : "Disconnected";
|
||||
return status;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Function Implementations
|
||||
// ---------------------------------------------------------------------
|
||||
void setupBluetooth() {
|
||||
SerialBT.begin("GrowTentController"); // Initialize Bluetooth with the device name.
|
||||
Serial.println("Bluetooth Started. Ready for configuration.");
|
||||
}
|
||||
|
||||
void handleBluetoothCommands() {
|
||||
// If data is available on the Bluetooth interface, read and process the command.
|
||||
if (SerialBT.available()) {
|
||||
String command = SerialBT.readStringUntil('\n');
|
||||
command.trim(); // Remove any leading/trailing whitespace.
|
||||
handleConfigCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
void sendBTStatusUpdate() {
|
||||
// Send a status update every 1000 milliseconds (1 second).
|
||||
if (millis() - lastBTStatusUpdate >= 1000) {
|
||||
lastBTStatusUpdate = millis();
|
||||
SerialBT.println(getStatusUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // BLUETOOTH_H
|
410
commands.h
Normal file
410
commands.h
Normal file
@ -0,0 +1,410 @@
|
||||
/**
|
||||
* @file commands.h
|
||||
* @brief Serial command processing for configuration updates.
|
||||
*
|
||||
* This module handles incoming commands over the Serial interface to update
|
||||
* configuration variables. Supported commands include:
|
||||
*
|
||||
* Global configuration:
|
||||
* - SET <parameter> <value>
|
||||
* e.g., "SET publishInterval 15000"
|
||||
*
|
||||
* WiFi configuration:
|
||||
* - SET_WIFI <SSID>,<Password>
|
||||
*
|
||||
* MQTT configuration:
|
||||
* - SET_MQTT_HOST <host>
|
||||
* - SET_MQTT_PORT <port>
|
||||
* - SET_MQTT_USER <username>
|
||||
* - SET_MQTT_PASS <password>
|
||||
* - SET_MQTT_TOPIC_ROOT <root>
|
||||
*
|
||||
* Per-plant configuration:
|
||||
* - SET PLANT<index> MODE <value>
|
||||
* - SET PLANT<index> SETPOINTS <mode> <LOW|HIGH> <value>
|
||||
*
|
||||
* Additionally, a new command is provided to toggle relay logic:
|
||||
* - SET relayLogic <HIGH|LOW>
|
||||
* (HIGH for active-high relays, LOW for active-low; default is LOW.)
|
||||
*
|
||||
* After processing any command, the updated configuration is saved to EEPROM.
|
||||
*
|
||||
* Author: James C. Alexander
|
||||
* Date: 2025-02-03
|
||||
*/
|
||||
|
||||
#ifndef COMMANDS_H
|
||||
#define COMMANDS_H
|
||||
|
||||
#include "config.h"
|
||||
#include "config_storage.h"
|
||||
#include <WiFi.h>
|
||||
#include <BluetoothSerial.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include "wifi_interface.h"
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// External Objects and Variables
|
||||
// ---------------------------------------------------------------------
|
||||
extern BluetoothSerial SerialBT; ///< Bluetooth serial interface.
|
||||
extern bool wifiEnabled; ///< Flag to enable/disable WiFi reconnect loop.
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Helper Function: startsWithIgnoreCase
|
||||
// ---------------------------------------------------------------------
|
||||
inline bool startsWithIgnoreCase(String str, String prefix) {
|
||||
str.toLowerCase();
|
||||
prefix.toLowerCase();
|
||||
return str.startsWith(prefix);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// handleConfigCommand()
|
||||
// ---------------------------------------------------------------------
|
||||
inline void handleConfigCommand(String command) {
|
||||
command.trim();
|
||||
|
||||
// -----------------------
|
||||
// Handle Reboot Commands
|
||||
// -----------------------
|
||||
if (startsWithIgnoreCase(command, "REBOOT") || startsWithIgnoreCase(command, "RESTART")) {
|
||||
Serial.println("Reboot command received! Restarting ESP32...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
return;
|
||||
}
|
||||
if (command.startsWith("SET_DEFAULT")) {
|
||||
saveDefaultConfigToEEPROM();
|
||||
Serial.println("Defaults Restored.");
|
||||
Serial.println("Reboot command received! Restarting ESP32...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
return;
|
||||
}
|
||||
// -----------------------
|
||||
// Handle WiFi Commands
|
||||
// -----------------------
|
||||
if (command.startsWith("SET_WIFI ")) {
|
||||
String creds = command.substring(9);
|
||||
int separator = creds.indexOf(',');
|
||||
if (separator > 0) {
|
||||
String newSSID = creds.substring(0, separator);
|
||||
String newPassword = creds.substring(separator + 1);
|
||||
Serial.println("Updating WiFi credentials...");
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
WiFi.disconnect();
|
||||
Serial.println("WiFi disconnected for credential update.");
|
||||
}
|
||||
newSSID.toCharArray(ssid, sizeof(ssid));
|
||||
newPassword.toCharArray(password, sizeof(password));
|
||||
saveConfigToEEPROM();
|
||||
Serial.print("WiFi credentials updated: SSID=");
|
||||
Serial.println(ssid);
|
||||
Serial.println("Please issue WIFI_CONNECT to restart connection.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (startsWithIgnoreCase(command, "SCAN_WIFI")) {
|
||||
bool jsonOutput = command.length() > 10 && startsWithIgnoreCase(command.substring(10), "JSON");
|
||||
Serial.println("Scanning for WiFi networks...");
|
||||
int networkCount = WiFi.scanNetworks();
|
||||
if (networkCount < 0) {
|
||||
Serial.printf("WiFi scan failed! Error code: %d\n", networkCount);
|
||||
return;
|
||||
}
|
||||
if (networkCount == 0) {
|
||||
Serial.println("No WiFi networks found.");
|
||||
return;
|
||||
}
|
||||
if (jsonOutput) {
|
||||
DynamicJsonDocument jsonDoc(1024);
|
||||
JsonArray networks = jsonDoc.createNestedArray("networks");
|
||||
for (int i = 0; i < networkCount; i++) {
|
||||
JsonObject net = networks.createNestedObject();
|
||||
net["ssid"] = WiFi.SSID(i);
|
||||
net["signal"] = WiFi.RSSI(i);
|
||||
net["security"] = (WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? "Open" : "Protected";
|
||||
}
|
||||
String jsonString;
|
||||
serializeJson(jsonDoc, jsonString);
|
||||
Serial.println(jsonString);
|
||||
} else {
|
||||
Serial.printf("Found %d networks:\n", networkCount);
|
||||
for (int i = 0; i < networkCount; i++) {
|
||||
Serial.printf("%d: SSID: %s | Signal: %d dBm | Security: %s\n",
|
||||
i + 1,
|
||||
WiFi.SSID(i).c_str(),
|
||||
WiFi.RSSI(i),
|
||||
WiFi.encryptionType(i) == WIFI_AUTH_OPEN ? "Open" : "Protected");
|
||||
}
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
return;
|
||||
}
|
||||
// -----------------------
|
||||
// Handle MQTT Commands
|
||||
// -----------------------
|
||||
if (command.startsWith("SET_MQTT_HOST ")) {
|
||||
String newHost = command.substring(14);
|
||||
newHost.trim();
|
||||
if (newHost.length() > 0) {
|
||||
newHost.toCharArray(mqtt_host, sizeof(mqtt_host));
|
||||
saveConfigToEEPROM();
|
||||
Serial.print("MQTT host updated to: ");
|
||||
Serial.println(mqtt_host);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (command.startsWith("SET_MQTT_PORT ")) {
|
||||
String portStr = command.substring(14);
|
||||
portStr.trim();
|
||||
uint16_t newPort = portStr.toInt();
|
||||
if (newPort > 0) {
|
||||
mqtt_port = newPort;
|
||||
saveConfigToEEPROM();
|
||||
Serial.print("MQTT port updated to: ");
|
||||
Serial.println(mqtt_port);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (command.startsWith("SET_MQTT_USER ")) {
|
||||
String newUser = command.substring(14);
|
||||
newUser.trim();
|
||||
newUser.toCharArray(mqtt_username, sizeof(mqtt_username));
|
||||
saveConfigToEEPROM();
|
||||
Serial.print("MQTT username updated to: ");
|
||||
Serial.println(mqtt_username);
|
||||
return;
|
||||
}
|
||||
if (command.startsWith("SET_MQTT_PASS ")) {
|
||||
String newPass = command.substring(14);
|
||||
newPass.trim();
|
||||
newPass.toCharArray(mqtt_password, sizeof(mqtt_password));
|
||||
saveConfigToEEPROM();
|
||||
Serial.print("MQTT password updated to: ");
|
||||
Serial.println(mqtt_password);
|
||||
return;
|
||||
}
|
||||
if (command.startsWith("SET_MQTT_TOPIC_ROOT ")) {
|
||||
String newRoot = command.substring(20);
|
||||
newRoot.trim();
|
||||
newRoot.toCharArray(mqtt_topic_root, sizeof(mqtt_topic_root));
|
||||
saveConfigToEEPROM();
|
||||
Serial.print("MQTT topic root updated to: ");
|
||||
Serial.println(mqtt_topic_root);
|
||||
return;
|
||||
}
|
||||
if (startsWithIgnoreCase(command, "SET_MQTT_CONN ")) {
|
||||
String params = command.substring(14);
|
||||
params.trim();
|
||||
int idx1 = params.indexOf(',');
|
||||
int idx2 = params.indexOf(',', idx1 + 1);
|
||||
int idx3 = params.indexOf(',', idx2 + 1);
|
||||
int idx4 = params.indexOf(',', idx3 + 1);
|
||||
if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
|
||||
Serial.println("Invalid MQTT connection format. Expected:");
|
||||
Serial.println("SET_MQTT_CONN <host>,<port>,<user>,<password>,<topic_root>");
|
||||
return;
|
||||
}
|
||||
String newHost = params.substring(0, idx1);
|
||||
String newPortStr = params.substring(idx1 + 1, idx2);
|
||||
String newUser = params.substring(idx2 + 1, idx3);
|
||||
String newPass = params.substring(idx3 + 1, idx4);
|
||||
String newTopicRoot = params.substring(idx4 + 1);
|
||||
newHost.trim();
|
||||
newPortStr.trim();
|
||||
newUser.trim();
|
||||
newPass.trim();
|
||||
newTopicRoot.trim();
|
||||
uint16_t newPort = newPortStr.toInt();
|
||||
if (newPort <= 0) {
|
||||
Serial.println("Invalid MQTT port. Must be a number greater than 0.");
|
||||
return;
|
||||
}
|
||||
newHost.toCharArray(mqtt_host, sizeof(mqtt_host));
|
||||
mqtt_port = newPort;
|
||||
newUser.toCharArray(mqtt_username, sizeof(mqtt_username));
|
||||
newPass.toCharArray(mqtt_password, sizeof(mqtt_password));
|
||||
newTopicRoot.toCharArray(mqtt_topic_root, sizeof(mqtt_topic_root));
|
||||
saveConfigToEEPROM();
|
||||
Serial.println("MQTT settings updated:");
|
||||
Serial.print("Host: "); Serial.println(mqtt_host);
|
||||
Serial.print("Port: "); Serial.println(mqtt_port);
|
||||
Serial.print("User: "); Serial.println(mqtt_username);
|
||||
Serial.print("Password: "); Serial.println("********");
|
||||
Serial.print("Topic Root: "); Serial.println(mqtt_topic_root);
|
||||
return;
|
||||
}
|
||||
// -----------------------
|
||||
// Handle Global Configuration SET Command
|
||||
// -----------------------
|
||||
if (command.startsWith("SET ")) {
|
||||
int firstSpace = command.indexOf(' ');
|
||||
int secondSpace = command.indexOf(' ', firstSpace + 1);
|
||||
if (secondSpace < 0) {
|
||||
Serial.println("Invalid SET command format. Expected: SET <parameter> <value>");
|
||||
return;
|
||||
}
|
||||
String param = command.substring(firstSpace + 1, secondSpace);
|
||||
String value = command.substring(secondSpace + 1);
|
||||
value.trim();
|
||||
if (param.equalsIgnoreCase("publishInterval"))
|
||||
publishInterval = value.toInt();
|
||||
else if (param.equalsIgnoreCase("wifiReconnectInterval"))
|
||||
wifiReconnectInterval = value.toInt();
|
||||
else if (param.equalsIgnoreCase("mqttReconnectInterval"))
|
||||
mqttReconnectInterval = value.toInt();
|
||||
else if (param.equalsIgnoreCase("mixerLevelSetpoint"))
|
||||
mixerLevelSetpoint = value.toFloat();
|
||||
else if (param.equalsIgnoreCase("feederLevelSetpoint"))
|
||||
feederLevelSetpoint = value.toFloat();
|
||||
else if (param.equalsIgnoreCase("phSettlingTime"))
|
||||
phSettlingTime = value.toFloat();
|
||||
else if (param.equalsIgnoreCase("phTargetMax"))
|
||||
phTargetMax = value.toFloat();
|
||||
else if (param.equalsIgnoreCase("phPumpDuration"))
|
||||
phPumpDuration = value.toFloat();
|
||||
else if (param.equalsIgnoreCase("mixingPumpDuration"))
|
||||
mixingPumpDuration = value.toFloat();
|
||||
else if (param.equalsIgnoreCase("autoMode"))
|
||||
autoMode = (value == "1" || value.equalsIgnoreCase("true"));
|
||||
else if (param.equalsIgnoreCase("bypassFillInterlock"))
|
||||
bypassFillInterlock = (value == "1" || value.equalsIgnoreCase("true"));
|
||||
else {
|
||||
Serial.print("Unknown global parameter: ");
|
||||
Serial.println(param);
|
||||
return;
|
||||
}
|
||||
saveConfigToEEPROM();
|
||||
Serial.print("Global parameter updated: ");
|
||||
Serial.print(param);
|
||||
Serial.print(" = ");
|
||||
Serial.println(value);
|
||||
return;
|
||||
}
|
||||
// -----------------------
|
||||
// Handle Per-Plant Configuration Commands
|
||||
// -----------------------
|
||||
if (command.startsWith("SET PLANT")) {
|
||||
String remainder = command.substring(9);
|
||||
remainder.trim();
|
||||
int spacePos = remainder.indexOf(' ');
|
||||
if (spacePos < 0) {
|
||||
Serial.println("Invalid per-plant command format.");
|
||||
return;
|
||||
}
|
||||
String plantStr = remainder.substring(0, spacePos);
|
||||
int plantIndex = plantStr.toInt() - 1;
|
||||
if (plantIndex < 0 || plantIndex >= 3) {
|
||||
Serial.println("Plant index out of range.");
|
||||
return;
|
||||
}
|
||||
remainder = remainder.substring(spacePos + 1);
|
||||
remainder.trim();
|
||||
if (remainder.startsWith("MODE ")) {
|
||||
String modeVal = remainder.substring(5);
|
||||
modeVal.trim();
|
||||
plantConfigs[plantIndex].currentMode = modeVal.toInt();
|
||||
saveConfigToEEPROM();
|
||||
Serial.print("Plant ");
|
||||
Serial.print(plantIndex + 1);
|
||||
Serial.print(" active mode updated to: ");
|
||||
Serial.println(plantConfigs[plantIndex].currentMode);
|
||||
return;
|
||||
} else if (remainder.startsWith("SETPOINTS ")) {
|
||||
String params = remainder.substring(10);
|
||||
params.trim();
|
||||
int firstSp = params.indexOf(' ');
|
||||
int secondSp = params.indexOf(' ', firstSp + 1);
|
||||
int thirdSp = params.indexOf(' ', secondSp + 1);
|
||||
if (firstSp < 0 || secondSp < 0 || thirdSp < 0) {
|
||||
Serial.println("Invalid SETPOINTS command format. Expected: SETPOINTS <mode> <LOW|HIGH> <value>");
|
||||
return;
|
||||
}
|
||||
String modeName = params.substring(0, firstSp);
|
||||
String threshType = params.substring(firstSp + 1, secondSp);
|
||||
String valueStr = params.substring(secondSp + 1);
|
||||
valueStr.trim();
|
||||
int modeIndex = -1;
|
||||
if (modeName.equalsIgnoreCase("seedling"))
|
||||
modeIndex = SEEDLING_MODE;
|
||||
else if (modeName.equalsIgnoreCase("vegetative"))
|
||||
modeIndex = VEGETATIVE_MODE;
|
||||
else if (modeName.equalsIgnoreCase("flowering"))
|
||||
modeIndex = FLOWERING_MODE;
|
||||
else if (modeName.equalsIgnoreCase("drying"))
|
||||
modeIndex = DRYING_MODE;
|
||||
else {
|
||||
Serial.print("Unknown watering mode: ");
|
||||
Serial.println(modeName);
|
||||
return;
|
||||
}
|
||||
float newValue = valueStr.toFloat();
|
||||
if (threshType.equalsIgnoreCase("LOW")) {
|
||||
plantConfigs[plantIndex].setpoints[modeIndex].soilMoistureLow = newValue;
|
||||
Serial.print("Plant ");
|
||||
Serial.print(plantIndex + 1);
|
||||
Serial.print(" mode ");
|
||||
Serial.print(modeName);
|
||||
Serial.print(" low setpoint updated to: ");
|
||||
Serial.println(newValue);
|
||||
} else if (threshType.equalsIgnoreCase("HIGH")) {
|
||||
plantConfigs[plantIndex].setpoints[modeIndex].soilMoistureHigh = newValue;
|
||||
Serial.print("Plant ");
|
||||
Serial.print(plantIndex + 1);
|
||||
Serial.print(" mode ");
|
||||
Serial.print(modeName);
|
||||
Serial.print(" high setpoint updated to: ");
|
||||
Serial.println(newValue);
|
||||
} else {
|
||||
Serial.print("Unknown threshold type: ");
|
||||
Serial.println(threshType);
|
||||
return;
|
||||
}
|
||||
saveConfigToEEPROM();
|
||||
return;
|
||||
} else {
|
||||
Serial.println("Unknown per-plant command format.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// New Command: Toggle Relay Logic
|
||||
// -----------------------
|
||||
if (startsWithIgnoreCase(command, "SET relayLogic ")) {
|
||||
String value = command.substring(15);
|
||||
value.trim();
|
||||
if (value.equalsIgnoreCase("HIGH") || value.equalsIgnoreCase("true")) {
|
||||
relayActiveHigh = true;
|
||||
Serial.println("Relay activation set to ACTIVE-HIGH.");
|
||||
} else if (value.equalsIgnoreCase("LOW") || value.equalsIgnoreCase("false")) {
|
||||
relayActiveHigh = false;
|
||||
Serial.println("Relay activation set to ACTIVE-LOW.");
|
||||
} else {
|
||||
Serial.println("Invalid relayLogic value. Use 'HIGH' or 'LOW'.");
|
||||
}
|
||||
saveConfigToEEPROM();
|
||||
return;
|
||||
}
|
||||
|
||||
// If no command matched
|
||||
Serial.println("Unknown command.");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// processSerialCommands()
|
||||
// ---------------------------------------------------------------------
|
||||
inline void processSerialCommands() {
|
||||
if (Serial.available() > 0) {
|
||||
String command = Serial.readStringUntil('\n');
|
||||
command.trim();
|
||||
if (command.length() > 0) {
|
||||
Serial.print("Received command: ");
|
||||
Serial.println(command);
|
||||
handleConfigCommand(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // COMMANDS_H
|
58
commands.md
Normal file
58
commands.md
Normal file
@ -0,0 +1,58 @@
|
||||
# 🌱 ESP32 Greenhouse Controller - Command Reference
|
||||
|
||||
## ⚡ System Commands
|
||||
| **Command** | **Description** |
|
||||
|------------------------|----------------|
|
||||
| `REBOOT` / `RESTART` | Reboots the ESP32 immediately. |
|
||||
| `SET_DEFAULT` | Restores all configuration to default and reboots the ESP32. |
|
||||
|
||||
## 📡 WiFi Commands
|
||||
| **Command** | **Description** |
|
||||
|----------------------------------|----------------|
|
||||
| `SET_WIFI <SSID>,<PASSWORD>` | Updates WiFi credentials & saves them to EEPROM. |
|
||||
| `SCAN_WIFI` | Scans for available WiFi networks (human-readable). |
|
||||
| `SCAN_WIFI JSON` | Scans for WiFi networks and returns results in JSON format. |
|
||||
|
||||
## 📡 MQTT Commands
|
||||
| **Command** | **Description** |
|
||||
|--------------------------------------|----------------|
|
||||
| `SET_MQTT_HOST <host>` | Updates MQTT broker hostname or IP. |
|
||||
| `SET_MQTT_PORT <port>` | Updates MQTT broker port. |
|
||||
| `SET_MQTT_USER <username>` | Updates MQTT username. |
|
||||
| `SET_MQTT_PASS <password>` | Updates MQTT password. |
|
||||
| `SET_MQTT_TOPIC_ROOT <root>` | Updates MQTT topic root. |
|
||||
| `SET_MQTT_CONN <host>,<port>,<user>,<pass>,<root>` | Sets all MQTT settings in one command. |
|
||||
|
||||
## ⚙️ Global Configuration Commands
|
||||
| **Command** | **Description** |
|
||||
|--------------------------------|----------------|
|
||||
| `SET <parameter> <value>` | Updates a global configuration parameter. |
|
||||
| **Supported Parameters** | `publishInterval`, `wifiReconnectInterval`, `mqttReconnectInterval`, `mixerLevelSetpoint`, `feederLevelSetpoint`, `mixerFullLevel`, `mixerRefillLevel`, `phSettlingTime`, `phTargetMax`, `phPumpDuration`, `mixingPumpDuration`, `autoMode`, `bypassFillInterlock` |
|
||||
|
||||
## 🌱 Per-Plant Configuration Commands
|
||||
| **Command** | **Description** |
|
||||
|-----------------------------------------------|----------------|
|
||||
| `SET PLANT<index> MODE <value>` | Sets the active watering mode for a plant (index starts at 1). |
|
||||
| `SET PLANT<index> SETPOINTS <mode> <LOW|HIGH> <value>` | Updates moisture setpoints for a plant's growth stage. |
|
||||
|
||||
## 🔹 Example Usages:
|
||||
```
|
||||
SET_WIFI MyHomeWiFi,SecurePassword123
|
||||
```
|
||||
**Output:**
|
||||
```
|
||||
WiFi credentials updated: SSID=MyHomeWiFi
|
||||
```
|
||||
|
||||
```
|
||||
SCAN_WIFI JSON
|
||||
```
|
||||
**Output:**
|
||||
```json
|
||||
{
|
||||
"networks": [
|
||||
{ "ssid": "HomeWiFi", "signal": -67, "security": "Protected" },
|
||||
{ "ssid": "GuestWiFi", "signal": -72, "security": "Protected" }
|
||||
]
|
||||
}
|
||||
```
|
208
config.h
Normal file
208
config.h
Normal file
@ -0,0 +1,208 @@
|
||||
/**
|
||||
* @file config.h
|
||||
* @brief Global configuration definitions for the GreenHouse Controller.
|
||||
*
|
||||
* This file defines default values for WiFi, MQTT, timing parameters, setpoints,
|
||||
* and per-plant watering configurations. It also declares modifiable global variables,
|
||||
* which are defined in one source file (typically the main sketch). Additionally,
|
||||
* inline functions are provided to dynamically build MQTT topics based on the current
|
||||
* configuration.
|
||||
*
|
||||
* Author: James C. Alexander
|
||||
* Date: 2025-02-03
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Global Default Values
|
||||
// ---------------------------------------------------------------------
|
||||
#define DEFAULT_SSID "" ///< Default WiFi SSID.
|
||||
#define DEFAULT_WIFI_PASSWORD "" ///< Default WiFi password.
|
||||
#define DEFAULT_MQTT_HOST "000.000.000.000" ///< Default MQTT broker host.
|
||||
#define DEFAULT_MQTT_PORT 1883 ///< Default MQTT broker port.
|
||||
#define DEFAULT_MQTT_USERNAME "" ///< Default MQTT username (empty if not used).
|
||||
#define DEFAULT_MQTT_PASSWORD "" ///< Default MQTT password (empty if not used).
|
||||
#define DEFAULT_MQTT_TOPIC_ROOT "GroPro" ///< Default MQTT topic root.
|
||||
|
||||
#define DEFAULT_PUBLISH_INTERVAL 2500 ///< Default publish interval in milliseconds.
|
||||
#define DEFAULT_WIFI_RECONNECT_INTERVAL 5000 ///< Default WiFi reconnect interval in milliseconds.
|
||||
#define DEFAULT_MQTT_RECONNECT_INTERVAL 5000 ///< Default MQTT reconnect interval in milliseconds.
|
||||
#define DEFAULT_MIXER_LEVEL_SETPOINT 50.0 ///< Default mixer tank refill setpoint (cm).
|
||||
#define DEFAULT_FEEDER_LEVEL_SETPOINT 10.0 ///< Default feeder tank refill setpoint (cm).
|
||||
#define DEFAULT_MIXER_FULL_LEVEL 80.0 ///< Default mixer tank full level (cm).
|
||||
#define DEFAULT_MIXER_REFILL_LEVEL 40.0 ///< Default mixer tank refill level (cm).
|
||||
#define DEFAULT_PH_SETTLING_TIME 5.0 ///< Default delay after mixing before checking pH (seconds).
|
||||
#define DEFAULT_PH_TARGET_MAX 6.5 ///< Default maximum acceptable pH.
|
||||
#define DEFAULT_PH_PUMP_DURATION 3.0 ///< Default duration to run the pH pump (seconds).
|
||||
#define DEFAULT_MIXING_PUMP_DURATION 10.0 ///< Default duration to run the mixing pump (seconds).
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Watering Modes and Per-Plant Watering Configuration
|
||||
// ---------------------------------------------------------------------
|
||||
/**
|
||||
* @enum WateringMode
|
||||
* @brief Defines the various watering modes.
|
||||
*/
|
||||
enum WateringMode {
|
||||
SEEDLING_MODE = 0,
|
||||
VEGETATIVE_MODE = 1,
|
||||
FLOWERING_MODE = 2,
|
||||
DRYING_MODE = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct WateringSetpoints
|
||||
* @brief Holds the soil moisture setpoints for a specific watering mode.
|
||||
*/
|
||||
struct WateringSetpoints {
|
||||
float soilMoistureLow; ///< Low threshold for soil moisture.
|
||||
float soilMoistureHigh; ///< High threshold for soil moisture.
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct PlantWateringConfig
|
||||
* @brief Holds the complete watering configuration for a single plant.
|
||||
*/
|
||||
struct PlantWateringConfig {
|
||||
WateringSetpoints setpoints[4]; ///< Setpoints for each watering mode.
|
||||
uint8_t currentMode; ///< Active watering mode index (0-3).
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Default watering configurations for three plants.
|
||||
*/
|
||||
const PlantWateringConfig DEFAULT_PLANT_CONFIGS[3] = {
|
||||
{ { {60, 80}, {40, 70}, {30, 60}, {20, 40} }, VEGETATIVE_MODE },
|
||||
{ { {60, 80}, {40, 70}, {30, 60}, {20, 40} }, VEGETATIVE_MODE },
|
||||
{ { {60, 80}, {40, 70}, {30, 60}, {20, 40} }, VEGETATIVE_MODE }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// WiFi Configuration
|
||||
// ---------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Modifiable WiFi configuration buffers.
|
||||
*/
|
||||
extern char ssid[32]; ///< WiFi SSID.
|
||||
extern char password[64]; ///< WiFi password.
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// MQTT Configuration
|
||||
// ---------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Modifiable MQTT broker settings.
|
||||
*/
|
||||
extern char mqtt_host[64]; ///< MQTT broker host.
|
||||
extern uint16_t mqtt_port; ///< MQTT broker port.
|
||||
extern char mqtt_username[32]; ///< MQTT username.
|
||||
extern char mqtt_password[32]; ///< MQTT password.
|
||||
extern char mqtt_topic_root[32]; ///< MQTT topic root.
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Inline Functions for Dynamic Topic Construction
|
||||
// ---------------------------------------------------------------------
|
||||
inline String getMqttConfigTopic() {
|
||||
return String(mqtt_topic_root) + "/config";
|
||||
}
|
||||
inline String getMqttPublishIntervalTopic() {
|
||||
return String(mqtt_topic_root) + "/config/pubInt";
|
||||
}
|
||||
inline String getMqttWifiReconnectTopic() {
|
||||
return String(mqtt_topic_root) + "/config/wifInt";
|
||||
}
|
||||
inline String getMqttMqttReconnectTopic() {
|
||||
return String(mqtt_topic_root) + "/config/mqtInt";
|
||||
}
|
||||
inline String getMqttMixerLevelSetpointTopic() {
|
||||
return String(mqtt_topic_root) + "/config/mixSet";
|
||||
}
|
||||
inline String getMqttFeederLevelSetpointTopic() {
|
||||
return String(mqtt_topic_root) + "/config/feeSet";
|
||||
}
|
||||
inline String getMqttBypassFillInterlockTopic() {
|
||||
return String(mqtt_topic_root) + "/config/bypass";
|
||||
}
|
||||
inline String getMqttWateringModeTopic(uint8_t plantIndex) {
|
||||
return String(mqtt_topic_root) + "/config/plant" + String(plantIndex + 1) + "/wMode";
|
||||
}
|
||||
inline String getMqttSoilMoistureLowTopic(uint8_t plantIndex) {
|
||||
return String(mqtt_topic_root) + "/config/plant" + String(plantIndex + 1) + "/sLow";
|
||||
}
|
||||
inline String getMqttSoilMoistureHighTopic(uint8_t plantIndex) {
|
||||
return String(mqtt_topic_root) + "/config/plant" + String(plantIndex + 1) + "/sHigh";
|
||||
}
|
||||
inline String getAmbientTempTopic() {
|
||||
return String(mqtt_topic_root) + "/climate/temp";
|
||||
}
|
||||
inline String getAmbientHumidityTopic() {
|
||||
return String(mqtt_topic_root) + "/climate/hum";
|
||||
}
|
||||
inline String getPhTopic() {
|
||||
return String(mqtt_topic_root) + "/mixing/ph";
|
||||
}
|
||||
inline String getTdsTopic() {
|
||||
return String(mqtt_topic_root) + "/mixing/tds";
|
||||
}
|
||||
inline String getPhotoTopic() {
|
||||
return String(mqtt_topic_root) + "/lighting/photo";
|
||||
}
|
||||
inline String getFeederLevelTopic() {
|
||||
return String(mqtt_topic_root) + "/tank/feeder";
|
||||
}
|
||||
inline String getMixerLevelTopic() {
|
||||
return String(mqtt_topic_root) + "/tank/mixer";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Global Configuration Variables (Stored in EEPROM)
|
||||
// ---------------------------------------------------------------------
|
||||
extern unsigned long publishInterval;
|
||||
extern unsigned long wifiReconnectInterval;
|
||||
extern unsigned long mqttReconnectInterval;
|
||||
extern float mixerLevelSetpoint;
|
||||
extern float feederLevelSetpoint;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Additional Global Configuration Variables
|
||||
// ---------------------------------------------------------------------
|
||||
extern float mixerFullLevel;
|
||||
extern float mixerRefillLevel;
|
||||
extern float phSettlingTime;
|
||||
extern float phTargetMax;
|
||||
extern float phPumpDuration;
|
||||
extern float mixingPumpDuration;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Auto Mode and Interlock Flags
|
||||
// ---------------------------------------------------------------------
|
||||
extern bool autoMode;
|
||||
extern bool bypassFillInterlock;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Tank Level Sensor Readings
|
||||
// ---------------------------------------------------------------------
|
||||
extern float mixerLevel;
|
||||
extern float feederLevel;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Per-Plant Watering Configuration Variables
|
||||
// ---------------------------------------------------------------------
|
||||
extern PlantWateringConfig plantConfigs[3];
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// New Global Variable for Relay Logic
|
||||
// ---------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Relay activation logic flag.
|
||||
* false: relays are active-low (default)
|
||||
* true: relays are active-high.
|
||||
*/
|
||||
extern bool relayActiveHigh;
|
||||
|
||||
#endif // CONFIG_H
|
Loading…
x
Reference in New Issue
Block a user