#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <U8g2lib.h>
#include <ArduinoOTA.h>
#include <time.h>

// Определяем пины для программного I2C на ESP-01S
#define I2C_SCL_PIN 2 // GPIO2 (SCL)
#define I2C_SDA_PIN 0 // GPIO0 (SDA)

// Определение пина для светодиода
#define LED_PIN 2 // GPIO2, встроенный светодиод

// Определение пина для зуммера
#define BUZZER_PIN 0 // GPIO0

// Инициализация дисплея с использованием программного I2C
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, I2C_SCL_PIN, I2C_SDA_PIN, U8X8_PIN_NONE);

// Данные для подключения к Wi-Fi
const char* ssid = "Mesto_Chistoty";
const char* password = "Mesto13092022";

// Учетные данные для веб-сервера
const char* www_username = "admin";
const char* www_password = "35539900";

// Учетные данные для OTA
const char* otaHostname = "ESP01S_AlarmClock"; // Имя устройства в сети
const char* otaPassword = "OTApassword123"; // Пароль для OTA

// Инициализация веб-сервера на порту 80
ESP8266WebServer server(80);

// Настройки NTP-клиента
const long utcOffsetInSeconds = 3 * 3600; // Московское время UTC+3
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);

// Переменная для хранения времени будильника
String alarmTime = "";

// Переменные для управления миганием светодиода
unsigned long previousMillisLED = 0;
const long intervalLED = 1000; // Интервал мигания в миллисекундах (1 секунда)
bool ledState = false;

// Глобальные переменные для управления будильником
bool alarmActive = false;
unsigned long alarmStartTime = 0;
const unsigned long alarmDuration = 10000; // 10 секунд

// Переменные для управления звуком будильника
unsigned long buzzerPreviousMillis = 0;
int buzzerState = 0;
const unsigned long beepOnDuration = 200;   // Длительность писка (200 мс)
const unsigned long beepOffDuration = 100;  // Пауза между писками (100 мс)
const unsigned long pauseDuration = 1500;   // Пауза после двойного писка (1500 мс)

// Новые глобальные переменные для анимации котика
unsigned long previousMillisCat = 0;
const unsigned long catFrameInterval = 500; // Интервал смены кадров (мс)
unsigned int currentCatFrame = 0;

// Определение анимационных кадров котика
#define CAT_ANIMATION_FRAMES 2

// Кадр 1: Простое лицо котика
const unsigned char cat_frame1[] U8G2_FONT_SECTION("cat_frame1") = 
  "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
  "\x00\x07\xE0\x00\x07\xE0\x00\x00\x00\x00"
  "\x03\xFF\xC0\x03\xFF\xC0\x00\x00\x00\x00"
  "\x07\xFF\xE0\x07\xFF\xE0\x00\x00\x00\x00"
  "\x0F\xFF\xF0\x0F\xFF\xF0\x00\x00\x00\x00"
  "\x1E\x1F\x1E\x1E\x1F\x1E\x00\x00\x00\x00"
  "\x3C\x00\x3C\x3C\x00\x3C\x00\x00\x00\x00"
  "\x78\x00\x78\x78\x00\x78\x00\x00\x00\x00"
  "\xF0\x00\xF0\xF0\x00\xF0\x00\x00\x00\x00";

// Кадр 2: Немного изменённое лицо котика (например, мигнуть глазами)
const unsigned char cat_frame2[] U8G2_FONT_SECTION("cat_frame2") = 
  "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
  "\x00\x07\xE0\x00\x07\xE0\x00\x00\x00\x00"
  "\x03\xFF\xC0\x03\xFF\xC0\x00\x00\x00\x00"
  "\x07\xFF\xE0\x07\xFF\xE0\x00\x00\x00\x00"
  "\x0F\xFF\xF0\x0F\xFF\xF0\x00\x00\x00\x00"
  "\x1E\x0F\x1E\x1E\x0F\x1E\x00\x00\x00\x00"
  "\x3C\x00\x3C\x3C\x00\x3C\x00\x00\x00\x00"
  "\x78\x00\x78\x78\x00\x78\x00\x00\x00\x00"
  "\xF0\x00\xF0\xF0\x00\xF0\x00\x00\x00\x00";

// Массив указателей на кадры анимации
const unsigned char* catFrames[CAT_ANIMATION_FRAMES] = { cat_frame1, cat_frame2 };
const unsigned int catFrameWidths[CAT_ANIMATION_FRAMES] = { 10, 10 }; // Ширина каждого кадра (в байтах)
const unsigned int catFrameHeights[CAT_ANIMATION_FRAMES] = { 9, 9 }; // Высота каждого кадра (в пикселях)

