#include <WiFi.h>             // Бібліотека для роботи з Wi-Fi.
#include <PubSubClient.h>     // Бібліотека для роботи з протоколом MQTT (публікація та підписка).

#define LIGHT_PIN 16          // Пін для керування світлодіодом.
#define PIR_PIN 19            // Пін для датчика руху (PIR).
#define ECHO_PIN 17           // Пін для приймача ультразвукового датчика.
#define TRIG_PIN 18           // Пін для передавача ультразвукового датчика.

#define WIFI_SSID "Wokwi-GUEST"  // Назва мережі Wi-Fi.
#define WIFI_PW ""               // Пароль до Wi-Fi (порожній у цьому прикладі).
#define MQTT_BROKER "broker.mqtt-dashboard.com"  // Адреса MQTT брокера.
#define MQTT_PORT 1883                            // Порт MQTT брокера.

int val = 0;              // Змінна для збереження відстані, виміряної ультразвуковим датчиком.
bool pir = false;         // Флаг для стану датчика руху.
bool LED = false;         // Стан світлодіода (ввімкнено/вимкнено).
unsigned long lastTime1 = 0;  // Час останньої взаємодії ультразвукового датчика.
unsigned long lastTime2 = 0;  // Час останнього виявлення руху PIR-датчиком.
int on_Off = 0;           // Стан ввімкнення/вимкнення через MQTT.
int time_ = 10000;        // Таймер у мілісекундах для автоматичного вимкнення світлодіода.

WiFiClient espClient;     // Об'єкт для роботи з Wi-Fi клієнтом.
PubSubClient client(espClient);  // Об'єкт для роботи з MQTT клієнтом.

void setup() {
  Serial.begin(9600);  // Ініціалізація серійного зв'язку на швидкості 9600 біт/с.

  pinMode(LIGHT_PIN, OUTPUT);   // Налаштування піна для світлодіода як вихідного.
  pinMode(PIR_PIN, INPUT);      // Налаштування піна для датчика руху як вхідного.
  pinMode(TRIG_PIN, OUTPUT);    // Налаштування TRIG піна ультразвукового датчика як вихідного.
  pinMode(ECHO_PIN, INPUT);     // Налаштування ECHO піна ультразвукового датчика як вхідного.

  WiFi.begin(WIFI_SSID, WIFI_PW);  // Початок підключення до Wi-Fi.
  while (WiFi.status() != WL_CONNECTED) {  // Чекати, поки пристрій не підключиться до Wi-Fi.
    delay(250);  // Затримка для перевірки підключення.
    Serial.print(".");  // Виведення прогресу підключення.
  }
  Serial.println("\nConnected to hotspot:");  // Підтвердження підключення.
  Serial.println(WIFI_SSID);  // Виведення імені Wi-Fi.
  Serial.print("IP address is: ");
  Serial.println(WiFi.localIP());  // Виведення IP-адреси пристрою.
  Serial.println("###");

  client.setServer(MQTT_BROKER, MQTT_PORT);  // Налаштування сервера MQTT брокера.
  client.setCallback(callback);  // Вказання функції зворотного виклику для обробки повідомлень.

  while (!client.connected()) {  // Чекати підключення до MQTT брокера.
    Serial.println("Connecting to MQTT...");
    if (client.connect("espGood", "Qwerty12", "Qwerty12")) { 
      Serial.println("Connected to broker");  // Успішне підключення.
      client.subscribe("On_Off");  // Підписка на топік для керування вмиканням/вимиканням.
      client.subscribe("Time");    // Підписка на топік для зміни часу автоматичного вимкнення.
    } else {
      Serial.print("Failed with state ");
      Serial.println(client.state());  // Виведення помилки підключення.
      delay(2000);  // Затримка перед повторною спробою.
    }
  }
}

void loop() {
  val = readDistanceCm();  // Зчитування відстані з ультразвукового датчика.
  if ((val < 10 && (millis() - lastTime1) > 2000) || (on_Off == 1)) { 
    // Якщо відстань менше 10 см або отримано команду ввімкнення через MQTT.
    on_Off = 0;  // Скидання флагу команди.
    LED = !LED;  // Зміна стану світлодіода.
    lastTime1 = millis();  // Оновлення часу взаємодії.
    if (LED) {
      client.publish("LIGHT_", "1");  // Надсилання повідомлення про ввімкнення.
    } else {
      client.publish("LIGHT_", "0");  // Надсилання повідомлення про вимкнення.
    }
    digitalWrite(LIGHT_PIN, LED);  // Зміна стану світлодіода.
  }

  pir = digitalRead(PIR_PIN);  // Зчитування стану PIR-датчика.
  if (pir) {
    lastTime2 = millis();  // Оновлення часу останнього руху.
  }

  if (((millis() - lastTime2) > time_) && LED == true) {
    // Якщо перевищено час автоматичного вимкнення та світлодіод ввімкнено.
    LED = false;  // Вимикання світлодіода.
    client.publish("LIGHT_", "0");  // Надсилання повідомлення про вимкнення.
    digitalWrite(LIGHT_PIN, LED);  // Фізичне вимкнення світлодіода.
  }

  client.loop();  // Обробка MQTT-з'єднання.
}

int readDistanceCm() {
  digitalWrite(TRIG_PIN, LOW);  // Встановлення TRIG в низький стан.
  delayMicroseconds(2);  // Затримка 2 мкс.
  digitalWrite(TRIG_PIN, HIGH);  // Встановлення TRIG у високий стан.
  delayMicroseconds(10);  // Затримка 10 мкс для запуску імпульсу.
  digitalWrite(TRIG_PIN, LOW);  // Встановлення TRIG назад у низький стан.
  int duration = pulseIn(ECHO_PIN, HIGH);  // Вимірювання часу затримки відбитого сигналу.
  return duration * 0.034 / 2;  // Розрахунок відстані у см.
}

void callback(char* topic, byte* payload, unsigned int length) {
  // Обробка повідомлень з топіків MQTT.
  String valueString = "";  // Рядок для збереження отриманого повідомлення.
  Serial.print("Message arrived in topic: ");
  Serial.println(topic);  // Виведення топіка.
  Serial.print("Message: ");

  for (int i = 0; i < length; i++) {
    valueString += (char)payload[i];  // Конвертація повідомлення з байтів у текст.
  }
  Serial.println(valueString);

  if (String(topic) == "On_Off") {
    if (valueString == "1") {
      on_Off = 1;  // Увімкнення через MQTT.
    }
  }

  if (String(topic) == "Time") {
    time_ = valueString.toInt();  // Зміна таймера автоматичного вимкнення.
    if (time_ < 0) { 
      time_ = 10000;  // Перевірка на коректність, встановлення значення за замовчуванням.
    }
  }

  delay(3000);  // Затримка обробки (рекомендовано уникати таких затримок у callback).
}
NOCOMNCVCCGNDINLED1PWRRelay Module