#include <UniversalTelegramBot.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_NeoPixel.h>
#include <WiFiClientSecure.h>
#include <TM1637Display.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP32Servo.h>
#include <DHTesp.h>
#include <RTClib.h>
#include <WiFi.h>
#include <Wire.h>
#include <map>
namespace IOT_06 // Embedded
{
enum BUTTON : uint8_t // MODEL: TACTILE_MOMENTARYPUSH_BUTTON
{
BUTTON_PIN = 0,
};
enum DHT22 : uint8_t // MODEL: DHT22_AM2302
{
DHT_PIN = 32,
};
enum LED : uint8_t // MODEL: STANDARD_5MM
{
ON = HIGH,
OFF = LOW,
LED_PIN = 14,
};
enum SWITCH : uint8_t // MODEL: SPDT
{
SWITCH_PIN = 12,
DEBOUNCE_DELAY = 50,
};
enum SEGMENTLED : uint8_t // MODEL: TM1637_4DIGIT_7SEGMENT
{
CLK_PIN = 15,
DIO_PIN = 2,
};
enum PROXIMITY : int8_t // MODEL: HCSR04
{
ECHO_PIN = 18,
TRIG_PIN = 19,
INVALID_DISTANCE = -1,
TIMEOUT_SECONDS = 10,
};
enum LCD1602 : uint8_t // MODEL:PCF8574T_I2C
{
ADDRESS = 0x27,
COLUMNS = 20,
ROWS = 2,
SDA_PIN = 23,
SCL_PIN = 22,
};
enum LEDRING : uint8_t // MODEL: WS2812
{
STATUS_OFF = 0,
STATUS_DIM = 1,
STATUS_ON = 2,
RING_PIN = 21,
RING_COUNT = 40,
MAX_BRIGHTNESS = 255,
DIM_BRIGHTNESS = MAX_BRIGHTNESS / 2,
MIN_BRIGHTNESS = 0,
};
enum SERVO : uint16_t // MODEL: SG90_MICRO
{
SERVO_PIN = 4,
DELAY_MS = 1000,
MIN_PULSE = 500,
MAX_PULSE = 2500,
MIN_DEGREE = 0,
MAX_DEGREE = 180,
PERIOD_HERTZ = 50,
};
enum PHOTO : uint16_t // MODEL: STANDARD_LDR
{
PHOTO_PIN = 17,
PHOTO_MIN_VALUE = 0,
PHOTO_MAX_VALUE = 4095,
};
enum POTEN : uint16_t // MODEL: LINEAR10K
{
POT_PIN = 33,
POT_MIN_VALUE = 0,
POT_MAX_VALUE = 4095,
};
namespace LEDBAR // MODEL: 10SEGMENT
{
constexpr int SIZE = 3;
constexpr int BAR_PINS[SIZE] = {25, 26, 27};
constexpr int MIN_ID = 0;
constexpr int MAX_ID = SIZE - 1;
}
enum WOKWI : int
{
BAUD_RATE_BPS = 115200,
FORCED_CRASH_LOOP = -1,
};
namespace MQTT
{
constexpr const int CONNECTION_TIMEOUT_MS = 1000;
constexpr const int RECONNECT_WAIT_MS = 5000;
constexpr const int TIMER_WAIT_MS = 2000;
constexpr const int DEFAULT_PORT = 1883;
constexpr const int SECURE_PORT = 8883;
constexpr const char* SSID = "Wokwi-GUEST";
constexpr const char* PASSWORD = "";
constexpr const char* SERVER = "test.mosquitto.org";
constexpr const char* LIGHT_COMMAND = "fish_tank/device/light/command";
constexpr const char* SERVO_COMMAND = "fish_tank/device/servo/command";
constexpr const char* LED_COMMAND = "fish_tank/device/led/command";
constexpr const char* SERVO_TIMER = "fish_tank/device/servo/timer";
constexpr const char* LIGHT_TIMER = "fish_tank/device/light/timer";
constexpr const char* LED_TIMER = "fish_tank/device/led/timer";
constexpr const char* LIGHT_STATUS = "fish_tank/device/light/status";
constexpr const char* SERVO_STATUS = "fish_tank/device/servo/status";
constexpr const char* LED_STATUS = "fish_tank/device/led/status";
constexpr const char* DISTANCE_THRESHOLD = "fish_tank/device/distance/threshold";
constexpr const char* LED_BAR_STATUS = "fish_tank/device/ledbar/status";
constexpr const char* temp_threshold_celsius = "fish_tank/device/sensor/temp_threshold_celsius";
constexpr const char* humid_threshold = "fish_tank/device/sensor/hum_threshold";
constexpr const char* SWITCH_STATUS = "fish_tank/device/switch/status";
constexpr const char* ULTRASONIC_SENSOR = "fish_tank/sensor/ultrasonic/proximity";
constexpr const char* PHOTO_SENSOR = "fish_tank/sensor/photo/light_level";
constexpr const char* TEMP_SENSOR = "fish_tank/sensor/dht22/temp";
constexpr const char* POT_SENSOR = "fish_tank/sensor/potentiometer/value";
constexpr const char* HUM_SENSOR = "fish_tank/sensor/dht22/hum";
constexpr const char* PH_SENSOR = "fish_tank/sensor/pH";
}
namespace TELEGRAM_BOT
{
constexpr const char *TOKEN = "8141850946:AAFi6xstIWzd-sDCuZP8N7sBgQ4VIxET9wg";
constexpr const char *CHAT_ID = "5721869894";
constexpr const int REQUEST_DELAY_MS = 100;
}
using VoidFunction = std::function<void(const String&, const String&)>;
using CommandMapper = std::map<String, VoidFunction>;
class FishTank
{
public: // Cores
FishTank();
void setup();
void loop();
private: // Servers
WiFiClient wifiClient;
PubSubClient mqttClient;
WiFiClientSecure hostClient;
UniversalTelegramBot teleClient;
private: // Devices
Servo servo;
DHTesp dht;
RTC_DS3231 rtc;
LiquidCrystal_I2C lcd;
float temp_threshold_celsius = 25.0;
float humid_threshold = 60.0;
int distance_threshold_cm = 50;
int currentRingBrightness = 0;
bool buttonState;
private: // LEDs
LED led;
Adafruit_NeoPixel ledring;
TM1637Display ledsegment;
private: // States
bool ledStatus;
bool wifiStatus;
bool servoStatus;
bool switchStatus;
bool ledTimerStatus;
bool ringTimerStatus;
bool servoTimerStatus;
bool servoMovingStatus;
private: // Internalities
unsigned long lastSwitch;
unsigned long lastMessage;
unsigned long lastPotValue;
unsigned long lastServoMove;
unsigned long lastBotRequest;
unsigned long lastReconnected;
private: // Timers
unsigned long ledTimer;
unsigned long ringTimer;
unsigned long servoTimer;
private: // Commands
CommandMapper commandMap;
private: // Initializers
void setupServers();
void setupDevices();
void setupLeds();
private: // Handlers
void handleMQTT();
void handleDHT22();
void handleSwitch();
void handleButtons();
void handleTelegram();
void handleProximity();
void handleLightLevel();
void handlePhotoSensor();
void handleServoActions();
void handlePotentiometer();
private: // Bot Actions
void botCommandAll(int numNewMessages);
void botCommandMessage(const telegramMessage &botMessage);
void botCommandLed(const String& message);
void botCommandServo(const String& message);
void botCommandLedRing(const String& message);
private: // Updaters
void updateLedTimer(const String& message);
void updateRingTimer(const String& message);
void updateServoTimer(const String& message);
void updateLEDBar(int lightLevel);
private: // Helpers
void reconnectMQTT();
void subscribeToMQTTTopics();
void mqttCallback(char* topic, byte* payload, unsigned int length);
void displayTime();
void openServo();
void closeServo();
void setPixelRingBrightness(int brightness);
void displayRTCOnLCD();
private: // Getters
int getDistance();
CommandMapper getCommandMap();
};
}
namespace IOT_06
{
FishTank::FishTank():
// Servers
wifiClient(),
mqttClient(wifiClient),
hostClient(),
teleClient({TELEGRAM_BOT::TOKEN, hostClient}),
/// Devices
servo(),
dht(),
rtc(),
lcd(LCD1602::ADDRESS, LCD1602::COLUMNS, LCD1602::ROWS),
// Leds
led(LED::OFF),
ledring(LEDRING::RING_COUNT, LEDRING::RING_PIN),
ledsegment(SEGMENTLED::CLK_PIN, SEGMENTLED::DIO_PIN),
// States
ledStatus(false),
wifiStatus(false),
servoStatus(false),
switchStatus(false),
ledTimerStatus(false),
ringTimerStatus(false),
servoTimerStatus(false),
servoMovingStatus(false),
// Internalities
lastSwitch(0),
lastMessage(0),
lastPotValue(0),
lastServoMove(0),
lastBotRequest(0),
lastReconnected(0),
// Timers
ledTimer(0),
ringTimer(0),
servoTimer(0),
// Commands
commandMap(getCommandMap())
{
Serial.begin(WOKWI::BAUD_RATE_BPS);
}
void FishTank::setup()
{
setupServers();
setupDevices();
setupLeds();
}
void FishTank::loop()
{
handleMQTT();
handleDHT22();
handleSwitch();
handleButtons();
handleTelegram();
handleProximity();
handleLightLevel();
handlePhotoSensor();
handleServoActions();
handlePotentiometer();
}
void FishTank::setupServers()
{
hostClient.setCACert(TELEGRAM_CERTIFICATE_ROOT);
mqttClient.setServer(MQTT::SERVER, MQTT::DEFAULT_PORT);
mqttClient.setCallback([this](char* topic, byte* payload, unsigned int length) {
this->mqttCallback(topic, payload, length);
});
WiFi.mode(WIFI_STA);
WiFi.begin(MQTT::SSID, MQTT::PASSWORD);
while (WiFi.status() != static_cast<int>(WL_CONNECTED))
{
wifiStatus = false;
delay(MQTT::CONNECTION_TIMEOUT_MS);
Serial.println("Connecting to WiFi..");
}
wifiStatus = true;
Serial.println(WiFi.localIP());
}
void FishTank::setupDevices()
{
pinMode(SWITCH::SWITCH_PIN, INPUT);
pinMode(PROXIMITY::TRIG_PIN, OUTPUT);
pinMode(PROXIMITY::ECHO_PIN, INPUT);
Wire.begin(LCD1602::SDA_PIN, LCD1602::SCL_PIN);
delay(100);
dht.setup(DHT22::DHT_PIN, DHTesp::DHT22);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("LCD Initialized");
lcd.setCursor(0, 1);
lcd.print("Hello World!");
servo.attach(SERVO::SERVO_PIN, MIN_PULSE, MAX_PULSE);
servo.setPeriodHertz(SERVO::PERIOD_HERTZ);
servo.write(SERVO::MIN_DEGREE);
servoStatus = false;
delay(100);
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
while (WOKWI::FORCED_CRASH_LOOP);
}
if (rtc.lostPower()) {
Serial.println("RTC lost power, setting the time!");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
ledsegment.setBrightness(0x0f);
ledsegment.showNumberDec(0, false);
}
void FishTank::setupLeds()
{
pinMode(LED::LED_PIN, OUTPUT);
for (int i = 0; i < LEDBAR::SIZE; i++) {
pinMode(LEDBAR::BAR_PINS[i], OUTPUT);
}
pinMode(LEDRING::RING_PIN, OUTPUT);
for (int i = 0; i < LEDBAR::SIZE; i++) {
digitalWrite(LEDBAR::BAR_PINS[i], HIGH);
}
ledring.begin();
ledring.show();
}
void FishTank::handleMQTT()
{
if (!mqttClient.connected()) {
reconnectMQTT();
}
mqttClient.loop();
unsigned long currentMillis = millis();
if (currentMillis - lastMessage > MQTT::TIMER_WAIT_MS) {
lastMessage = currentMillis;
TempAndHumidity data = dht.getTempAndHumidity();
mqttClient.publish(MQTT::TEMP_SENSOR, String(data.temperature, 2).c_str());
mqttClient.publish(MQTT::HUM_SENSOR, String(data.humidity, 1).c_str());
if (ledTimerStatus && currentMillis >= ledTimer) {
digitalWrite(LED::LED_PIN, LED::ON);
ledStatus = true;
ledTimerStatus = false;
mqttClient.publish(MQTT::LED_STATUS, "ON");
}
if (servoTimerStatus && currentMillis >= servoTimer) {
openServo();
servoTimerStatus = false;
mqttClient.publish(MQTT::SERVO_STATUS, "OPEN");
}
if (ringTimerStatus && currentMillis >= ringTimer) {
setPixelRingBrightness(LEDRING::MAX_BRIGHTNESS);
ringTimerStatus = false;
mqttClient.publish(MQTT::LIGHT_STATUS, "ON");
}
}
}
void FishTank::handleDHT22()
{
TempAndHumidity data = dht.getTempAndHumidity();
float phValue = float(lastPotValue) / POTEN::POT_MAX_VALUE * 14.0;
mqttClient.publish(MQTT::TEMP_SENSOR, String(data.temperature, 2).c_str());
mqttClient.publish(MQTT::HUM_SENSOR, String(data.humidity, 1).c_str());
mqttClient.publish(MQTT::PH_SENSOR, String(phValue, 2).c_str());
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(data.temperature, 2);
lcd.print(" C ");
lcd.setCursor(0, 1);
lcd.print("Humidity: ");
lcd.print(data.humidity, 1);
lcd.print(" % ");
}
void FishTank::handleSwitch()
{
bool slideStatus = digitalRead(SWITCH::SWITCH_PIN);
if (slideStatus != switchStatus) {
if (millis() - lastSwitch > SWITCH::DEBOUNCE_DELAY) {
lastSwitch = millis();
switchStatus = slideStatus;
if (slideStatus == HIGH) {
digitalWrite(LED::LED_PIN, LED::ON);
ledStatus = true;
mqttClient.publish(MQTT::LED_STATUS, "ON");
} else {
digitalWrite(LED::LED_PIN, LED::OFF);
ledStatus = false;
mqttClient.publish(MQTT::LED_STATUS, "OFF");
}
}
}
}
void FishTank::handleButtons()
{
bool currentButtonState = digitalRead(BUTTON::BUTTON_PIN);
if (currentButtonState != buttonState) {
lastSwitch = millis();
if (currentButtonState == HIGH) {
Serial.println("Button pressed.");
if (!servoStatus) {
openServo();
}
else {
closeServo();
}
}
buttonState = currentButtonState;
}
if (millis() - lastSwitch < SWITCH::DEBOUNCE_DELAY) {
return;
}
}
void FishTank::handleTelegram()
{
if (millis() > lastBotRequest + TELEGRAM_BOT::REQUEST_DELAY_MS)
{
int numNewMessages = teleClient.getUpdates(teleClient.last_message_received + 1);
while (numNewMessages)
{
botCommandAll(numNewMessages);
numNewMessages = teleClient.getUpdates(teleClient.last_message_received + 1);
}
lastBotRequest = millis();
}
}
void FishTank::handleProximity()
{
int distance = getDistance();
if (distance == PROXIMITY::INVALID_DISTANCE) {
Serial.println("Error: Invalid ultrasonic sensor reading!");
mqttClient.publish("error/log", "Ultrasonic sensor error");
return;
}
bool isNear = (distance <= distance_threshold_cm);
if (isNear) {
setPixelRingBrightness(LEDRING::MAX_BRIGHTNESS);
mqttClient.publish(MQTT::ULTRASONIC_SENSOR, "near");
} else {
setPixelRingBrightness(LEDRING::MIN_BRIGHTNESS);
mqttClient.publish(MQTT::ULTRASONIC_SENSOR, "far");
}
}
void FishTank::handleLightLevel()
{
int lightLevel = analogRead(PHOTO::PHOTO_PIN);
if (lightLevel < PHOTO::PHOTO_MIN_VALUE || lightLevel > PHOTO::PHOTO_MAX_VALUE) {
Serial.println("Error: Invalid photoresistor value!");
mqttClient.publish("error/log", "Photoresistor reading out of range");
return;
}
updateLEDBar(lightLevel);
mqttClient.publish(MQTT::PHOTO_SENSOR, String(lightLevel).c_str());
}
void FishTank::handlePhotoSensor() {
int lightLevel = analogRead(PHOTO::PHOTO_PIN);
mqttClient.publish(MQTT::PHOTO_SENSOR, String(lightLevel).c_str());
}
void FishTank::handleServoActions()
{
if (servoMovingStatus && (millis() - lastServoMove >= SERVO::DELAY_MS)) {
servoMovingStatus = false;
}
}
void FishTank::handlePotentiometer()
{
int newPotValue = analogRead(POTEN::POT_PIN);
if (newPotValue != lastPotValue) {
lastPotValue = newPotValue;
int brightness = map(newPotValue,
POTEN::POT_MIN_VALUE, POTEN::POT_MAX_VALUE,
LEDRING::MIN_BRIGHTNESS, LEDRING::MAX_BRIGHTNESS
);
setPixelRingBrightness(brightness);
mqttClient.publish(MQTT::POT_SENSOR, String(lastPotValue).c_str());
mqttClient.publish("fish_tank/device/pixel_ring/brightness", String(brightness).c_str());
}
}
void FishTank::botCommandAll(int numNewMessages)
{
for (int i = 0; i < numNewMessages; ++i)
{
botCommandMessage(teleClient.messages[i]);
}
}
void FishTank::botCommandMessage(const telegramMessage &botMessage)
{
const String chatId = String(botMessage.chat_id);
const String text = botMessage.text;
const String username = botMessage.from_name;
if (chatId != TELEGRAM_BOT::CHAT_ID) {
teleClient.sendMessage(chatId, "Unauthorized user", "");
return;
}
// Split the text into command and parameters
int firstSpace = text.indexOf(' ');
String command = (firstSpace == -1) ? text : text.substring(0, firstSpace);
String params = (firstSpace == -1) ? "" : text.substring(firstSpace + 1);
auto it = commandMap.find(command);
bool isValid = (it != commandMap.end());
String validity = isValid ? "valid" : "invalid, please use \"/start\" to show help";
DateTime now = rtc.now();
char timestamp[32];
snprintf(timestamp, sizeof(timestamp), "[%02d:%02d:%02d.%03d]", now.hour(), now.minute(), now.second(), now.unixtime() % 1000);
String logMessage = "[" + String(timestamp) + "] Bot read user [" + username + "] ";
logMessage += "command \"" + command + "\", it is " + validity;
teleClient.sendMessage(chatId, logMessage, "");
Serial.printf("%s\n", logMessage.c_str());
if (isValid) {
it->second(chatId, params);
}
}
void FishTank::botCommandLed(const String& message)
{
if (message.equals("ON")) {
digitalWrite(LED::LED_PIN, HIGH);
ledStatus = true;
mqttClient.publish(MQTT::LED_STATUS, "ON");
} else if (message.equals("OFF")) {
digitalWrite(LED::LED_PIN, LOW);
ledStatus = false;
mqttClient.publish(MQTT::LED_STATUS, "OFF");
}
}
void FishTank::botCommandServo(const String& message)
{
if (message.equalsIgnoreCase("OPEN")) {
openServo();
} else if (message.equalsIgnoreCase("CLOSE")) {
closeServo();
}
String status = servoStatus ? "OPEN" : "CLOSED";
mqttClient.publish(MQTT::SERVO_STATUS, status.c_str());
Serial.println("Servo command received: " + status);
}
void FishTank::botCommandLedRing(const String& message)
{
if (message.equals("ON")) {
setPixelRingBrightness(LEDRING::MAX_BRIGHTNESS);
mqttClient.publish(MQTT::LIGHT_STATUS, "ON");
} else if (message.equals("OFF")) {
setPixelRingBrightness(LEDRING::MIN_BRIGHTNESS);
mqttClient.publish(MQTT::LIGHT_STATUS, "OFF");
}
}
void FishTank::updateLedTimer(const String& message)
{
if (message.length() > 0) {
int timerSeconds = message.toInt();
if (timerSeconds > 0) {
ledTimer = millis() + static_cast<unsigned long>(timerSeconds) * 1000;
ledTimerStatus = true;
}
}
}
void FishTank::updateServoTimer(const String& message)
{
if (message.length() > 0) {
int timerSeconds = message.toInt();
if (timerSeconds > 0) {
servoTimer = millis() + static_cast<unsigned long>(timerSeconds) * 1000;
servoTimerStatus = true;
}
}
}
void FishTank::updateLEDBar(int lightLevel)
{
int activeLEDs = map(lightLevel,
PHOTO::PHOTO_MIN_VALUE, PHOTO::PHOTO_MAX_VALUE,
LEDBAR::MIN_ID, LEDBAR::MAX_ID + 1
);
for (int id = LEDBAR::MIN_ID; id <= LEDBAR::MAX_ID; id++) {
digitalWrite(LEDBAR::BAR_PINS[id], (id < activeLEDs) ? HIGH : LOW);
}
}
void FishTank::updateRingTimer(const String& message)
{
if (message.length() > 0) {
int timerSeconds = message.toInt();
if (timerSeconds > 0) {
ringTimer = millis() + static_cast<unsigned long>(timerSeconds) * 1000;
ringTimerStatus = true;
}
}
}
void FishTank::reconnectMQTT()
{
if (mqttClient.connected()) {
return;
}
unsigned long currentMillis = millis();
if (currentMillis - lastReconnected > MQTT::RECONNECT_WAIT_MS) {
lastReconnected = currentMillis;
Serial.print("Attempting MQTT connection...\n");
String clientId = "ESP32Client-";
clientId += String(random(0xffff), HEX);
if (mqttClient.connect(clientId.c_str())) {
Serial.println("Successfully connected to MQTT server");
subscribeToMQTTTopics();
} else {
Serial.print("Failed to connect to MQTT, rc=");
Serial.print(mqttClient.state());
Serial.println(" Will retry in 5 seconds");
}
}
}
void FishTank::subscribeToMQTTTopics()
{
mqttClient.subscribe(MQTT::LED_COMMAND);
mqttClient.subscribe(MQTT::SERVO_COMMAND);
mqttClient.subscribe(MQTT::LIGHT_COMMAND);
mqttClient.subscribe(MQTT::LED_TIMER);
mqttClient.subscribe(MQTT::SERVO_TIMER);
mqttClient.subscribe(MQTT::LIGHT_TIMER);
}
void FishTank::mqttCallback(char* topic, byte* payload, unsigned int length)
{
String message;
for (unsigned int i = 0; i < length; i++) {
message += static_cast<char>(payload[i]);
}
Serial.printf("MQTT Message received on topic %s: %s\n", topic, message.c_str());
if (String(topic).equals(MQTT::LED_COMMAND)) {
botCommandLed(message);
} else if (String(topic).equals(MQTT::SERVO_COMMAND)) {
botCommandServo(message);
} else if (String(topic).equals(MQTT::LIGHT_COMMAND)) {
botCommandLedRing(message);
} else if (String(topic).equals(MQTT::LED_TIMER)) {
updateLedTimer(message);
} else if (String(topic).equals(MQTT::SERVO_TIMER)) {
updateServoTimer(message);
} else if (String(topic).equals(MQTT::LIGHT_TIMER)) {
updateRingTimer(message);
} else {
String errorMsg = "Invalid command received on topic: \"" + String(topic) + "\"";
mqttClient.publish("error/log", errorMsg.c_str());
}
}
void FishTank::displayTime()
{
DateTime now = rtc.now();
int displayTime = (now.hour() * 100) + now.minute();
ledsegment.showNumberDec(displayTime, true);
}
void FishTank::openServo()
{
servo.write(SERVO::MAX_DEGREE);
delay(100);
}
void FishTank::closeServo() {
servo.write(SERVO::MIN_DEGREE);
delay(100);
}
void FishTank::setPixelRingBrightness(int brightness)
{
brightness = constrain(brightness, LEDRING::MIN_BRIGHTNESS, LEDRING::MAX_BRIGHTNESS);
currentRingBrightness = brightness;
for (int i = 0; i < LEDRING::RING_COUNT; i++) {
ledring.setPixelColor(i, ledring.Color(brightness, brightness, brightness));
}
ledring.show();
}
void FishTank::displayRTCOnLCD()
{
DateTime now = rtc.now();
lcd.setCursor(0, 1);
lcd.print("Time: ");
lcd.print(now.hour());
lcd.print(":");
lcd.print(now.minute());
lcd.print(":");
lcd.print(now.second());
lcd.print(" ");
}
int FishTank::getDistance()
{
digitalWrite(PROXIMITY::TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(PROXIMITY::TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(PROXIMITY::TRIG_PIN, LOW);
long timeout_us = PROXIMITY::TIMEOUT_SECONDS * 1'000'000UL;
long duration = pulseIn(PROXIMITY::ECHO_PIN, HIGH, timeout_us);
if (duration <= 0) {
return PROXIMITY::INVALID_DISTANCE;
}
return duration * 0.034 / 2;
}
CommandMapper FishTank::getCommandMap()
{
struct Command
{
const String command;
VoidFunction handler;
};
const Command commands[] = {
// Welcome and Help
{"/start", [this](const String &chatId, const String ¶ms) {
String welcome = "Welcome to the FishTank Control Bot!\n\n";
welcome += "Available Commands:\n";
welcome += "/relay_on - Turn Relay ON\n";
welcome += "/relay_off - Turn Relay OFF\n";
welcome += "/relay_toggle - Toggle Relay State\n";
welcome += "/relay_state - Get Relay Status\n\n";
welcome += "/led_on - Turn LED ON\n";
welcome += "/led_off - Turn LED OFF\n";
welcome += "/led_toggle - Toggle LED State\n";
welcome += "/led_status - Get LED Status\n\n";
welcome += "/ledring_on - Turn LED Ring ON\n";
welcome += "/ledring_off - Turn LED Ring OFF\n";
welcome += "/ledring_brightness <value> - Set LED Ring Brightness (0-255)\n";
welcome += "/ledring_status - Get LED Ring Brightness\n\n";
welcome += "/servo_open - Open Servo\n";
welcome += "/servo_close - Close Servo\n";
welcome += "/servo_status - Get Servo Status\n\n";
welcome += "/lcd_show <message> - Display Message on LCD\n";
welcome += "/lcd_clear - Clear LCD Display\n\n";
welcome += "/display_number <number> - Display Number on 7-Segment\n";
welcome += "/display_clear - Clear 7-Segment Display\n\n";
welcome += "/ledbar_set <value> - Set LED Bar Graph (1-" + String(LEDBAR::SIZE) + ")\n";
welcome += "/ledbar_clear - Clear LED Bar Graph\n\n";
welcome += "/distance_read - Get Ultrasonic Sensor Distance\n";
welcome += "/distance_threshold <value> - Set Distance Threshold\n\n";
welcome += "/sensor_read - Get Temperature and Humidity\n";
welcome += "/sensor_set <parameter> <value> - Set Sensor Parameters\n\n";
welcome += "/rtc_set <YYYY-MM-DD HH:MM:SS> - Set RTC Time\n";
welcome += "/rtc_time - Get RTC Time\n\n";
welcome += "/switch_toggle - Toggle Slide Switch\n";
welcome += "/switch_status - Get Slide Switch Status\n\n";
welcome += "/button_press - Simulate Button Press\n";
welcome += "/button_status - Get Button Status\n";
teleClient.sendMessage(chatId, welcome, "");
}},
// Relay Commands
{"/relay_on", [this](const String &chatId, const String ¶ms) {
teleClient.sendMessage(chatId, "Relay turned ON.", "");
digitalWrite(LED::LED_PIN, HIGH);
ledStatus = true;
mqttClient.publish(MQTT::LED_STATUS, "ON");
}},
{"/relay_off", [this](const String &chatId, const String ¶ms) {
teleClient.sendMessage(chatId, "Relay turned OFF.", "");
digitalWrite(LED::LED_PIN, LOW);
ledStatus = false;
mqttClient.publish(MQTT::LED_STATUS, "OFF");
}},
{"/relay_toggle", [this](const String &chatId, const String ¶ms) {
bool currentState = digitalRead(LED::LED_PIN) == HIGH;
digitalWrite(LED::LED_PIN, !currentState ? HIGH : LOW);
ledStatus = !currentState;
String stateStr = ledStatus ? "ON" : "OFF";
teleClient.sendMessage(chatId, "Relay toggled to " + stateStr + ".", "");
mqttClient.publish(MQTT::LED_STATUS, stateStr.c_str());
}},
{"/relay_state", [this](const String &chatId, const String ¶ms) {
bool isOn = digitalRead(LED::LED_PIN) == HIGH;
String stateStr = isOn ? "Relay is ON." : "Relay is OFF.";
teleClient.sendMessage(chatId, stateStr, "");
}},
// LED Commands
{"/led_on", [this](const String &chatId, const String ¶ms) {
teleClient.sendMessage(chatId, "LED turned ON.", "");
digitalWrite(LED::LED_PIN, HIGH);
ledStatus = true;
mqttClient.publish(MQTT::LED_STATUS, "ON");
}},
{"/led_off", [this](const String &chatId, const String ¶ms) {
teleClient.sendMessage(chatId, "LED turned OFF.", "");
digitalWrite(LED::LED_PIN, LOW);
ledStatus = false;
mqttClient.publish(MQTT::LED_STATUS, "OFF");
}},
{"/led_toggle", [this](const String &chatId, const String ¶ms) {
bool currentState = digitalRead(LED::LED_PIN) == HIGH;
digitalWrite(LED::LED_PIN, !currentState ? HIGH : LOW);
ledStatus = !currentState;
String stateStr = ledStatus ? "ON" : "OFF";
teleClient.sendMessage(chatId, "LED toggled to " + stateStr + ".", "");
mqttClient.publish(MQTT::LED_STATUS, stateStr.c_str());
}},
{"/led_status", [this](const String &chatId, const String ¶ms) {
bool isOn = digitalRead(LED::LED_PIN) == HIGH;
String stateStr = isOn ? "LED is ON." : "LED is OFF.";
teleClient.sendMessage(chatId, stateStr, "");
}},
// LED Ring Commands
{"/ledring_on", [this](const String &chatId, const String ¶ms) {
teleClient.sendMessage(chatId, "LED Ring turned ON.", "");
setPixelRingBrightness(LEDRING::MAX_BRIGHTNESS);
mqttClient.publish(MQTT::LIGHT_STATUS, "ON");
}},
{"/ledring_off", [this](const String &chatId, const String ¶ms) {
teleClient.sendMessage(chatId, "LED Ring turned OFF.", "");
setPixelRingBrightness(LEDRING::MIN_BRIGHTNESS);
mqttClient.publish(MQTT::LIGHT_STATUS, "OFF");
}},
{"/ledring_brightness", [this](const String &chatId, const String ¶ms) {
int brightness = params.toInt();
brightness = constrain(brightness, LEDRING::MIN_BRIGHTNESS, LEDRING::MAX_BRIGHTNESS);
setPixelRingBrightness(brightness);
teleClient.sendMessage(chatId, "LED Ring brightness set to " + String(brightness) + ".", "");
mqttClient.publish(MQTT::LIGHT_STATUS, String(brightness).c_str());
}},
{"/ledring_status", [this](const String &chatId, const String ¶ms) {
teleClient.sendMessage(chatId, "LED Ring is set to brightness level " + String(currentRingBrightness) + ".", "");
}},
// Servo Commands
{"/servo_open", [this](const String &chatId, const String ¶ms) {
openServo();
teleClient.sendMessage(chatId, "Servo opened.", "");
mqttClient.publish(MQTT::SERVO_STATUS, "OPEN");
}},
{"/servo_close", [this](const String &chatId, const String ¶ms) {
closeServo();
teleClient.sendMessage(chatId, "Servo closed.", "");
mqttClient.publish(MQTT::SERVO_STATUS, "CLOSED");
}},
{"/servo_status", [this](const String &chatId, const String ¶ms) {
String statusStr = servoStatus ? "Servo is OPEN." : "Servo is CLOSED.";
teleClient.sendMessage(chatId, statusStr, "");
}},
// LCD Commands
{"/lcd_show", [this](const String &chatId, const String ¶ms) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(params);
teleClient.sendMessage(chatId, "Displayed message on LCD.", "");
}},
{"/lcd_clear", [this](const String &chatId, const String ¶ms) {
lcd.clear();
teleClient.sendMessage(chatId, "LCD cleared.", "");
}},
// 7-Segment Display Commands
{"/display_number", [this](const String &chatId, const String ¶ms) {
int number = params.toInt();
if (number < 0 || number > 9999) {
teleClient.sendMessage(chatId, "Please enter a number between 0 and 9999.", "");
return;
}
ledsegment.showNumberDec(number, false);
teleClient.sendMessage(chatId, "Displayed number on 7-Segment Display.", "");
}},
{"/display_clear", [this](const String &chatId, const String ¶ms) {
ledsegment.clear();
teleClient.sendMessage(chatId, "7-Segment Display cleared.", "");
}},
// LED Bar Graph Commands
{"/ledbar_set", [this](const String &chatId, const String ¶ms) {
int value = params.toInt();
value = constrain(value, 1, LEDBAR::SIZE); // Adjust based on LEDBAR::SIZE
for(int i = 0; i < LEDBAR::SIZE; ++i){
digitalWrite(LEDBAR::BAR_PINS[i], i < value ? HIGH : LOW);
}
teleClient.sendMessage(chatId, "LED Bar Graph set to " + String(value) + " segments.", "");
mqttClient.publish(MQTT::LED_BAR_STATUS, String(value).c_str());
}},
{"/ledbar_clear", [this](const String &chatId, const String ¶ms) {
for(int i = 0; i < LEDBAR::SIZE; ++i){
digitalWrite(LEDBAR::BAR_PINS[i], LOW);
}
teleClient.sendMessage(chatId, "LED Bar Graph cleared.", "");
mqttClient.publish(MQTT::LED_BAR_STATUS, "0");
}},
// Distance Sensor Commands
{"/distance_read", [this](const String &chatId, const String ¶ms) {
int distance = getDistance();
if(distance != PROXIMITY::INVALID_DISTANCE){
teleClient.sendMessage(chatId, "Current Distance: " + String(distance) + " cm.", "");
}
else{
teleClient.sendMessage(chatId, "Error reading distance.", "");
}
}},
{"/distance_threshold", [this](const String &chatId, const String ¶ms) {
int threshold = params.toInt();
distance_threshold_cm = threshold;
teleClient.sendMessage(chatId, "Distance threshold set to " + String(threshold) + " cm.", "");
mqttClient.publish(MQTT::DISTANCE_THRESHOLD, String(threshold).c_str());
}},
// Sensor Commands
{"/sensor_read", [this](const String &chatId, const String ¶ms) {
TempAndHumidity data = dht.getTempAndHumidity();
float phValue = float(lastPotValue) / POTEN::POT_MAX_VALUE * 14.0;
String sensorData = "Temperature: " + String(data.temperature, 2) + " C\n";
sensorData += "Humidity: " + String(data.humidity, 1) + " %\n";
sensorData += "pH Level: " + String(phValue, 2);
teleClient.sendMessage(chatId, sensorData, "");
}},
{"/sensor_set", [this](const String &chatId, const String ¶ms) {
// Example: /sensor_set temp_threshold_celsius 30
int firstSpace = params.indexOf(' ');
String param1 = (firstSpace == -1) ? params : params.substring(0, firstSpace);
String param2 = (firstSpace == -1) ? "" : params.substring(firstSpace + 1);
if(param1.equalsIgnoreCase("temp_threshold_celsius")){
float threshold = param2.toFloat();
temp_threshold_celsius = threshold;
teleClient.sendMessage(chatId, "Temperature threshold set to " + String(threshold) + " C.", "");
mqttClient.publish(MQTT::temp_threshold_celsius, String(threshold).c_str());
}
else if(param1.equalsIgnoreCase("hum_threshold")){
float threshold = param2.toFloat();
humid_threshold = threshold;
teleClient.sendMessage(chatId, "Humidity threshold set to " + String(threshold) + " %.", "");
mqttClient.publish(MQTT::humid_threshold, String(threshold).c_str());
}
else{
teleClient.sendMessage(chatId, "Invalid sensor parameter.", "");
}
}},
// RTC Commands
{"/rtc_set", [this](const String &chatId, const String ¶ms) {
// Expected format: YYYY-MM-DD HH:MM:SS
int year, month, day, hour, minute, second;
sscanf(params.c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second);
rtc.adjust(DateTime(year, month, day, hour, minute, second));
teleClient.sendMessage(chatId, "RTC time set to " + params + ".", "");
}},
{"/rtc_time", [this](const String &chatId, const String ¶ms) {
DateTime now = rtc.now();
String timeStr = "Current RTC Time: ";
timeStr += String(now.year()) + "-" + String(now.month()) + "-" + String(now.day()) + " ";
timeStr += String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
teleClient.sendMessage(chatId, timeStr, "");
}},
// Slide Switch Commands
{"/switch_toggle", [this](const String &chatId, const String ¶ms) {
bool currentState = digitalRead(SWITCH::SWITCH_PIN) == HIGH;
digitalWrite(SWITCH::SWITCH_PIN, !currentState ? HIGH : LOW);
switchStatus = !currentState;
String stateStr = switchStatus ? "ON" : "OFF";
teleClient.sendMessage(chatId, "Slide Switch toggled to " + stateStr + ".", "");
mqttClient.publish(MQTT::SWITCH_STATUS, stateStr.c_str());
}},
{"/switch_status", [this](const String &chatId, const String ¶ms) {
bool isOn = digitalRead(SWITCH::SWITCH_PIN) == HIGH;
String stateStr = isOn ? "Slide Switch is ON." : "Slide Switch is OFF.";
teleClient.sendMessage(chatId, stateStr, "");
}},
// Pushbutton Commands
{"/button_press", [this](const String &chatId, const String ¶ms) {
// Simulating a button press might involve toggling a state or triggering an action
// Depending on your hardware setup
// Note: Physical buttons are inputs; remote simulation depends on hardware capabilities
teleClient.sendMessage(chatId, "Button press simulation not implemented.", "");
}},
{"/button_status", [this](const String &chatId, const String ¶ms) {
bool isPressed = digitalRead(BUTTON::BUTTON_PIN) == HIGH; // Adjust based on wiring
String statusStr = isPressed ? "Button is PRESSED." : "Button is RELEASED.";
teleClient.sendMessage(chatId, statusStr, "");
}}
};
CommandMapper cmdMap;
for (const auto& cmd : commands)
{
cmdMap[cmd.command] = cmd.handler;
}
return cmdMap;
}
}
IOT_06::FishTank iotFishTank;
void setup()
{
iotFishTank.setup();
}
void loop()
{
iotFishTank.loop();
}