// Прототипы функций
void handleRoot();
void handleSetAlarm();
void handleTime();
void checkAlarm();
void handleActiveAlarm();
void drawAlarmIcon(int x, int y);
void displayTime();
void displayOn();
void displayOff();
void displayCatAnimation();

// HTML-код для веб-страницы с отображением времени и даты
const char* htmlPage = R"=====(
<!DOCTYPE html>
<html>
<head>
  <title>Alarm Clock Settings</title>
  <style>
    body {
      background-color: #1E1E1E;
      color: #FFFFFF;
      font-family: Arial, sans-serif;
      text-align: center;
      padding-top: 50px;
    }
    h1 {
      color: #FF4081;
    }
    #time {
      font-size: 48px;
      margin: 20px 0;
    }
    #date {
      font-size: 32px;
      margin: 10px 0;
    }
    input[type=time] {
      padding: 10px;
      font-size: 18px;
      margin: 20px 0;
    }
    input[type=submit] {
      background-color: #FF4081;
      color: #FFFFFF;
      border: none;
      padding: 15px 30px;
      font-size: 18px;
      cursor: pointer;
    }
    input[type=submit]:hover {
      background-color: #E91E63;
    }
  </style>
</head>
<body>
  <h1>Alarm Clock</h1>
  <div id="time">--:--:--</div>
  <div id="date">--.--.----</div>
  <form action="/setAlarm" method="POST">
    <label for="alarm">Alarm Time:</label><br>
    <input type="time" id="alarm" name="alarm" required><br>
    <input type="submit" value="Set Alarm">
  </form>
  <script>
    function updateTime() {
      fetch('/time')
        .then(response => response.text())
        .then(dateTime => {
          let [time, date] = dateTime.split(' ');
          document.getElementById('time').textContent = time;
          document.getElementById('date').textContent = date;
        })
        .catch(err => console.error(err));
    }
    setInterval(updateTime, 1000); // Обновляем время каждую секунду
    updateTime(); // Обновляем сразу после загрузки страницы
  </script>
</body>
</html>
)=====";

// Функция setup()
void setup() {
  // Инициализация Serial Monitor
  Serial.begin(115200);

  // Инициализация дисплея
  u8g2.begin();

  // Настройка пина для светодиода
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);  // Выключаем светодиод (активный LOW)

  // Настройка пина для зуммера
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW); // Выключаем зуммер

  // Подключение к Wi-Fi
  Serial.print("Подключение к Wi-Fi...");
  WiFi.begin(ssid, password);

  // Ожидание подключения
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nПодключено к Wi-Fi сети.");
  Serial.print("IP адрес: ");
  Serial.println(WiFi.localIP());

  // Запуск NTP-клиента
  timeClient.begin();

  // Настройка обработчиков для веб-сервера
  server.on("/", handleRoot);
  server.on("/setAlarm", HTTP_POST, handleSetAlarm);
  server.on("/time", handleTime); // Обработчик для отправки текущего времени и даты

  // Настройка OTA
  ArduinoOTA.setHostname(otaHostname);
  ArduinoOTA.setPassword(otaPassword);

  // Обработчик начала обновления
  ArduinoOTA.onStart ( []  {
    String updateType;
    if (ArduinoOTA.getCommand() == U_FLASH)
      updateType = "sketch";
    else // U_SPIFFS
      updateType = "filesystem";

    // NOTE: если вы обновляете SPIFFS, убедитесь, что вы больше ничего не делаете в этих обработчиках
    Serial.println("Начало обновления: " + updateType);
  } ) ;

  // Обработчик завершения обновления
  ArduinoOTA.onEnd( [] {
    Serial.println("\nОбновление завершено.");
  })  ;

  // Обработчик прогресса обновления
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Обновление: %u%%\r", (progress * 100) / total);
  });

  // Обработчик ошибок обновления
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Ошибка[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Ошибка аутентификации");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Ошибка начала");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Ошибка подключения");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Ошибка приема");
    else if (error == OTA_END_ERROR) Serial.println("Ошибка завершения");
  });

  // Инициализация OTA
  ArduinoOTA.begin();
  Serial.println("OTA настроен и готов к использованию.");

  // Запуск веб-сервера
  server.begin();
  Serial.println("Веб-сервер запущен.");
}

