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

411 lines
14 KiB
C

/**
* @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