#include <UniversalTelegramBot.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_NeoPixel.h>
#include <WiFiClientSecure.h>
#include <TM1637Display.h>
#include <PubSubClient.h>
#include <WiFiManager.h>
#include <ArduinoJson.h>
#include <ESP32Servo.h>
#include <DHTesp.h>
#include <RTClib.h>
#include <WiFi.h>
#include <Wire.h>
#include <map>
#include <functional>
namespace IOT_06 // SERVERS
{
constexpr const char *BOT_TOKEN = "7591269806:AAEwwJjeho0eHbKV9FAqVeiNhg2WvQ2FSuA";
constexpr const int BOT_DELAY_MS = 100;
const std::map<String, String> BOT_VALID_USERS = {
{ "5721869894", "Võ Hoàng Anh" },
{ "5851537920", "Nguyễn Mạnh Hùng" },
{ "5154626826", "Hoàng Lê Minh Đăng" }
};
const std::map<String, String> BOT_VALID_DATABASE = {
{ "5721869894", "spy" },
{ "5851537920", "hung" },
{ "5154626826", "dang" }
};
constexpr const char *BOT_VALID_TOKENS =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"_-+*/#@?~|";
constexpr const int WIFI_CONNECT_TIMEOUT = 200;
constexpr const int WIFI_CONNECT_BLOCKING = 0;
constexpr const char *WIFI_SSID = "Wokwi-GUEST";
constexpr const char *WIFI_PASS = "";
constexpr const int MQTT_RECONNECT_WAIT_MS = 5000;
constexpr const int MQTT_DEFAULT_PORT = 1883;
constexpr const char *MQTT_SERVER = "mqtt.eclipseprojects.io";
constexpr const char *MQTT_ALL_SENSORS_STATUS = "fish_tank/device/sensors/all";
constexpr const char *MQTT_LEDSEGMENT_STATUS = "fish_tank/device/ledsegment/status";
constexpr const char *MQTT_LEDRING_STATUS = "fish_tank/device/ledring/status";
constexpr const char *MQTT_CELSIUS_STATUS = "fish_tank/device/dht/temp";
constexpr const char *MQTT_SWITCH_STATUS = "fish_tank/device/switch/status";
constexpr const char *MQTT_LEDBAR_STATUS = "fish_tank/device/ledbar/status";
constexpr const char *MQTT_BUTTON_STATUS = "fish_tank/device/button/status";
constexpr const char *MQTT_WATER_STATUS = "fish_tank/device/water/status";
constexpr const char *MQTT_LIGHT_STATUS = "fish_tank/device/light/status";
constexpr const char *MQTT_SERVO_STATUS = "fish_tank/device/servo/status";
constexpr const char *MQTT_POTEN_STATUS = "fish_tank/device/poten/status";
constexpr const char *MQTT_HUMID_STATUS = "fish_tank/device/dht/hum";
constexpr const char *MQTT_DHT_STATUS = "fish_tank/device/dht/status";
constexpr const char *MQTT_LED_STATUS = "fish_tank/device/led/status";
constexpr const char *MQTT_PIR_STATUS = "fish_tank/device/pir/status";
constexpr const char *MQTT_RTC_STATUS = "fish_tank/device/rtc/status";
constexpr const char *MQTT_LCD_STATUS = "fish_tank/device/lcd/status";
constexpr const char *MQTT_PH_STATUS = "fish_tank/device/pH/status";
constexpr const char *MQTT_GENERAL = "fish_tank/device/general";
constexpr const char *MQTT_STATUSES[] = {
MQTT_ALL_SENSORS_STATUS,
MQTT_LEDSEGMENT_STATUS,
MQTT_LEDRING_STATUS,
MQTT_CELSIUS_STATUS,
MQTT_SWITCH_STATUS,
MQTT_LEDBAR_STATUS,
MQTT_BUTTON_STATUS,
MQTT_WATER_STATUS,
MQTT_LIGHT_STATUS,
MQTT_SERVO_STATUS,
MQTT_POTEN_STATUS,
MQTT_HUMID_STATUS,
MQTT_DHT_STATUS,
MQTT_LED_STATUS,
MQTT_PIR_STATUS,
MQTT_RTC_STATUS,
MQTT_LCD_STATUS,
MQTT_PH_STATUS,
MQTT_GENERAL
};
constexpr const char *MQTT_LED_COMMAND = "fish_tank/device/led/command";
constexpr const char *MQTT_LEDBAR_COMMAND = "fish_tank/device/light/command";
constexpr const char *MQTT_SERVO_COMMAND = "fish_tank/device/servo/command";
constexpr const char *MQTT_LEDRING_COLOR = "fish_tank/device/ledring/color";
constexpr const char *MQTT_LEDRING_BRIGHTNESS = "fish_tank/device/ledring/brightness";
constexpr const char *MQTT_ALERT = "fish_tank/device/alert";
}
namespace IOT_06 // PINS
{
constexpr const int BUTTON_PIN = 13;
constexpr const int DHT_PIN = 32;
constexpr const int LED_PIN = 14;
constexpr const int SWITCH_PIN = 12;
constexpr const int LEDSEG_CLK_PIN = 0;
constexpr const int LEDSEG_DIO_PIN = 4;
constexpr const int SONIC_ECHO_PIN = 16;
constexpr const int SONIC_TRIG_PIN = 17;
constexpr const int LCD_SDA_PIN = 18;
constexpr const int LCD_SCL_PIN = 19;
constexpr const int BUZZER_PIN = 21;
constexpr const int RTC_SCL_PIN = 22;
constexpr const int RTC_SDA_PIN = 23;
constexpr const int LEDRING_PIN = 5;
constexpr const int SERVO_PIN = 2;
constexpr const int PHOTO_PIN = 35;
constexpr const int POTEN_PIN = 33;
constexpr const int PIR_PIN = 34;
constexpr const int LEDBAR_SIZE = 3;
constexpr const int WATER_LED_PIN = 15;
constexpr const int LEDBAR_PINS[LEDBAR_SIZE] = {25, 26, 27};
}
namespace IOT_06 // VALUES
{
constexpr const int NOT_FOUND = -1;
constexpr const int BUTTON_DELAY_MS = 50;
constexpr const int PHOTO_MIN_VALUE = 0;
constexpr const int PHOTO_MAX_VALUE = 4095;
constexpr const int LEDRING_OFF = 0;
constexpr const int LEDRING_ON = 2;
constexpr const int LEDRING_COUNT = 40;
constexpr const int LEDRING_MIN_BRIGHT = 0;
constexpr const int LEDRING_MAX_BRIGHT = 255;
constexpr const int SONIC_INVALID_DISTANCE = -1;
constexpr const int LED_ON = HIGH;
constexpr const int LED_OFF = LOW;
constexpr const int LCD_ADDRESS = 0x27;
constexpr const int LCD_COLUMNS = 16;
constexpr const int LCD_ROWS = 2;
constexpr const int SERVO_MIN_DEGREE = 0;
constexpr const int SERVO_MAX_DEGREE = 180;
constexpr const int POTEN_MIN_VALUE = 0;
constexpr const int POTEN_MAX_VALUE = 4095;
constexpr const int WATER_LEVEL_THRESHOLD = 90;
constexpr const int WATER_PUMP_THRESHOLD = 50;
constexpr const int PH_MAX_VALUE = 14;
constexpr const int PH_MIN_VALUE = 0;
constexpr const int SENSOR_READ_INTERVAL = 2000;
}
namespace IOT_06 // CORES
{
const String CMD_START = "/start";
const String CMD_NOPARAM = "";
using CommandType = String;
using DescriptionType = String;
using VoidFunction = std::function<void(const CommandType&, const DescriptionType&)>;
struct Command
{
CommandType command;
CommandType initial;
DescriptionType params;
DescriptionType description;
VoidFunction callback;
};
using CommandMapper = std::map<CommandType, Command>;
class FishTank
{
private: // Servers
WiFiClient wifiClient;
PubSubClient mqttClient;
WiFiClientSecure hostClient;
UniversalTelegramBot teleClient;
WiFiManager wfimClient;
private: // Devices
Servo servo;
DHTesp dht;
RTC_DS3231 rtc;
LiquidCrystal_I2C lcd;
Adafruit_NeoPixel ledring;
TM1637Display ledsegment;
TwoWire rtc_wire;
private: // Internalities
bool ledIsOn;
bool relayIsOn;
bool buzzerIsOn;
bool servoIsOpen;
bool pirIsDetected;
bool lastButtonState;
unsigned long lastEverySensorRead;
unsigned long lastBotRequest;
unsigned long lastReconnected;
unsigned long lastDebounceTime;
unsigned long lastDigitsDisplay;
unsigned long lastRingBrightness;
private: // Commands
CommandMapper commandMap;
std::map<String, int> userCounter;
std::map<String, String> userCaptcha;
public: // Constructor
FishTank() :
wifiClient(),
mqttClient(wifiClient),
hostClient(),
teleClient({BOT_TOKEN, hostClient}),
servo(),
dht(),
rtc(),
lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS),
rtc_wire(TwoWire(1)),
ledring(LEDRING_COUNT, LEDRING_PIN, NEO_GRB + NEO_KHZ800),
ledsegment(LEDSEG_CLK_PIN, LEDSEG_DIO_PIN),
commandMap(getCommandMap()),
userCounter(),
userCaptcha()
{
// ..
}
public: // Initializers
void setup();
void setupServers();
void setupInputDevices();
void setupOutputDevices();
public: // Handlers
void loop();
void loopInputDevices();
void loopOutputDevices();
void loopSchedule();
void loopWifi();
void loopMQTT();
void loopTelegramBot();
public: // Helpers
void handleBotCommand(const String &chatId, const String &username, const String &command, const String ¶ms);
void sendMessage(const String& chatId, const String& msg, const String& publisher);
public: // Getters
int getDistance();
CommandMapper getCommandMap();
};
}
namespace IOT_06 // INITIALIZERS
{
void FishTank::setup()
{
Serial.begin(9600);
Serial.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
ledIsOn = false;
relayIsOn = false;
buzzerIsOn = false;
servoIsOpen = false;
pirIsDetected = false;
lastButtonState = false;
lastBotRequest = 0;
lastReconnected = 0;
lastDebounceTime = 0;
lastDigitsDisplay = 0;
lastRingBrightness = 0;
lastEverySensorRead = 0;
Serial.println("ESP32 is being setup");
for (const auto &user : BOT_VALID_USERS) {
userCounter[user.first] = 0;
userCaptcha[user.first] = BOT_VALID_DATABASE.at(user.first) + user.first[0] + String(random(0xffff), HEX);
}
Serial.println("User is being setup");
setupServers();
setupInputDevices();
setupOutputDevices();
Serial.println("Everything is setup successfully");
}
void FishTank::setupServers()
{
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.println("Wifi setup successfully.");
WiFiManagerParameter custom_mqtt_server("SPyofgame", "SPyofgame", "Custom", 40);
wfimClient.addParameter(&custom_mqtt_server);
wfimClient.preloadWiFi("SPyofgame", "SPyofgame");
wfimClient.setConfigPortalTimeout(20);
wfimClient.setConfigPortalBlocking(WIFI_CONNECT_BLOCKING);
Serial.print("wfimClient.getWiFiSSID(): SSID=" + wfimClient.getWiFiSSID() + " Pass=" + wfimClient.getWiFiPass());
if(wfimClient.autoConnect("AutoConnectAP")){
Serial.println("Wifi Manager Connected");
}
else {
Serial.println("Wifi Config Portal Is Running");
}
Serial.print("custom_mqtt_server: ");
Serial.println(custom_mqtt_server.getValue());
Serial.println("WifiManager setup successfully.");
hostClient.setCACert(TELEGRAM_CERTIFICATE_ROOT);
Serial.println("Host setup successfully.");
mqttClient.setServer(MQTT_SERVER, MQTT_DEFAULT_PORT);
mqttClient.setCallback([this](char *topic, byte *payload, unsigned int length){
String message;
for (unsigned int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.printf("MQTT Message: topic=%s, msg=%s\n", topic, message.c_str());
if (String(topic) == MQTT_SERVO_COMMAND) {
if(message == "On"){
Serial.println("Received servo On command");
Serial.println("Running servo one cycle...");
for (int pos = SERVO_MIN_DEGREE; pos <= SERVO_MAX_DEGREE; ++pos) {
servo.write(pos);
delay(10);
}
for (int pos = SERVO_MAX_DEGREE; pos >= SERVO_MIN_DEGREE; --pos) {
servo.write(pos);
delay(10);
}
mqttClient.publish(MQTT_SERVO_STATUS, "CYCLE_DONE");
Serial.println("Servo cycle done");
} else {
Serial.println("Unknown servo command");
}
}
if (String(topic) == MQTT_LEDRING_COLOR) {
Serial.println("Received LED ring color command: " + message);
auto hexToColor = [this](const String &hexColor) -> uint32_t
{
if (hexColor.length() != 6) {
return 0;
}
long number = strtol(hexColor.c_str(), nullptr, 16);
return ledring.Color((number >> 16) & 0xFF, (number >> 8) & 0xFF, number & 0xFF);
};
uint32_t color = hexToColor(message);
if (color == 0 && message != "000000") {
Serial.println("Invalid color format!");
mqttClient.publish(MQTT_LEDRING_STATUS, "ERROR:Invalid color");
return;
}
for (int i = 0; i < LEDRING_COUNT; i++) {
ledring.setPixelColor(i, color);
}
ledring.show();
Serial.println("LED Ring color changed.");
mqttClient.publish(MQTT_LEDRING_STATUS, ("COLOR:" + message).c_str());
}
if (String(topic) == MQTT_LEDRING_BRIGHTNESS) {
Serial.println("Received LED ring brightness command: " + message);
int brightnessVal = message.toInt();
if (brightnessVal >= 0 && brightnessVal <= 255) {
ledring.setBrightness(brightnessVal);
ledring.show();
Serial.printf("LED Ring brightness set to %d\n", brightnessVal);
String statusMsg = "BRIGHTNESS:" + String(brightnessVal);
mqttClient.publish(MQTT_LEDRING_STATUS, statusMsg.c_str());
} else {
Serial.println("Invalid brightness value received!");
mqttClient.publish(MQTT_LEDRING_STATUS, "ERROR:Invalid brightness");
}
}
if (String(topic) == MQTT_LED_COMMAND) {
if (message == "On") {
digitalWrite(LED_PIN, HIGH);
mqttClient.publish(MQTT_LED_STATUS, "ON");
} else if (message == "Off") {
digitalWrite(LED_PIN, LOW);
mqttClient.publish(MQTT_LED_STATUS, "OFF");
}
Serial.printf("LED (Pump) status changed to %s\n", message.c_str());
}
if (String(topic) == MQTT_LEDBAR_COMMAND) {
if (message == "On") {
for (int i = 0; i < LEDBAR_SIZE; i++) {
digitalWrite(LEDBAR_PINS[i], HIGH);
}
mqttClient.publish(MQTT_LEDBAR_STATUS, "ON");
} else if (message == "Off") {
for (int i = 0; i < LEDBAR_SIZE; i++) {
digitalWrite(LEDBAR_PINS[i], LOW);
}
mqttClient.publish(MQTT_LEDBAR_STATUS, "OFF");
}
Serial.printf("LED Bar status changed to %s\n", message.c_str());
}
});
Serial.println("MQTT setup successfully.");
}
void FishTank::setupInputDevices()
{
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.println("Button setup successfully.");
pinMode(SWITCH_PIN, INPUT);
digitalWrite(SWITCH_PIN, LOW);
Serial.println("Switch setup successfully.");
pinMode(PHOTO_PIN, INPUT);
Serial.println("Photoresistor setup successfully.");
pinMode(SONIC_TRIG_PIN, OUTPUT);
pinMode(SONIC_ECHO_PIN, INPUT);
Serial.println("Ultra Sonic Sensor setup successfully.");
pinMode(PIR_PIN, INPUT);
Serial.println("PIR Sensor setup successfully.");
dht.setup(DHT_PIN, DHTesp::DHT22);
Serial.println("DHT Sensor setup successfully.");
}
void FishTank::setupOutputDevices()
{
Wire.begin(LCD_SDA_PIN, LCD_SCL_PIN);
delay(100);
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("LCD Initialized");
lcd.setCursor(0, 1);
lcd.print("Use \"" + CMD_START + "\"");
Serial.println("LCD setup successfully.");
servo.attach(SERVO_PIN);
servo.write(SERVO_MIN_DEGREE);
Serial.println("Servo initialized to CLOSED position.");
ledsegment.setBrightness(0x0f);
ledsegment.showNumberDec(lastDigitsDisplay, false);
Serial.println("LedSegment setup successfully.");
rtc_wire.begin(RTC_SDA_PIN, RTC_SCL_PIN);
delay(100);
while (!rtc.begin(&rtc_wire)) {
Serial.println("Couldn't find RTC: SDA=" + String(RTC_SDA_PIN) + "SCL=" + String(RTC_SCL_PIN));
delay(100);
}
if (rtc.lostPower()) {
Serial.println("RTC lost power, setting the time!");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
Serial.println("RTC setup successfully.");
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
Serial.println("Led setup successfully.");
pinMode(WATER_LED_PIN, OUTPUT);
digitalWrite(WATER_LED_PIN, LOW);
Serial.println("Water Led setup successfully.");
for (int i = 0; i < LEDBAR_SIZE; i++) {
pinMode(LEDBAR_PINS[i], OUTPUT);
digitalWrite(LEDBAR_PINS[i], LOW);
}
Serial.println("LedBar setup successfully.");
pinMode(LEDRING_PIN, OUTPUT);
ledring.begin();
ledring.setBrightness(LEDRING_MAX_BRIGHT);
for (int i = 0; i < LEDRING_COUNT; i++) {
ledring.setPixelColor(i, ledring.Color(255, 255, 255));
}
ledring.show();
Serial.println("LedRing setup successfully.");
}
}
namespace IOT_06 // HANDLERS
{
void FishTank::loop()
{
loopInputDevices();
loopOutputDevices();
loopSchedule();
loopWifi();
loopMQTT();
loopTelegramBot();
}
void FishTank::loopInputDevices()
{
if (digitalRead(SWITCH_PIN) == HIGH) {
ESP.restart();
return ;
}
static unsigned long lastDebounceTime = 0;
static bool lastStableButtonState = HIGH;
static bool currentButtonState = HIGH;
bool rawButtonState = digitalRead(BUTTON_PIN);
if (rawButtonState != currentButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > BUTTON_DELAY_MS) {
if (rawButtonState != lastStableButtonState) {
lastStableButtonState = rawButtonState;
if (lastStableButtonState == LOW) {
if (servoIsOpen) {
servoIsOpen = false;
for (int pos = SERVO_MAX_DEGREE; pos >= SERVO_MIN_DEGREE; --pos) {
servo.write(pos);
delay(10);
}
}
else {
servoIsOpen = true;
for (int pos = SERVO_MIN_DEGREE; pos <= SERVO_MAX_DEGREE; ++pos) {
servo.write(pos);
delay(10);
}
}
Serial.println("Button pressed, toggling servo.");
}
}
}
currentButtonState = rawButtonState;
int pirState = digitalRead(PIR_PIN);
if (pirState == HIGH && !pirIsDetected) {
pirIsDetected = true;
Serial.println("PIR detected movement!");
mqttClient.publish(MQTT_PIR_STATUS, "Movement detected");
}
else if (pirState == LOW && pirIsDetected) {
pirIsDetected = false;
Serial.println("No movement detected by PIR.");
mqttClient.publish(MQTT_PIR_STATUS, "No movement");
}
}
void FishTank::loopOutputDevices()
{
Serial.println("--------------------------------");
int distance = getDistance();
int waterLevel = (distance == SONIC_INVALID_DISTANCE) ? 0 : distance;
if (waterLevel > WATER_LEVEL_THRESHOLD) {
if (!buzzerIsOn) {
buzzerIsOn = true;
digitalWrite(BUZZER_PIN, HIGH);
Serial.println("Buzzer ON! Water level too high!");
mqttClient.publish(MQTT_ALERT, "Water level too high!");
}
} else {
if (buzzerIsOn) {
buzzerIsOn = false;
digitalWrite(BUZZER_PIN, LOW);
Serial.println("Buzzer OFF. Water level normal.");
mqttClient.publish(MQTT_ALERT, "Water level normal.");
}
}
if (waterLevel < WATER_PUMP_THRESHOLD) {
digitalWrite(LED_PIN, HIGH);
mqttClient.publish(MQTT_LED_STATUS, "ON");
Serial.println("Pump ON - Water level low");
} else {
digitalWrite(LED_PIN, LOW);
mqttClient.publish(MQTT_LED_STATUS, "OFF");
Serial.println("Pump OFF - Water level adequate");
}
if (waterLevel < WATER_PUMP_THRESHOLD) {
digitalWrite(WATER_LED_PIN, HIGH);
} else {
digitalWrite(WATER_LED_PIN, LOW);
}
}
void FishTank::loopSchedule()
{
Serial.println("--------------------------------");
unsigned long currentMillis = millis();
if ((currentMillis - lastEverySensorRead >= SENSOR_READ_INTERVAL) || (lastEverySensorRead == 0))
{
lastEverySensorRead = currentMillis;
TempAndHumidity data = dht.getTempAndHumidity();
int photoVal = analogRead(PHOTO_PIN);
int distance = getDistance();
int waterLevel = (distance == SONIC_INVALID_DISTANCE) ? 0 : distance;
float temperature = data.temperature;
float humidity = data.humidity;
float pHValue = map(photoVal, PHOTO_MIN_VALUE, PHOTO_MAX_VALUE, PH_MAX_VALUE, PH_MIN_VALUE);
Serial.print("Photo value: ");
Serial.println(photoVal);
DateTime now = rtc.now();
mqttClient.publish(MQTT_CELSIUS_STATUS, String(temperature).c_str());
mqttClient.publish(MQTT_HUMID_STATUS, String(humidity).c_str());
mqttClient.publish(MQTT_PH_STATUS, String(pHValue).c_str());
mqttClient.publish(MQTT_WATER_STATUS, String(waterLevel).c_str());
StaticJsonDocument<256> doc;
doc["temperature"] = temperature;
doc["humidity"] = humidity;
doc["pH"] = pHValue;
doc["water"] = waterLevel;
doc["year"] = now.year();
doc["month"] = now.month();
doc["day"] = now.day();
doc["hour"] = now.hour();
doc["minute"] = now.minute();
doc["second"] = now.second();
char buffer[300];
size_t n = serializeJson(doc, buffer);
mqttClient.publish(MQTT_ALL_SENSORS_STATUS, buffer, n);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Temp:" + String(temperature) + "C");
lcd.setCursor(0, 1);
lcd.print("Hum:" + String(humidity) + "%");
}
}
void FishTank::loopWifi()
{
if (WiFi.status() != static_cast<int>(WL_CONNECTED)) {
Serial.println("================================================================");
Serial.print("Wifi is disconnected, trying to connect to WiFi...");
while (WiFi.status() != static_cast<int>(WL_CONNECTED)) {
delay(WIFI_CONNECT_TIMEOUT);
Serial.print(".");
}
Serial.print("\nConnected to WiFi: ");
Serial.println(WiFi.localIP());
}
}
void FishTank::loopMQTT()
{
if (mqttClient.connected()) {
mqttClient.loop();
return ;
}
unsigned long currentMillis = millis();
if (currentMillis - lastReconnected >= MQTT_RECONNECT_WAIT_MS) {
lastReconnected = currentMillis;
String clientId = "ESP32Client-" + String(random(0xffff), HEX);
Serial.println("================================================================");
Serial.println("MQTT is disconnected, trying to connect to MQTT...");
if (!mqttClient.connect(clientId.c_str())) {
Serial.println("Failed to connect to MQTT, rc=" + String(mqttClient.state()));
Serial.println("Will retry in " + String(MQTT_RECONNECT_WAIT_MS) + " milliseconds");
mqttClient.loop();
return ;
}
Serial.println("Successfully connected to MQTT server " + String(MQTT_SERVER));
for (const String& status : MQTT_STATUSES) {
DateTime now = rtc.now();
String timeStr = "SETUP " + status + " on ";
timeStr += String(now.year()) + "-" + String(now.month()) + "-" + String(now.day()) + " ";
timeStr += String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
mqttClient.subscribe(status.c_str());
mqttClient.publish(status.c_str(), timeStr.c_str());
Serial.println("Subscribed to topic: " + status);
}
for (const auto &user : BOT_VALID_USERS) {
String msg = "Welcome user#" + user.first + "[" + user.second + "], please use " + CMD_START + " " + userCaptcha[user.first];
sendMessage(user.first, msg, MQTT_GENERAL);
}
}
mqttClient.loop();
}
void FishTank::loopTelegramBot()
{
unsigned long currentMillis = millis();
if (lastBotRequest < currentMillis - BOT_DELAY_MS)
{
lastBotRequest = currentMillis;
Serial.println("--------------------------------");
Serial.println("Reading updates from Telegram");
int numNewMessages = teleClient.getUpdates(teleClient.last_message_received + 1);
Serial.println("Updating from Telegram with " + String(numNewMessages) + " new messages");
for (int id = 0; id < numNewMessages; ++id) {
const String chatId = String(teleClient.messages[id].chat_id);
const String message = teleClient.messages[id].text;
const String username = teleClient.messages[id].from_name;
int firstSpace = message.indexOf(' ');
String command = (firstSpace == NOT_FOUND) ? message : message.substring(0, firstSpace);
String params = (firstSpace == NOT_FOUND) ? "" : message.substring(firstSpace + 1);
auto fix = [](const String &raw) -> String {
String res;
for (char c : raw) if (strchr(BOT_VALID_TOKENS, c)) {
res += c;
}
return res;
};
handleBotCommand(chatId, username, fix(command), fix(params));
}
}
}
}
namespace IOT_06 // HELPERS
{
void FishTank::handleBotCommand(const String &chatId, const String &username, const String &command, const String ¶ms)
{
auto find_user = BOT_VALID_USERS.find(chatId);
if (find_user == BOT_VALID_USERS.end()) {
sendMessage(chatId, "Unknown user [" + username + "] with id [" + chatId + "]", MQTT_GENERAL);
return ;
}
String userMsg = "User#" + find_user->first + "[" + find_user->second + "]";
String cmdMsg = "command [" + command + "] with param=[" + params + "]";
String captchaMsg = CMD_START + " " + userCaptcha[chatId];
String preMsg = userMsg + " requests " + cmdMsg + "\n";
if (userCounter[chatId] == 0) {
if (command != CMD_START) {
String expect = "Expected the captcha command [" + captchaMsg + "] before any run";
sendMessage(chatId, preMsg + expect, MQTT_GENERAL);
return ;
}
if (params == userCaptcha[chatId]) {
sendMessage(chatId, preMsg + "User is authorized", MQTT_GENERAL);
}
else {
sendMessage(chatId, preMsg + "User is unauthorized, try [" + captchaMsg + "]", MQTT_GENERAL);
return ;
}
}
String cmdID = "Command#" + String(++userCounter[chatId]) + "[" + command + "]";
auto find_function = commandMap.find(command);
if (find_function != commandMap.end()) {
sendMessage(chatId, cmdID + " is being executed for " + userMsg, MQTT_GENERAL);
find_function->second.callback(chatId, params);
}
else {
sendMessage(chatId, cmdID + " is not found, sorry " + userMsg, MQTT_GENERAL);
}
}
void FishTank::sendMessage(const String& chatId, const String& msg, const String& publisher)
{
Serial.println("--------------------------------");
mqttClient.subscribe(MQTT_GENERAL);
mqttClient.publish(MQTT_GENERAL, "sendMessage() is called");
if (!publisher.isEmpty()) {
mqttClient.subscribe(publisher.c_str());
mqttClient.publish(publisher.c_str(), msg.c_str());
Serial.println(String("MQTT: \"") + publisher + "\"");
}
Serial.println(String("BOT: ") + msg);
teleClient.sendMessage(chatId, msg.c_str(), "");
}
}
namespace IOT_06 // GETTERS
{
int FishTank::getDistance()
{
digitalWrite(SONIC_TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(SONIC_TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(SONIC_TRIG_PIN, LOW);
long duration = pulseIn(SONIC_ECHO_PIN, HIGH);
if (duration <= 0) {
return SONIC_INVALID_DISTANCE;
}
return duration * 0.034 / 2;
}
CommandMapper FishTank::getCommandMap()
{
const Command commands[] = {
{"/relay_on", CMD_NOPARAM, CMD_NOPARAM, "Turn Relay ON", [this](const String &chatId, const String ¶ms) {
relayIsOn = true;
digitalWrite(LED_PIN, HIGH);
sendMessage(chatId, "Relay turned ON.", MQTT_LIGHT_STATUS);
}},
{"/relay_off", CMD_NOPARAM, CMD_NOPARAM, "Turn Relay OFF", [this](const String &chatId, const String ¶ms) {
relayIsOn = false;
digitalWrite(LED_PIN, LOW);
sendMessage(chatId, "Relay turned OFF.", MQTT_LIGHT_STATUS);
}},
{"/relay_toggle", CMD_NOPARAM, CMD_NOPARAM, "Toggle Relay State", [this](const String &chatId, const String ¶ms) {
relayIsOn ^= 1;
String stateStr = relayIsOn ? "ON" : "OFF";
sendMessage(chatId, "Relay toggled to " + stateStr + ".", MQTT_LIGHT_STATUS);
}},
{"/relay_state", CMD_NOPARAM, CMD_NOPARAM, "Get Relay Status", [this](const String &chatId, const String ¶ms) {
bool isOn = digitalRead(LED_PIN) == HIGH;
String stateStr = isOn ? "Relay is ON." : "Relay is OFF.";
sendMessage(chatId, stateStr, MQTT_LIGHT_STATUS);
}},
{"/led_on", CMD_NOPARAM, CMD_NOPARAM, "Turn LED ON", [this](const String &chatId, const String ¶ms) {
digitalWrite(LED_PIN, HIGH);
ledIsOn = true;
sendMessage(chatId, "LED turned ON.", MQTT_LED_STATUS);
}},
{"/led_off", CMD_NOPARAM, CMD_NOPARAM, "Turn LED OFF", [this](const String &chatId, const String ¶ms) {
digitalWrite(LED_PIN, LOW);
ledIsOn = false;
sendMessage(chatId, "LED turned OFF.", MQTT_LED_STATUS);
}},
{"/led_toggle", CMD_NOPARAM, CMD_NOPARAM, "Toggle LED State", [this](const String &chatId, const String ¶ms) {
bool currentLEDState = digitalRead(LED_PIN) == HIGH;
digitalWrite(LED_PIN, !currentLEDState ? HIGH : LOW);
ledIsOn = !currentLEDState;
String stateStr = ledIsOn ? "ON" : "OFF";
sendMessage(chatId, "LED toggled to " + stateStr + ".", MQTT_LED_STATUS);
}},
{"/led_status", CMD_NOPARAM, CMD_NOPARAM, "Get LED Status", [this](const String &chatId, const String ¶ms) {
bool isOn = digitalRead(LED_PIN) == HIGH;
String stateStr = isOn ? "LED is ON." : "LED is OFF.";
sendMessage(chatId, stateStr, MQTT_LED_STATUS);
}},
{"/ledring_poten", CMD_NOPARAM, CMD_NOPARAM, "Adjust LED Ring Brightness based on Potentiometer", [this](const String &chatId, const String ¶ms) {
int potValue = analogRead(POTEN_PIN);
lastRingBrightness = map(potValue, POTEN_MIN_VALUE, POTEN_MAX_VALUE, LEDRING_MIN_BRIGHT, LEDRING_MAX_BRIGHT);
ledring.setBrightness(lastRingBrightness);
ledring.show();
String msg = "LED Ring brightness adjusted based on potentiometer: " + String(lastRingBrightness);
sendMessage(chatId, msg, MQTT_LEDRING_STATUS);
}},
{"/ledring_on", CMD_NOPARAM, CMD_NOPARAM, "Turn LED Ring ON", [this](const String &chatId, const String ¶ms) {
lastRingBrightness = LEDRING_MAX_BRIGHT;
ledring.setBrightness(lastRingBrightness);
ledring.show();
sendMessage(chatId, "LED Ring turned ON.", MQTT_LEDRING_STATUS);
}},
{"/ledring_off", CMD_NOPARAM, CMD_NOPARAM, "Turn LED Ring OFF", [this](const String &chatId, const String ¶ms) {
lastRingBrightness = LEDRING_MIN_BRIGHT;
ledring.setBrightness(lastRingBrightness);
ledring.show();
sendMessage(chatId, "LED Ring turned OFF.", MQTT_LEDRING_STATUS);
}},
{"/ledring_brightness", "128", "<brightvalue>", "Set LED Ring Brightness (brightvalue: 0..255)", [this](const String &chatId, const String ¶ms) {
int brightvalue = constrain(params.toInt(), LEDRING_MIN_BRIGHT, LEDRING_MAX_BRIGHT);
lastRingBrightness = brightvalue;
ledring.setBrightness(lastRingBrightness);
ledring.show();
sendMessage(chatId, "LED Ring brightness set to " + String(lastRingBrightness) + ".", MQTT_LEDRING_STATUS);
}},
{"/ledring_white", CMD_NOPARAM, CMD_NOPARAM, "Set LED Ring Color To White", [this](const String &chatId, const String ¶ms) {
for (int i = 0; i < LEDRING_COUNT; i++) {
ledring.setPixelColor(i, ledring.Color(255, 255, 255));
}
ledring.show();
sendMessage(chatId, "LED Ring color set to WHITE.", MQTT_LEDRING_STATUS);
}},
{"/ledring_red", CMD_NOPARAM, CMD_NOPARAM, "Set LED Ring Color To Red", [this](const String &chatId, const String ¶ms) {
for (int i = 0; i < LEDRING_COUNT; i++) {
ledring.setPixelColor(i, ledring.Color(255, 0, 0));
}
ledring.show();
sendMessage(chatId, "LED Ring color set to RED.", MQTT_LEDRING_STATUS);
}},
{"/ledring_color", "#123456", "#RRGGBB", "Set LED Ring Color To Red", [this](const String &chatId, const String ¶ms) {
if (params.length() != 7 || params[0] != '#') {
sendMessage(chatId, "Invalid color format. Please use #RRGGBB.", MQTT_LEDRING_STATUS);
return;
}
for (int i = 1; i < 7; i++) {
if (!isxdigit(params[i])) {
sendMessage(chatId, "Invalid color format. Please use #RRGGBB.", MQTT_LEDRING_STATUS);
return;
}
}
int RR = strtol(params.substring(1, 3).c_str(), nullptr, 16);
int GG = strtol(params.substring(3, 5).c_str(), nullptr, 16);
int BB = strtol(params.substring(5, 7).c_str(), nullptr, 16);
for (int i = 0; i < LEDRING_COUNT; i++) {
ledring.setPixelColor(i, ledring.Color(RR, GG, BB));
}
ledring.show();
sendMessage(chatId, "LED Ring color set to " + params + ".", MQTT_LEDRING_STATUS);
}},
{"/ledring_status", CMD_NOPARAM, CMD_NOPARAM, "Get LED Ring Brightness", [this](const String &chatId, const String ¶ms) {
String statusStr = "LED Ring brightness level: " + String(lastRingBrightness);
sendMessage(chatId, statusStr, MQTT_LEDRING_STATUS);
}},
{"/servo_open", CMD_NOPARAM, CMD_NOPARAM, "Open Servo", [this](const String &chatId, const String ¶ms) {
if (servoIsOpen) {
sendMessage(chatId, "Servo is already OPENED.", MQTT_SERVO_STATUS);
}
else {
servoIsOpen = true;
for (int pos = SERVO_MIN_DEGREE; pos <= SERVO_MAX_DEGREE; ++pos) {
servo.write(pos);
delay(10);
}
sendMessage(chatId, "Servo opened.", MQTT_SERVO_STATUS);
}
}},
{"/servo_close", CMD_NOPARAM, CMD_NOPARAM, "Close Servo", [this](const String &chatId, const String ¶ms) {
if (!servoIsOpen) {
sendMessage(chatId, "Servo is already CLOSED.", MQTT_SERVO_STATUS);
}
else {
servoIsOpen = false;
for (int pos = SERVO_MAX_DEGREE; pos >= SERVO_MIN_DEGREE; --pos) {
servo.write(pos);
delay(10);
}
sendMessage(chatId, "Servo closed.", MQTT_SERVO_STATUS);
}
}},
{"/servo_toggle", CMD_NOPARAM, CMD_NOPARAM, "Toggle Servo", [this](const String &chatId, const String ¶ms) {
String statusStr = servoIsOpen ? "OPEN" : "CLOSED";
if (servoIsOpen) {
servoIsOpen = false;
for (int pos = SERVO_MAX_DEGREE; pos >= SERVO_MIN_DEGREE; --pos) {
servo.write(pos);
delay(10);
}
}
else {
servoIsOpen = true;
for (int pos = SERVO_MIN_DEGREE; pos <= SERVO_MAX_DEGREE; ++pos) {
servo.write(pos);
delay(10);
}
}
sendMessage(chatId, "Servo toggled to " + statusStr + ".", MQTT_SERVO_STATUS);
}},
{"/servo_status", CMD_NOPARAM, CMD_NOPARAM, "Get Servo Status", [this](const String &chatId, const String ¶ms) {
String msg = servoIsOpen ? "Servo is OPEN." : "Servo is CLOSED.";
sendMessage(chatId, msg, MQTT_SERVO_STATUS);
}},
{"/lcd_show", "1234567890123456789012345678901234567890", "<msg>", "Display Message on LCD, wrapped into two rows", [this](const String &chatId, const String ¶ms) {
lcd.clear();
if (params.length() > LCD_COLUMNS) {
String firstLine = params.substring(0, LCD_COLUMNS);
String secondLine = params.substring(LCD_COLUMNS);
lcd.setCursor(0, 0);
lcd.print(firstLine);
lcd.setCursor(0, 1);
lcd.print(secondLine);
} else {
lcd.setCursor(0, 0);
lcd.print(params);
}
sendMessage(chatId, "Displayed message on LCD", MQTT_LCD_STATUS);
}},
{"/lcd_show_1", "Row1 Example", "<msg>", "Display Message on LCD row 1", [this](const String &chatId, const String ¶ms) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(params);
sendMessage(chatId, "Displayed message on LCD row 1", MQTT_LCD_STATUS);
}},
{"/lcd_show_2", "Row2 Example", "<msg>", "Display Message on LCD row 2", [this](const String &chatId, const String ¶ms) {
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(params);
sendMessage(chatId, "Displayed message on LCD row 2", MQTT_LCD_STATUS);
}},
{"/lcd_clear", CMD_NOPARAM, CMD_NOPARAM, "Clear LCD Display", [this](const String &chatId, const String ¶ms) {
lcd.clear();
sendMessage(chatId, "LCD cleared.", MQTT_LCD_STATUS);
}},
{"/lcd_rtc", CMD_NOPARAM, CMD_NOPARAM, "Display Current Time on LCD", [this](const String &chatId, const String ¶ms) {
DateTime now = rtc.now();
char dateBuffer[20];
char timeBuffer[20];
lcd.clear();
snprintf(dateBuffer, sizeof(dateBuffer), "DATE=%02d/%02d/%04d", now.day(), now.month(), now.year());
lcd.setCursor(0, 0);
lcd.print(dateBuffer);
snprintf(timeBuffer, sizeof(timeBuffer), "TIME=%02d:%02d:%02d.00", now.hour(), now.minute(), now.second());
lcd.setCursor(0, 1);
lcd.print(timeBuffer);
String msg = "Displayed RTC date and time on LCD:\n" + String(dateBuffer) + " " + String(timeBuffer);
sendMessage(chatId, msg, MQTT_LCD_STATUS);
}},
{"/lcd_humid", CMD_NOPARAM, CMD_NOPARAM, "Display Current Humidity on LCD", [this](const String &chatId, const String ¶ms) {
TempAndHumidity data = dht.getTempAndHumidity();
char humidBuffer[20];
char floatBuffer[20];
lcd.clear();
snprintf(humidBuffer, sizeof(humidBuffer), "Humidity: %.1f%%", data.humidity);
lcd.setCursor(0, 0);
lcd.print(humidBuffer);
snprintf(floatBuffer, sizeof(floatBuffer), "=%.10f%", data.humidity / 100.0f);
lcd.setCursor(0, 1);
lcd.print(floatBuffer);
String msg = "Displayed humidity on LCD: " + String(humidBuffer) + "=" + String(floatBuffer);
sendMessage(chatId, msg, MQTT_LCD_STATUS);
}},
{"/lcd_temp", CMD_NOPARAM, CMD_NOPARAM, "Display Current Temperature on LCD", [this](const String &chatId, const String ¶ms) {
TempAndHumidity data = dht.getTempAndHumidity();
float tempC = data.temperature;
float tempF = tempC * 9.0 / 5.0 + 32.0;
char tempcBuffer[20];
char tempfBuffer[20];
lcd.clear();
snprintf(tempcBuffer, sizeof(tempcBuffer), "Temp: %.2f C", tempC);
lcd.setCursor(0, 0);
lcd.print(tempcBuffer);
snprintf(tempfBuffer, sizeof(tempfBuffer), "Temp: %.2f F", tempF);
lcd.setCursor(0, 1);
lcd.print(tempfBuffer);
String msg = "Displayed temperature on LCD: " + String(tempcBuffer) + "=" + String(tempfBuffer);
sendMessage(chatId, msg, MQTT_LCD_STATUS);
}},
{"/lcd_display", CMD_NOPARAM, CMD_NOPARAM, "Display Digits on LCD", [this](const String &chatId, const String ¶ms) {
String digits = params;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Digits: " + String(lastDigitsDisplay));
sendMessage(chatId, "Displayed digits on LCD: " + lastDigitsDisplay, MQTT_LCD_STATUS);
}},
{"/lcd_distance", CMD_NOPARAM, CMD_NOPARAM, "Display Ultrasonic Distance on LCD", [this](const String &chatId, const String ¶ms) {
int distance = getDistance();
if(distance != SONIC_INVALID_DISTANCE){
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Distance:");
lcd.setCursor(0, 1);
lcd.print(String(distance) + " cm");
sendMessage(chatId, "Displayed Ultrasonic distance on LCD: " + String(distance) + " cm", MQTT_LCD_STATUS);
}
else{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Distance Error");
sendMessage(chatId, "Error reading distance.", MQTT_LCD_STATUS);
}
}},
{"/lcd_poten", CMD_NOPARAM, CMD_NOPARAM, "Display Potentiometer Value on LCD", [this](const String &chatId, const String ¶ms) {
int potValue = analogRead(POTEN_PIN);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Potentiometer:");
lcd.setCursor(0, 1);
lcd.print(String(potValue));
sendMessage(chatId, "Displayed Potentiometer value on LCD: " + String(potValue), MQTT_LCD_STATUS);
}},
{"/display_number", "2702", "<number>", "Display Number on 7-Segment", [this](const String &chatId, const String ¶ms) {
int number = params.toInt();
if (number < 0 || number > 9999) {
sendMessage(chatId, "Invalid number for 7-Segment Display: " + String(number), MQTT_LEDSEGMENT_STATUS);
return;
}
lastDigitsDisplay = number;
ledsegment.showNumberDec(lastDigitsDisplay, false);
sendMessage(chatId, "Displayed number on 7-Segment Display: " + String(number), MQTT_LEDSEGMENT_STATUS);
}},
{"/display_clear", CMD_NOPARAM, CMD_NOPARAM, "Clear 7-Segment Display", [this](const String &chatId, const String ¶ms) {
ledsegment.clear();
lastDigitsDisplay = 0;
ledsegment.showNumberDec(lastDigitsDisplay, false);
sendMessage(chatId, "7-Segment Display cleared.", MQTT_LEDSEGMENT_STATUS);
}},
{"/ledbar_set", "2", "<ledlevel>", "Set LED Bar Graph (ledlevel: 0.." + String(LEDBAR_SIZE) + ")", [this](const String &chatId, const String ¶ms) {
int value = constrain(params.toInt(), 1, LEDBAR_SIZE);
for (int id = 0; id < LEDBAR_SIZE; id++) {
digitalWrite(LEDBAR_PINS[id], (id < value) ? HIGH : LOW);
}
sendMessage(chatId, "LED Bar Graph set to " + String(value) + " segments.", MQTT_LEDBAR_STATUS);
}},
{"/ledbar_photo", CMD_NOPARAM, CMD_NOPARAM, "Set LED Bar Graph based on Photoresistor", [this](const String &chatId, const String ¶ms) {
long lightLevel = analogRead(PHOTO_PIN);
int barCount = map(lightLevel, PHOTO_MIN_VALUE, PHOTO_MAX_VALUE, 0, LEDBAR_SIZE);
for (int id = 0; id < LEDBAR_SIZE; id++) {
digitalWrite(LEDBAR_PINS[id], (id < barCount) ? HIGH : LOW);
}
sendMessage(chatId, "LED Bar Graph updated based on photoresistor: " + String(barCount) + " segments.", MQTT_LEDBAR_STATUS);
}},
{"/ledbar_full", CMD_NOPARAM, CMD_NOPARAM, "Set all LED Bar Graph", [this](const String &chatId, const String ¶ms) {
for (int id = 0; id < LEDBAR_SIZE; id++) {
digitalWrite(LEDBAR_PINS[id], HIGH);
}
sendMessage(chatId, "LED Bar Graph set to full.", MQTT_LEDBAR_STATUS);
}},
{"/ledbar_clear", CMD_NOPARAM, CMD_NOPARAM, "Clear LED Bar Graph", [this](const String &chatId, const String ¶ms) {
for (int id = 0; id < LEDBAR_SIZE; id++) {
digitalWrite(LEDBAR_PINS[id], LOW);
}
sendMessage(chatId, "LED Bar Graph cleared.", MQTT_LEDBAR_STATUS);
}},
{"/distance_read", CMD_NOPARAM, CMD_NOPARAM, "Get Ultrasonic Sensor Distance", [this](const String &chatId, const String ¶ms) {
int distance = getDistance();
if(distance != SONIC_INVALID_DISTANCE){
sendMessage(chatId, "Current Distance: " + String(distance) + " cm.", MQTT_SWITCH_STATUS); // Assuming SWITCH_STATUS as distance status
}
else{
sendMessage(chatId, "Error reading distance.", MQTT_SWITCH_STATUS);
}
}},
{"/sensor_read", CMD_NOPARAM, CMD_NOPARAM, "Get Temperature, Humidity, Light Level, Potentiometer, and Distance", [this](const String &chatId, const String ¶ms) {
TempAndHumidity data = dht.getTempAndHumidity();
long lightLevel = analogRead(PHOTO_PIN);
long potValue = analogRead(POTEN_PIN);
int distance = getDistance();
float tempC = data.temperature;
float tempF = tempC * 9.0 / 5.0 + 32.0;
String sensorData = "Temperature: " + String(tempC, 2) + "*C = " + String(tempF, 2) + "*F |\n";
sensorData += "Humidity: " + String(data.humidity, 1) + "% |\n";
sensorData += "Light Level: " + String(lightLevel) + " |\n";
sensorData += "Potentiometer: " + String(potValue) + " |\n";
sensorData += "Distance: " + ((distance != SONIC_INVALID_DISTANCE) ? String(distance) + " cm" : "Error");
sendMessage(chatId, sensorData, MQTT_DHT_STATUS);
}},
{"/sensor_celsius", CMD_NOPARAM, CMD_NOPARAM, "Get Temperature and Humidity", [this](const String &chatId, const String ¶ms) {
TempAndHumidity data = dht.getTempAndHumidity();
String sensorData = "Temperature: " + String(data.temperature, 2) + "*C";
sendMessage(chatId, sensorData, MQTT_CELSIUS_STATUS);
}},
{"/sensor_humid", CMD_NOPARAM, CMD_NOPARAM, "Get Temperature and Humidity", [this](const String &chatId, const String ¶ms) {
TempAndHumidity data = dht.getTempAndHumidity();
String sensorData = "Humidity: " + String(data.humidity, 1) + "%";
sendMessage(chatId, sensorData, MQTT_HUMID_STATUS);
}},
{"/rtc_date", CMD_NOPARAM, CMD_NOPARAM, "Get Current RTC Date", [this](const String &chatId, const String ¶ms) {
DateTime now = rtc.now();
String timeStr = "Current RTC Date: ";
timeStr += String(now.year()) + "-" + String(now.month()) + "-" + String(now.day()) + " ";
sendMessage(chatId, timeStr, MQTT_RTC_STATUS);
}},
{"/rtc_time", CMD_NOPARAM, CMD_NOPARAM, "Get Current RTC Time", [this](const String &chatId, const String ¶ms) {
DateTime now = rtc.now();
String timeStr = "Current RTC Time: ";
timeStr += String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
sendMessage(chatId, timeStr, MQTT_RTC_STATUS);
}},
{"/rtc_datetime", CMD_NOPARAM, CMD_NOPARAM, "Get Current RTC DateTime", [this](const String &chatId, const String ¶ms) {
DateTime now = rtc.now();
String timeStr = "Current RTC DateTime: ";
timeStr += String(now.year()) + "-" + String(now.month()) + "-" + String(now.day()) + " ";
timeStr += String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
sendMessage(chatId, timeStr, MQTT_RTC_STATUS);
}},
{"/rtc_set", "2024-12-31 23:59:59", "<YYYY-MM-DD HH:MM:SS>", "Set system time", [this](const String &chatId, const String ¶ms) {
int year, month, day, hour, minute, second;
if (sscanf(params.c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second) != 6) {
sendMessage(chatId, "Invalid time format. Use YYYY-MM-DD HH:MM:SS.", MQTT_RTC_STATUS);
return;
}
rtc.adjust(DateTime(year, month, day, hour, minute, second));
sendMessage(chatId, "System time set to " + params, MQTT_RTC_STATUS);
}},
{"/switch_toggle", CMD_NOPARAM, CMD_NOPARAM, "Reset the setup process", [this](const String &chatId, const String ¶ms) {
setup();
sendMessage(chatId, "Reset ESP32", MQTT_SWITCH_STATUS);
}},
{"/button_press", CMD_NOPARAM, CMD_NOPARAM, "Toggle Servo", [this](const String &chatId, const String ¶ms) {
if (servoIsOpen) {
servoIsOpen = false;
for (int pos = SERVO_MAX_DEGREE; pos >= SERVO_MIN_DEGREE; --pos) {
servo.write(pos);
delay(10);
}
}
else {
servoIsOpen = true;
for (int pos = SERVO_MIN_DEGREE; pos <= SERVO_MAX_DEGREE; ++pos) {
servo.write(pos);
delay(10);
}
}
String statusStr = servoIsOpen ? "OPEN" : "CLOSED";
sendMessage(chatId, "Servo toggled to " + statusStr + " via /button_press.", MQTT_SERVO_STATUS);
}},
{"/test_servo", CMD_NOPARAM, CMD_NOPARAM, "Test Servo Movement", [this](const String &chatId, const String ¶ms) {
sendMessage(chatId, "Starting servo test.", MQTT_SERVO_STATUS);
for (int pos = 0; pos <= 180; pos += 1) {
servo.write(pos);
delay(10);
}
for (int pos = 180; pos >= 0; pos -= 1) {
servo.write(pos);
delay(10);
}
sendMessage(chatId, "Servo test completed.", MQTT_SERVO_STATUS);
}},
{"/pump_on", CMD_NOPARAM, CMD_NOPARAM, "Turn the water pump ON", [this](const String &chatId, const String ¶ms) {
digitalWrite(LED_PIN, HIGH);
sendMessage(chatId, "Water pump turned ON.", MQTT_WATER_STATUS);
}},
{"/pump_off", CMD_NOPARAM, CMD_NOPARAM, "Turn the water pump OFF", [this](const String &chatId, const String ¶ms) {
digitalWrite(LED_PIN, LOW);
sendMessage(chatId, "Water pump turned OFF.", MQTT_WATER_STATUS);
}},
{"/water_level", CMD_NOPARAM, CMD_NOPARAM, "Check current water level", [this](const String &chatId, const String ¶ms) {
int waterLevel = getDistance();
String status = (waterLevel == SONIC_INVALID_DISTANCE) ? "Error reading water level" : "Water level: " + String(waterLevel) + " cm";
sendMessage(chatId, status, MQTT_WATER_STATUS);
}},
};
CommandMapper cmdMap;
for (const auto& cmd : commands)
{
cmdMap[cmd.command] = cmd;
}
cmdMap["/test_all"] = Command{
"/test_all", CMD_NOPARAM, CMD_NOPARAM, "Test all no param commands", [this](const String &chatId, const String ¶ms) {
for (const auto& pair : commandMap) {
const Command cmd = pair.second;
if (cmd.command == CMD_START || cmd.command == "/test_all") {
continue;
}
if (cmd.params == CMD_NOPARAM) {
sendMessage(chatId, "/test_all is testing \"" + cmd.command + "\"", MQTT_GENERAL);
commandMap[cmd.command].callback(chatId, params);
}
}
sendMessage(chatId, "/test_all is finished", MQTT_GENERAL);
}
};
cmdMap["/start"] = Command{
"/start", CMD_NOPARAM, CMD_NOPARAM, "Display help", [this](const String &chatId, const String ¶ms) {
String welcome = "Available Commands:\n\n";
for (const auto& pair : commandMap) {
const Command cmd = pair.second;
if (cmd.params == CMD_NOPARAM) {
welcome += "[" + cmd.command + "]";
}
else {
welcome += "[" + cmd.command + " " + cmd.params + "]";
}
welcome += " -> " + cmd.description;
if (cmd.params != CMD_NOPARAM) {
welcome += " (example: " + cmd.command + " " + cmd.initial + ")";
}
welcome += "\n";
}
sendMessage(chatId, welcome, MQTT_GENERAL);
}
};
return cmdMap;
}
}
IOT_06::FishTank iotFishTank;
void setup()
{
iotFishTank.setup();
}
void loop()
{
iotFishTank.loop();
}