// Функция loop()
void loop() {
  // Обработка OTA-запросов
  ArduinoOTA.handle();

  // Обновление времени через NTP
  timeClient.update();

  // Обработка запросов к веб-серверу
  server.handleClient();

  // Проверка будильника
  checkAlarm();

  // Обработка будильника, если он активен
  if (alarmActive) {
    handleActiveAlarm();
  } else {
    // Отображение текущего времени на дисплее
    displayTime();
  }

  // Мигание светодиода
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillisLED >= intervalLED) {
    previousMillisLED = currentMillis;
    // Инвертируем состояние светодиода
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState ? LOW : HIGH);  // Светодиод активен при LOW
  }
}

// Функции для управления питанием дисплея
void displayOn() {
  // Убедитесь, что дисплей включен (если вы используете дополнительные схемы)
  // В данном случае дисплей включён постоянно, поэтому функция может быть пустой
}

void displayOff() {
  // В данном случае дисплей включён постоянно, поэтому функция может быть пустой
  // Если у вас есть способ отключить питание дисплея, добавьте соответствующий код здесь
  // Например:
  // digitalWrite(DISPLAY_POWER_PIN, LOW);
  // u8g2.clearBuffer();
  // u8g2.sendBuffer();
}

// Функция для отображения времени и даты на OLED
void displayTime() {
  // Получаем текущее время в секундах с 1 января 1970 года
  time_t rawTime = timeClient.getEpochTime();

  // Конвертируем в структуру tm
  struct tm * timeInfo = localtime(&rawTime);

  // Форматируем время и дату
  char timeString[9]; // "HH:MM:SS"
  char dateString[11]; // "DD.MM.YYYY"

  strftime(timeString, sizeof(timeString), "%H:%M:%S", timeInfo);
  strftime(dateString, sizeof(dateString), "%d.%m.%Y", timeInfo);

  // Отображение времени и даты на OLED-дисплее
  u8g2.clearBuffer();

  // Устанавливаем шрифт для времени
  u8g2.setFont(u8g2_font_fub14_tr);  // Меньший шрифт для отображения секунд

  // Вычисляем позицию для центрирования времени
  int16_t timeWidth = u8g2.getStrWidth(timeString);
  int16_t timeX = (128 - timeWidth) / 2;

  // Вывод времени
  u8g2.drawStr(timeX, 20, timeString);

  // Устанавливаем тонкий шрифт для даты
  u8g2.setFont(u8g2_font_helvR12_tr);

  // Вычисляем позицию для центрирования даты
  int16_t dateWidth = u8g2.getStrWidth(dateString);
  int16_t dateX = (128 - dateWidth) / 2;

  // Вывод даты
  u8g2.drawStr(dateX, 38, dateString);

  // Проверяем, установлен ли будильник
  if (alarmTime != "") {
    // Преобразуем время будильника в char*
    char alarmTimeString[6];
    alarmTime.toCharArray(alarmTimeString, sizeof(alarmTimeString));

    // Устанавливаем шрифт для времени будильника
    u8g2.setFont(u8g2_font_helvR12_tr);

    // Определяем позицию для отображения будильника
    int iconWidth = 12; // Примерная ширина иконки
    int alarmTimeWidth = u8g2.getStrWidth(alarmTimeString);
    int totalWidth = iconWidth + 2 + alarmTimeWidth;
    int startX = (128 - totalWidth) / 2;
    int iconX = startX;
    int iconY = 52;

    // Рисуем иконку будильника
    drawAlarmIcon(iconX, iconY);

    // Отображаем время будильника рядом с иконкой
    u8g2.drawStr(iconX + iconWidth + 2, iconY, alarmTimeString);
  }

  // Отправляем буфер на дисплей
  u8g2.sendBuffer();
}

// Функция для рисования иконки будильника
void drawAlarmIcon(int x, int y) {
  // Рисуем простую иконку будильника
  // Циферблат
  u8g2.drawCircle(x + 6, y - 6, 6);

  // Стрелки
  u8g2.drawLine(x + 6, y - 6, x + 6, y - 9); // Часовая стрелка
  u8g2.drawLine(x + 6, y - 6, x + 9, y - 6); // Минутная стрелка

  // Колокольчики
  u8g2.drawCircle(x + 2, y - 12, 2);
  u8g2.drawCircle(x + 10, y - 12, 2);

  // Подставка
  u8g2.drawLine(x + 3, y, x + 9, y);
}

