411 lines
14 KiB
C
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
|