// Функция для проверки срабатывания будильника
void checkAlarm() {
  if (alarmTime != "" && !alarmActive) {
    time_t rawTime = timeClient.getEpochTime();
    struct tm * timeInfo = localtime(&rawTime);
    char currentTime[6]; // "HH:MM"
    strftime(currentTime, sizeof(currentTime), "%H:%M", timeInfo);

    if (String(currentTime) == alarmTime) {
      // Срабатывание будильника
      Serial.println("Будильник сработал!");
      alarmActive = true;
      alarmStartTime = millis();
      // Сбрасываем время будильника, чтобы предотвратить повторное срабатывание
      alarmTime = "";
      // Инициализируем переменные зуммера
      buzzerState = 0;
      buzzerPreviousMillis = millis();
    }
  }
}

// Функция для обработки активного будильника с анимацией котика
void handleActiveAlarm() {
  // Текущее время
  unsigned long currentMillis = millis();

  // Анимация котика
  if (currentMillis - previousMillisCat >= catFrameInterval) {
    previousMillisCat = currentMillis;
    currentCatFrame = (currentCatFrame + 1) % CAT_ANIMATION_FRAMES;
  }

  // Включаем дисплей и показываем текущий кадр анимации
  displayOn();
  u8g2.clearBuffer();

  // Расчёт позиции для центрирования кадра
  int frameWidth = catFrameWidths[currentCatFrame];
  int frameHeight = catFrameHeights[currentCatFrame];
  int posX = (128 - frameWidth * 8) / 2; // Учитываем, что каждый байт = 8 пикселей по горизонтали
  int posY = (64 - frameHeight) / 2;

  // Рисуем текущий кадр
  u8g2.drawXBMP(posX, posY, frameWidth, frameHeight, catFrames[currentCatFrame]);

  // Отправляем буфер на дисплей
  u8g2.sendBuffer();
  displayOff();

  // Обработка звука будильника
  switch (buzzerState) {
    case 0:
      // Начинаем первый писк
      digitalWrite(BUZZER_PIN, HIGH);
      buzzerPreviousMillis = currentMillis;
      buzzerState = 1;
      break;
    case 1:
      // Первый писк включен
      if (currentMillis - buzzerPreviousMillis >= beepOnDuration) {
        digitalWrite(BUZZER_PIN, LOW);
        buzzerPreviousMillis = currentMillis;
        buzzerState = 2;
      }
      break;
    case 2:
      // Короткая пауза между писками
      if (currentMillis - buzzerPreviousMillis >= beepOffDuration) {
        digitalWrite(BUZZER_PIN, HIGH);
        buzzerPreviousMillis = currentMillis;
        buzzerState = 3;
      }
      break;
    case 3:
      // Второй писк включен
      if (currentMillis - buzzerPreviousMillis >= beepOnDuration) {
        digitalWrite(BUZZER_PIN, LOW);
        buzzerPreviousMillis = currentMillis;
        buzzerState = 4;
      }
      break;
    case 4:
      // Длинная пауза после двойного писка
      if (currentMillis - buzzerPreviousMillis >= pauseDuration) {
        buzzerPreviousMillis = currentMillis;
        buzzerState = 0; // Повторяем цикл
      }
      break;
    default:
      // Сброс состояния
      buzzerState = 0;
      break;
  }

  // Проверяем, прошло ли время будильника
  if (currentMillis - alarmStartTime >= alarmDuration) {
    // Выключаем зуммер
    digitalWrite(BUZZER_PIN, LOW);
    // Деактивируем будильник
    alarmActive = false;
    // Сбрасываем состояние зуммера
    buzzerState = 0;
    // Очищаем дисплей
    u8g2.clearBuffer();
    u8g2.sendBuffer();
    displayOff();
  }
}

// Обработчик корневой страницы
void handleRoot() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }
  server.send(200, "text/html", htmlPage);
}

// Обработчик установки времени будильника
void handleSetAlarm() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }

  if (server.hasArg("alarm")) {
    alarmTime = server.arg("alarm");
    Serial.print("Будильник установлен на: ");
    Serial.println(alarmTime);
    server.sendHeader("Location", "/");
    server.send(303);
  } else {
    server.send(400, "text/plain", "Bad Request");
  }
}

// Обработчик отправки текущего времени и даты
void handleTime() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }
  time_t rawTime = timeClient.getEpochTime();
  struct tm * timeInfo = localtime(&rawTime);

  char timeString[9]; // "HH:MM:SS"
  char dateString[11]; // "DD.MM.YYYY"

  strftime(timeString, sizeof(timeString), "%H:%M:%S", timeInfo);
  strftime(dateString, sizeof(dateString), "%d.%m.%Y", timeInfo);

  // Объединяем дату и время в одну строку
  String dateTimeString = String(timeString) + " " + String(dateString);

  server.send(200, "text/plain", dateTimeString);
}  добавь в код окно по принципу монитора порта для отображения работы устройств