код #include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

// Параметры сети Wi-Fi
const char* ssid     = "Beeline_2G_A7F18A";
const char* password = "iD2tAXJN3e";

// Параметры веб-сервера
ESP8266WebServer server(80);

// Параметры авторизации
const char* www_username = "admin";
const char* www_password = "35539900";

// NTP настройки для Московского времени (UTC+3)
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 10800, 60000); // UTC+3, обновление каждую минуту

// Структура для будильников
struct Alarm {
  int hour;
  int minute;
};
Alarm alarms[3];
bool alarmSet[3] = {false, false, false};

// Пин зуммера
const int buzzerPin = 14; // GPIO14 (D5)

// Пин встроенного светодиода ESP8266
const int ledPin = 2; // GPIO2 (D4 на большинстве плат ESP8266)

// Логи для веб-интерфейса
String logs = "";

// Инициализация дисплея U8g2 для SSD1306 128x64 I2C с поддержкой русского текста
// Используем шрифт для отображения времени крупным и тонким, а дату более мелким
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// Функция для добавления логов
void addLog(String message) {
  logs += message + "<br>";
  if (logs.length() > 2000) { // Ограничение длины логов
    logs = logs.substring(logs.length() - 2000);
  }
}

// Функция для отображения многострочных сообщений на OLED (для уведомлений и ошибок)
void displayMessage(String message, int startY = 10, int textSize = 1) {
  u8g2.clearBuffer();
  
  // Выбор шрифта с поддержкой кириллицы
  u8g2.setFont(u8g2_font_t0_10_tf); // Меньший шрифт для сообщений
  int lineHeight = 12; // Зависит от выбранного шрифта
  
  int currentY = startY;
  int32_t msgLength = message.length();
  String word = "";
  String line = "";
  
  for(int i = 0; i < msgLength; i++) {
    char c = message.charAt(i);
    if(c == ' ' || c == '\n') {
      // Проверка ширины текущей строки с новым словом
      uint16_t lineWidth = u8g2.getUTF8Width((line + " " + word).c_str());
      if(lineWidth > 128) { // Если превышает ширину дисплея
        // Вывести текущую строку
        u8g2.setCursor(0, currentY);
        u8g2.print(line);
        currentY += lineHeight;
        if (currentY >= 64) break; // Предотвращаем выход за пределы дисплея
        line = word; // Начать новую строку
      }
      else {
        // Добавить слово в текущую строку
        if(line.length() > 0) {
          line += " ";
        }
        line += word;
      }
      word = "";
      if(c == '\n') {
        // Вывести текущую строку
        u8g2.setCursor(0, currentY);
        u8g2.print(line);
        currentY += lineHeight;
        if (currentY >= 64) break;
        line = "";
      }
    }
    else {
      word += c;
    }
  }
  
  // Добавить последнее слово
  if(word.length() > 0) {
    uint16_t lineWidth = u8g2.getUTF8Width((line + " " + word).c_str());
    if(lineWidth > 128) { // Если превышает ширину дисплея
      // Вывести текущую строку
      u8g2.setCursor(0, currentY);
      u8g2.print(line);
      currentY += lineHeight;
      if (currentY >= 64) return;
      line = word; // Начать новую строку
    }
    else {
      // Добавить слово в текущую строку
      if(line.length() > 0) {
        line += " ";
      }
      line += word;
    }
  }
  
  // Вывести оставшуюся строку
  if(line.length() > 0 && currentY < 64) {
    u8g2.setCursor(0, currentY);
    u8g2.print(line);
    currentY += lineHeight;
  }
  
  u8g2.sendBuffer();
}

// Функция для получения форматированной даты
String getFormattedDate() {
  time_t rawTime = timeClient.getEpochTime();
  struct tm * timeInfo = localtime(&rawTime);
  
  // Формат: ДД.ММ.ГГГГ
  String day = String(timeInfo->tm_mday);
  String month = String(timeInfo->tm_mon + 1); // tm_mon от 0 до 11
  String year = String(timeInfo->tm_year + 1900); // tm_year отсчитывается от 1900
  
  // Добавление ведущих нулей
  if(day.length() < 2) day = "0" + day;
  if(month.length() < 2) month = "0" + month;
  
  return day + "." + month + "." + year;
}

// Функция для отображения времени и даты на OLED
void displayTimeAndDate(String timeStr, String dateStr) {
  u8g2.clearBuffer();
  
  // Вывод времени с тонким шрифтом крупного размера
  u8g2.setFont(u8g2_font_ncenB08_tr); // Выберите тонкий шрифт из библиотеки U8g2
  uint16_t timeWidth = u8g2.getUTF8Width(timeStr.c_str());
  int16_t xTime = (128 - timeWidth) / 2;
  int16_t yTime = 25; // Позиция по вертикали для времени
  u8g2.setCursor(xTime, yTime);
  u8g2.print(timeStr);
  
  // Вывод даты с мелким шрифтом
  u8g2.setFont(u8g2_font_t0_11_tf); // Меньший шрифт для даты
  uint16_t dateWidth = u8g2.getUTF8Width(dateStr.c_str());
  int16_t xDate = (128 - dateWidth) / 2;
  int16_t yDate = 50; // Позиция по вертикали для даты
  u8g2.setCursor(xDate, yDate);
  u8g2.print(dateStr);
  
  u8g2.sendBuffer();
}

// Функция для проверки аутентификации
bool authenticate() {
  if(!server.authenticate(www_username, www_password))
    return false;
  return true;
}

// Функция обработки главной страницы
void handleRoot() {
  if (!authenticate()) {
    server.requestAuthentication();
    return;
  }

  String message = server.arg("message");

  String html = "<!DOCTYPE html><html><head><title>ESP8266 Alarm Clock</title>";
  // Добавляем мета-тег для поддержки UTF-8
  html += "<meta charset=\"UTF-8\">";
  // Стилизация страницы
  html += "<style>";
  html += "body { background-color: #2e2e2e; color: orange; font-family: Arial, sans-serif; }";
  html += "h1 { text-align: center; }";
  html += "form { margin: 20px auto; padding: 10px; width: 80%; background-color: #3e3e3e; border-radius: 5px; }";
  html += "input[type='time'] { padding: 5px; margin-right: 10px; }";
  html += "input[type='submit'] { padding: 5px 10px; background-color: orange; border: none; border-radius: 3px; color: #2e2e2e; cursor: pointer; }";
  html += "input[type='submit']:hover { background-color: #ff9900; }";
  html += ".status { margin-top: 10px; }";
  html += "pre { background-color: #3e3e3e; padding: 10px; border-radius: 5px; max-height: 200px; overflow-y: scroll; white-space: pre-wrap; }";
  // Добавляем стили для мигания точек
  html += ".blinker { animation: blink 1s step-start infinite; }";
  html += "@keyframes blink { 50% { opacity: 0; } }";
  html += "</style>";
  // Добавляем JavaScript для мигания точек
  html += "<script>";
  html += "function addDots() {";
  html += "  var dots = document.getElementById('dots');";
  html += "  if(dots.innerHTML.length >= 3) { dots.innerHTML = ''; } else { dots.innerHTML += '.'; }";
  html += "}";
  html += "setInterval(addDots, 1000);";
  html += "</script>";
  html += "</head><body>";
  
  html += "<h1>Настройка Будильников</h1>";
  
  if (message.length() > 0) {
    html += "<p style='text-align:center; color: lightgreen;'>" + message + "</p>";
  }

  for(int i=0; i<3; i++) {
    String status = alarmSet[i] ? " Установлен на " 
      + String(alarms[i].hour) + ":" 
      + (alarms[i].minute < 10 ? "0" + String(alarms[i].minute) : String(alarms[i].minute)) 
      : " Не установлен";
    html += "<form action=\"/setAlarm" + String(i) + "\" method=\"POST\">";
    html += "Будильник " + String(i+1) + ": ";
    html += "<input type=\"time\" name=\"time\" required>";
    html += "<input type=\"submit\" value=\"Установить\">";
    html += "</form>";
    html += "<div class='status'>Статус: " + status + "</div><br>";
  }

  html += "<h2 style='text-align:center;'>Логи Устройства</h2>";
  html += "<pre>" + logs + "</pre>";

  // Добавляем мигающие точки для индикатора активности сервера
  html += "<div style='text-align:center; font-size:24px;'><span>Сервер активен</span><span id='dots' class='blinker'>...</span></div>";

  html += "</body></html>";
  server.send(200, "text/html", html);
}

// Функция обработки установки будильника
void handleSetAlarm(int index) {
  if (!authenticate()) {
    server.requestAuthentication();
    return;
  }

  if(server.hasArg("time")) {
    String time = server.arg("time");
    int separator = time.indexOf(':');
    if(separator > 0) {
      alarms[index].hour = time.substring(0, separator).toInt();
      alarms[index].minute = time.substring(separator + 1).toInt();
      alarmSet[index] = true;
      String logMsg = "Будильник " + String(index + 1) + " установлен на " 
        + String(alarms[index].hour) + ":" 
        + (alarms[index].minute < 10 ? "0" + String(alarms[index].minute) : String(alarms[index].minute));
      addLog(logMsg);
      displayMessage(logMsg, 10, 1);
      // Перенаправление с сообщением
      server.sendHeader("Location", "/?message=Будильник+" + String(index + 1) + "+установлен");
      server.send(303, "text/plain", "");
      return;
    }
  }
  server.send(400, "text/plain", "Bad Request");
}

// Перегрузка функций для трёх будильников
void handleSetAlarm0() { handleSetAlarm(0); }
void handleSetAlarm1() { handleSetAlarm(1); }
void handleSetAlarm2() { handleSetAlarm(2); }

// Функция проверки и срабатывания будильников
void checkAlarms(int currentHour, int currentMinute) {
  for(int i=0; i<3; i++) {
    if(alarmSet[i] && alarms[i].hour == currentHour && alarms[i].minute == currentMinute) {
      String logMsg = "Срабатывание будильника " + String(i + 1) + " в " 
        + String(currentHour) + ":" 
        + (currentMinute < 10 ? "0" + String(currentMinute) : String(currentMinute));
      addLog(logMsg);
      displayMessage(logMsg, 30, 1);

      // Активируем зуммер и мигаем дисплеем
      for(int j=0; j<20; j++) { // 10 секунд с миганием 2 раза в секунду
        u8g2.clearBuffer();
        u8g2.sendBuffer();
        digitalWrite(buzzerPin, HIGH);
        delay(100); // Быстрое мигание
        // Восстанавливаем отображение времени и даты
        String currentTime = timeClient.getFormattedTime().substring(0,5); // ЧЧ:ММ
        String currentDate = getFormattedDate();
        displayTimeAndDate(currentTime, currentDate);
        digitalWrite(buzzerPin, LOW);
        delay(100);
      }
      // Сброс будильника
      alarms[i].hour = 0;
      alarms[i].minute = 0;
      alarmSet[i] = false;
      String resetMsg = "Будильник " + String(i + 1) + " отключён после срабатывания";
      addLog(resetMsg);
      displayMessage(resetMsg, 50, 1);
    }
  }
}

void setup() {
  // Инициализация встроенного светодиода как выхода
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH); // Встроенный светодиод обычно активен при LOW, но зависит от вашей платы

  // Инициализация зуммера
  pinMode(buzzerPin, OUTPUT);
  digitalWrite(buzzerPin, LOW);

  // Инициализация I2C
  Wire.begin(4,5); // SDA = GPIO4 (D2), SCL = GPIO5 (D1)

  // Инициализация дисплея
  u8g2.begin();
  u8g2.enableUTF8Print(); // Включаем поддержку UTF-8
  displayMessage("Запуск устройства...", 10, 1);
  delay(2000);

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

  // Мигание встроенным светодиодом при запуске сервера на 10 секунд
  unsigned long startBlink = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startBlink < 10000) {
    digitalWrite(ledPin, LOW); // Включаем светодиод
    delay(100);
    digitalWrite(ledPin, HIGH); // Выключаем светодиод
    delay(100);
    
    // Обновляем сообщение с миганием точек
    static int dot_count = 0;
    dot_count = (dot_count + 1) % 4;
    String dots = "";
    for(int i=0; i<dot_count; i++) {
      dots += ".";
    }
    displayMessage("Подключение к Wi-Fi" + dots, 10, 1);
  }

  if (WiFi.status() == WL_CONNECTED) {
    // После успешного подключения
    String connectedMsg = "Wi-Fi подключено!\nIP: " + WiFi.localIP().toString();
    displayMessage(connectedMsg, 10, 1);
    addLog(connectedMsg);
  } else {
    // Если не удалось подключиться за 10 секунд
    displayMessage("Не удалось подключиться в\nтечение 10 секунд", 10, 1);
    addLog("Ошибка: Не удалось подключиться к Wi-Fi");
  }

  delay(1000);

  // Настройка NTP клиента
  timeClient.begin();
  timeClient.update();

  // Настройка маршрутов веб-сервера
  server.on("/", handleRoot);
  server.on("/setAlarm0", HTTP_POST, handleSetAlarm0);
  server.on("/setAlarm1", HTTP_POST, handleSetAlarm1);
  server.on("/setAlarm2", HTTP_POST, handleSetAlarm2);

  // Запуск веб-сервера
  server.begin();
  String serverStartMsg = "HTTP сервер запущен на " + WiFi.localIP().toString();
  displayMessage(serverStartMsg, 10, 1);
  addLog(serverStartMsg);
  delay(1000);

  // Первоначальное отображение времени и даты
  String currentTime = timeClient.getFormattedTime().substring(0,5); // ЧЧ:ММ
  String currentDate = getFormattedDate();
  displayTimeAndDate(currentTime, currentDate);
}

void loop() {
  server.handleClient();

  // Обновление времени каждую секунду
  if(timeClient.update()) {
    String currentTime = timeClient.getFormattedTime().substring(0,5); // ЧЧ:ММ
    String currentDate = getFormattedDate();

    displayTimeAndDate(currentTime, currentDate);

    // Добавляем лог обновления времени
    String logMsg = "Текущее время: " + currentTime + ", дата: " + currentDate;
    addLog(logMsg);

    // Проверка будильников
    int currentHour = timeClient.getHours();
    int currentMinute = timeClient.getMinutes();
    checkAlarms(currentHour, currentMinute);
  }

  delay(1000);
} ошибка #include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

// Параметры сети Wi-Fi
const char* ssid     = "Beeline_2G_A7F18A";
const char* password = "iD2tAXJN3e";

// Параметры веб-сервера
ESP8266WebServer server(80);

// Параметры авторизации
const char* www_username = "admin";
const char* www_password = "35539900";

// NTP настройки для Московского времени (UTC+3)
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 10800, 60000); // UTC+3, обновление каждую минуту

// Структура для будильников
struct Alarm {
  int hour;
  int minute;
};
Alarm alarms[3];
bool alarmSet[3] = {false, false, false};

// Пин зуммера
const int buzzerPin = 14; // GPIO14 (D5)

// Пин встроенного светодиода ESP8266
const int ledPin = 2; // GPIO2 (D4 на большинстве плат ESP8266)

// Логи для веб-интерфейса
String logs = "";

// Инициализация дисплея U8g2 для SSD1306 128x64 I2C с поддержкой русского текста
// Используем шрифт для отображения времени крупным и тонким, а дату более мелким
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// Функция для добавления логов
void addLog(String message) {
  logs += message + "<br>";
  if (logs.length() > 2000) { // Ограничение длины логов
    logs = logs.substring(logs.length() - 2000);
  }
}

// Функция для отображения многострочных сообщений на OLED (для уведомлений и ошибок)
void displayMessage(String message, int startY = 10, int textSize = 1) {
  u8g2.clearBuffer();
  
  // Выбор шрифта с поддержкой кириллицы
  u8g2.setFont(u8g2_font_t0_10_tf); // Меньший шрифт для сообщений
  int lineHeight = 12; // Зависит от выбранного шрифта
  
  int currentY = startY;
  int32_t msgLength = message.length();
  String word = "";
  String line = "";
  
  for(int i = 0; i < msgLength; i++) {
    char c = message.charAt(i);
    if(c == ' ' || c == '\n') {
      // Проверка ширины текущей строки с новым словом
      uint16_t lineWidth = u8g2.getUTF8Width((line + " " + word).c_str());
      if(lineWidth > 128) { // Если превышает ширину дисплея
        // Вывести текущую строку
        u8g2.setCursor(0, currentY);
        u8g2.print(line);
        currentY += lineHeight;
        if (currentY >= 64) break; // Предотвращаем выход за пределы дисплея
        line = word; // Начать новую строку
      }
      else {
        // Добавить слово в текущую строку
        if(line.length() > 0) {
          line += " ";
        }
        line += word;
      }
      word = "";
      if(c == '\n') {
        // Вывести текущую строку
        u8g2.setCursor(0, currentY);
        u8g2.print(line);
        currentY += lineHeight;
        if (currentY >= 64) break;
        line = "";
      }
    }
    else {
      word += c;
    }
  }
  
  // Добавить последнее слово
  if(word.length() > 0) {
    uint16_t lineWidth = u8g2.getUTF8Width((line + " " + word).c_str());
    if(lineWidth > 128) { // Если превышает ширину дисплея
      // Вывести текущую строку
      u8g2.setCursor(0, currentY);
      u8g2.print(line);
      currentY += lineHeight;
      if (currentY >= 64) return;
      line = word; // Начать новую строку
    }
    else {
      // Добавить слово в текущую строку
      if(line.length() > 0) {
        line += " ";
      }
      line += word;
    }
  }
  
  // Вывести оставшуюся строку
  if(line.length() > 0 && currentY < 64) {
    u8g2.setCursor(0, currentY);
    u8g2.print(line);
    currentY += lineHeight;
  }
  
  u8g2.sendBuffer();
}

// Функция для получения форматированной даты
String getFormattedDate() {
  time_t rawTime = timeClient.getEpochTime();
  struct tm * timeInfo = localtime(&rawTime);
  
  // Формат: ДД.ММ.ГГГГ
  String day = String(timeInfo->tm_mday);
  String month = String(timeInfo->tm_mon + 1); // tm_mon от 0 до 11
  String year = String(timeInfo->tm_year + 1900); // tm_year отсчитывается от 1900
  
  // Добавление ведущих нулей
  if(day.length() < 2) day = "0" + day;
  if(month.length() < 2) month = "0" + month;
  
  return day + "." + month + "." + year;
}

// Функция для отображения времени и даты на OLED
void displayTimeAndDate(String timeStr, String dateStr) {
  u8g2.clearBuffer();
  
  // Вывод времени с тонким шрифтом крупного размера
  u8g2.setFont(u8g2_font_ncenB08_tr); // Выберите тонкий шрифт из библиотеки U8g2
  uint16_t timeWidth = u8g2.getUTF8Width(timeStr.c_str());
  int16_t xTime = (128 - timeWidth) / 2;
  int16_t yTime = 25; // Позиция по вертикали для времени
  u8g2.setCursor(xTime, yTime);
  u8g2.print(timeStr);
  
  // Вывод даты с мелким шрифтом
  u8g2.setFont(u8g2_font_t0_11_tf); // Меньший шрифт для даты
  uint16_t dateWidth = u8g2.getUTF8Width(dateStr.c_str());
  int16_t xDate = (128 - dateWidth) / 2;
  int16_t yDate = 50; // Позиция по вертикали для даты
  u8g2.setCursor(xDate, yDate);
  u8g2.print(dateStr);
  
  u8g2.sendBuffer();
}

// Функция для проверки аутентификации
bool authenticate() {
  if(!server.authenticate(www_username, www_password))
    return false;
  return true;
}

// Функция обработки главной страницы
void handleRoot() {
  if (!authenticate()) {
    server.requestAuthentication();
    return;
  }

  String message = server.arg("message");

  String html = "<!DOCTYPE html><html><head><title>ESP8266 Alarm Clock</title>";
  // Добавляем мета-тег для поддержки UTF-8
  html += "<meta charset=\"UTF-8\">";
  // Стилизация страницы
  html += "<style>";
  html += "body { background-color: #2e2e2e; color: orange; font-family: Arial, sans-serif; }";
  html += "h1 { text-align: center; }";
  html += "form { margin: 20px auto; padding: 10px; width: 80%; background-color: #3e3e3e; border-radius: 5px; }";
  html += "input[type='time'] { padding: 5px; margin-right: 10px; }";
  html += "input[type='submit'] { padding: 5px 10px; background-color: orange; border: none; border-radius: 3px; color: #2e2e2e; cursor: pointer; }";
  html += "input[type='submit']:hover { background-color: #ff9900; }";
  html += ".status { margin-top: 10px; }";
  html += "pre { background-color: #3e3e3e; padding: 10px; border-radius: 5px; max-height: 200px; overflow-y: scroll; white-space: pre-wrap; }";
  // Добавляем стили для мигания точек
  html += ".blinker { animation: blink 1s step-start infinite; }";
  html += "@keyframes blink { 50% { opacity: 0; } }";
  html += "</style>";
  // Добавляем JavaScript для мигания точек
  html += "<script>";
  html += "function addDots() {";
  html += "  var dots = document.getElementById('dots');";
  html += "  if(dots.innerHTML.length >= 3) { dots.innerHTML = ''; } else { dots.innerHTML += '.'; }";
  html += "}";
  html += "setInterval(addDots, 1000);";
  html += "</script>";
  html += "</head><body>";
  
  html += "<h1>Настройка Будильников</h1>";
  
  if (message.length() > 0) {
    html += "<p style='text-align:center; color: lightgreen;'>" + message + "</p>";
  }

  for(int i=0; i<3; i++) {
    String status = alarmSet[i] ? " Установлен на " 
      + String(alarms[i].hour) + ":" 
      + (alarms[i].minute < 10 ? "0" + String(alarms[i].minute) : String(alarms[i].minute)) 
      : " Не установлен";
    html += "<form action=\"/setAlarm" + String(i) + "\" method=\"POST\">";
    html += "Будильник " + String(i+1) + ": ";
    html += "<input type=\"time\" name=\"time\" required>";
    html += "<input type=\"submit\" value=\"Установить\">";
    html += "</form>";
    html += "<div class='status'>Статус: " + status + "</div><br>";
  }

  html += "<h2 style='text-align:center;'>Логи Устройства</h2>";
  html += "<pre>" + logs + "</pre>";

  // Добавляем мигающие точки для индикатора активности сервера
  html += "<div style='text-align:center; font-size:24px;'><span>Сервер активен</span><span id='dots' class='blinker'>...</span></div>";

  html += "</body></html>";
  server.send(200, "text/html", html);
}

// Функция обработки установки будильника
void handleSetAlarm(int index) {
  if (!authenticate()) {
    server.requestAuthentication();
    return;
  }

  if(server.hasArg("time")) {
    String time = server.arg("time");
    int separator = time.indexOf(':');
    if(separator > 0) {
      alarms[index].hour = time.substring(0, separator).toInt();
      alarms[index].minute = time.substring(separator + 1).toInt();
      alarmSet[index] = true;
      String logMsg = "Будильник " + String(index + 1) + " установлен на " 
        + String(alarms[index].hour) + ":" 
        + (alarms[index].minute < 10 ? "0" + String(alarms[index].minute) : String(alarms[index].minute));
      addLog(logMsg);
      displayMessage(logMsg, 10, 1);
      // Перенаправление с сообщением
      server.sendHeader("Location", "/?message=Будильник+" + String(index + 1) + "+установлен");
      server.send(303, "text/plain", "");
      return;
    }
  }
  server.send(400, "text/plain", "Bad Request");
}

// Перегрузка функций для трёх будильников
void handleSetAlarm0() { handleSetAlarm(0); }
void handleSetAlarm1() { handleSetAlarm(1); }
void handleSetAlarm2() { handleSetAlarm(2); }

// Функция проверки и срабатывания будильников
void checkAlarms(int currentHour, int currentMinute) {
  for(int i=0; i<3; i++) {
    if(alarmSet[i] && alarms[i].hour == currentHour && alarms[i].minute == currentMinute) {
      String logMsg = "Срабатывание будильника " + String(i + 1) + " в " 
        + String(currentHour) + ":" 
        + (currentMinute < 10 ? "0" + String(currentMinute) : String(currentMinute));
      addLog(logMsg);
      displayMessage(logMsg, 30, 1);

      // Активируем зуммер и мигаем дисплеем
      for(int j=0; j<20; j++) { // 10 секунд с миганием 2 раза в секунду
        u8g2.clearBuffer();
        u8g2.sendBuffer();
        digitalWrite(buzzerPin, HIGH);
        delay(100); // Быстрое мигание
        // Восстанавливаем отображение времени и даты
        String currentTime = timeClient.getFormattedTime().substring(0,5); // ЧЧ:ММ
        String currentDate = getFormattedDate();
        displayTimeAndDate(currentTime, currentDate);
        digitalWrite(buzzerPin, LOW);
        delay(100);
      }
      // Сброс будильника
      alarms[i].hour = 0;
      alarms[i].minute = 0;
      alarmSet[i] = false;
      String resetMsg = "Будильник " + String(i + 1) + " отключён после срабатывания";
      addLog(resetMsg);
      displayMessage(resetMsg, 50, 1);
    }
  }
}

void setup() {
  // Инициализация встроенного светодиода как выхода
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH); // Встроенный светодиод обычно активен при LOW, но зависит от вашей платы

  // Инициализация зуммера
  pinMode(buzzerPin, OUTPUT);
  digitalWrite(buzzerPin, LOW);

  // Инициализация I2C
  Wire.begin(4,5); // SDA = GPIO4 (D2), SCL = GPIO5 (D1)

  // Инициализация дисплея
  u8g2.begin();
  u8g2.enableUTF8Print(); // Включаем поддержку UTF-8
  displayMessage("Запуск устройства...", 10, 1);
  delay(2000);

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

  // Мигание встроенным светодиодом при запуске сервера на 10 секунд
  unsigned long startBlink = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startBlink < 10000) {
    digitalWrite(ledPin, LOW); // Включаем светодиод
    delay(100);
    digitalWrite(ledPin, HIGH); // Выключаем светодиод
    delay(100);
    
    // Обновляем сообщение с миганием точек
    static int dot_count = 0;
    dot_count = (dot_count + 1) % 4;
    String dots = "";
    for(int i=0; i<dot_count; i++) {
      dots += ".";
    }
    displayMessage("Подключение к Wi-Fi" + dots, 10, 1);
  }

  if (WiFi.status() == WL_CONNECTED) {
    // После успешного подключения
    String connectedMsg = "Wi-Fi подключено!\nIP: " + WiFi.localIP().toString();
    displayMessage(connectedMsg, 10, 1);
    addLog(connectedMsg);
  } else {
    // Если не удалось подключиться за 10 секунд
    displayMessage("Не удалось подключиться в\nтечение 10 секунд", 10, 1);
    addLog("Ошибка: Не удалось подключиться к Wi-Fi");
  }

  delay(1000);

  // Настройка NTP клиента
  timeClient.begin();
  timeClient.update();

  // Настройка маршрутов веб-сервера
  server.on("/", handleRoot);
  server.on("/setAlarm0", HTTP_POST, handleSetAlarm0);
  server.on("/setAlarm1", HTTP_POST, handleSetAlarm1);
  server.on("/setAlarm2", HTTP_POST, handleSetAlarm2);

  // Запуск веб-сервера
  server.begin();
  String serverStartMsg = "HTTP сервер запущен на " + WiFi.localIP().toString();
  displayMessage(serverStartMsg, 10, 1);
  addLog(serverStartMsg);
  delay(1000);

  // Первоначальное отображение времени и даты
  String currentTime = timeClient.getFormattedTime().substring(0,5); // ЧЧ:ММ
  String currentDate = getFormattedDate();
  displayTimeAndDate(currentTime, currentDate);
}

void loop() {
  server.handleClient();

  // Обновление времени каждую секунду
  if(timeClient.update()) {
    String currentTime = timeClient.getFormattedTime().substring(0,5); // ЧЧ:ММ
    String currentDate = getFormattedDate();

    displayTimeAndDate(currentTime, currentDate);

    // Добавляем лог обновления времени
    String logMsg = "Текущее время: " + currentTime + ", дата: " + currentDate;
    addLog(logMsg);

    // Проверка будильников
    int currentHour = timeClient.getHours();
    int currentMinute = timeClient.getMinutes();
    checkAlarms(currentHour, currentMinute);
  }

  delay(1000);
}#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

// Параметры сети Wi-Fi
const char* ssid     = "Beeline_2G_A7F18A";
const char* password = "iD2tAXJN3e";

// Параметры веб-сервера
ESP8266WebServer server(80);

// Параметры авторизации
const char* www_username = "admin";
const char* www_password = "35539900";

// NTP настройки для Московского времени (UTC+3)
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 10800, 60000); // UTC+3, обновление каждую минуту

// Структура для будильников
struct Alarm {
  int hour;
  int minute;
};
Alarm alarms[3];
bool alarmSet[3] = {false, false, false};

// Пин зуммера
const int buzzerPin = 14; // GPIO14 (D5)

// Пин встроенного светодиода ESP8266
const int ledPin = 2; // GPIO2 (D4 на большинстве плат ESP8266)

// Логи для веб-интерфейса
String logs = "";

// Инициализация дисплея U8g2 для SSD1306 128x64 I2C с поддержкой русского текста
// Используем шрифт для отображения времени крупным и тонким, а дату более мелким
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// Функция для добавления логов
void addLog(String message) {
  logs += message + "<br>";
  if (logs.length() > 2000) { // Ограничение длины логов
    logs = logs.substring(logs.length() - 2000);
  }
}

// Функция для отображения многострочных сообщений на OLED (для уведомлений и ошибок)
void displayMessage(String message, int startY = 10, int textSize = 1) {
  u8g2.clearBuffer();
  
  // Выбор шрифта с поддержкой кириллицы
  u8g2.setFont(u8g2_font_t0_10_tf); // Меньший шрифт для сообщений
  int lineHeight = 12; // Зависит от выбранного шрифта
  
  int currentY = startY;
  int32_t msgLength = message.length();
  String word = "";
  String line = "";
  
  for(int i = 0; i < msgLength; i++) {
    char c = message.charAt(i);
    if(c == ' ' || c == '\n') {
      // Проверка ширины текущей строки с новым словом
      uint16_t lineWidth = u8g2.getUTF8Width((line + " " + word).c_str());
      if(lineWidth > 128) { // Если превышает ширину дисплея
        // Вывести текущую строку
        u8g2.setCursor(0, currentY);
        u8g2.print(line);
        currentY += lineHeight;
        if (currentY >= 64) break; // Предотвращаем выход за пределы дисплея
        line = word; // Начать новую строку
      }
      else {
        // Добавить слово в текущую строку
        if(line.length() > 0) {
          line += " ";
        }
        line += word;
      }
      word = "";
      if(c == '\n') {
        // Вывести текущую строку
        u8g2.setCursor(0, currentY);
        u8g2.print(line);
        currentY += lineHeight;
        if (currentY >= 64) break;
        line = "";
      }
    }
    else {
      word += c;
    }
  }
  
  // Добавить последнее слово
  if(word.length() > 0) {
    uint16_t lineWidth = u8g2.getUTF8Width((line + " " + word).c_str());
    if(lineWidth > 128) { // Если превышает ширину дисплея
      // Вывести текущую строку
      u8g2.setCursor(0, currentY);
      u8g2.print(line);
      currentY += lineHeight;
      if (currentY >= 64) return;
      line = word; // Начать новую строку
    }
    else {
      // Добавить слово в текущую строку
      if(line.length() > 0) {
        line += " ";
      }
      line += word;
    }
  }
  
  // Вывести оставшуюся строку
  if(line.length() > 0 && currentY < 64) {
    u8g2.setCursor(0, currentY);
    u8g2.print(line);
    currentY += lineHeight;
  }
  
  u8g2.sendBuffer();
}

// Функция для получения форматированной даты
String getFormattedDate() {
  time_t rawTime = timeClient.getEpochTime();
  struct tm * timeInfo = localtime(&rawTime);
  
  // Формат: ДД.ММ.ГГГГ
  String day = String(timeInfo->tm_mday);
  String month = String(timeInfo->tm_mon + 1); // tm_mon от 0 до 11
  String year = String(timeInfo->tm_year + 1900); // tm_year отсчитывается от 1900
  
  // Добавление ведущих нулей
  if(day.length() < 2) day = "0" + day;
  if(month.length() < 2) month = "0" + month;
  
  return day + "." + month + "." + year;
}

// Функция для отображения времени и даты на OLED
void displayTimeAndDate(String timeStr, String dateStr) {
  u8g2.clearBuffer();
  
  // Вывод времени с тонким шрифтом крупного размера
  u8g2.setFont(u8g2_font_ncenB08_tr); // Выберите тонкий шрифт из библиотеки U8g2
  uint16_t timeWidth = u8g2.getUTF8Width(timeStr.c_str());
  int16_t xTime = (128 - timeWidth) / 2;
  int16_t yTime = 25; // Позиция по вертикали для времени
  u8g2.setCursor(xTime, yTime);
  u8g2.print(timeStr);
  
  // Вывод даты с мелким шрифтом
  u8g2.setFont(u8g2_font_t0_11_tf); // Меньший шрифт для даты
  uint16_t dateWidth = u8g2.getUTF8Width(dateStr.c_str());
  int16_t xDate = (128 - dateWidth) / 2;
  int16_t yDate = 50; // Позиция по вертикали для даты
  u8g2.setCursor(xDate, yDate);
  u8g2.print(dateStr);
  
  u8g2.sendBuffer();
}

// Функция для проверки аутентификации
bool authenticate() {
  if(!server.authenticate(www_username, www_password))
    return false;
  return true;
}

// Функция обработки главной страницы
void handleRoot() {
  if (!authenticate()) {
    server.requestAuthentication();
    return;
  }

  String message = server.arg("message");

  String html = "<!DOCTYPE html><html><head><title>ESP8266 Alarm Clock</title>";
  // Добавляем мета-тег для поддержки UTF-8
  html += "<meta charset=\"UTF-8\">";
  // Стилизация страницы
  html += "<style>";
  html += "body { background-color: #2e2e2e; color: orange; font-family: Arial, sans-serif; }";
  html += "h1 { text-align: center; }";
  html += "form { margin: 20px auto; padding: 10px; width: 80%; background-color: #3e3e3e; border-radius: 5px; }";
  html += "input[type='time'] { padding: 5px; margin-right: 10px; }";
  html += "input[type='submit'] { padding: 5px 10px; background-color: orange; border: none; border-radius: 3px; color: #2e2e2e; cursor: pointer; }";
  html += "input[type='submit']:hover { background-color: #ff9900; }";
  html += ".status { margin-top: 10px; }";
  html += "pre { background-color: #3e3e3e; padding: 10px; border-radius: 5px; max-height: 200px; overflow-y: scroll; white-space: pre-wrap; }";
  // Добавляем стили для мигания точек
  html += ".blinker { animation: blink 1s step-start infinite; }";
  html += "@keyframes blink { 50% { opacity: 0; } }";
  html += "</style>";
  // Добавляем JavaScript для мигания точек
  html += "<script>";
  html += "function addDots() {";
  html += "  var dots = document.getElementById('dots');";
  html += "  if(dots.innerHTML.length >= 3) { dots.innerHTML = ''; } else { dots.innerHTML += '.'; }";
  html += "}";
  html += "setInterval(addDots, 1000);";
  html += "</script>";
  html += "</head><body>";
  
  html += "<h1>Настройка Будильников</h1>";
  
  if (message.length() > 0) {
    html += "<p style='text-align:center; color: lightgreen;'>" + message + "</p>";
  }

  for(int i=0; i<3; i++) {
    String status = alarmSet[i] ? " Установлен на " 
      + String(alarms[i].hour) + ":" 
      + (alarms[i].minute < 10 ? "0" + String(alarms[i].minute) : String(alarms[i].minute)) 
      : " Не установлен";
    html += "<form action=\"/setAlarm" + String(i) + "\" method=\"POST\">";
    html += "Будильник " + String(i+1) + ": ";
    html += "<input type=\"time\" name=\"time\" required>";
    html += "<input type=\"submit\" value=\"Установить\">";
    html += "</form>";
    html += "<div class='status'>Статус: " + status + "</div><br>";
  }

  html += "<h2 style='text-align:center;'>Логи Устройства</h2>";
  html += "<pre>" + logs + "</pre>";

  // Добавляем мигающие точки для индикатора активности сервера
  html += "<div style='text-align:center; font-size:24px;'><span>Сервер активен</span><span id='dots' class='blinker'>...</span></div>";

  html += "</body></html>";
  server.send(200, "text/html", html);
}

// Функция обработки установки будильника
void handleSetAlarm(int index) {
  if (!authenticate()) {
    server.requestAuthentication();
    return;
  }

  if(server.hasArg("time")) {
    String time = server.arg("time");
    int separator = time.indexOf(':');
    if(separator > 0) {
      alarms[index].hour = time.substring(0, separator).toInt();
      alarms[index].minute = time.substring(separator + 1).toInt();
      alarmSet[index] = true;
      String logMsg = "Будильник " + String(index + 1) + " установлен на " 
        + String(alarms[index].hour) + ":" 
        + (alarms[index].minute < 10 ? "0" + String(alarms[index].minute) : String(alarms[index].minute));
      addLog(logMsg);
      displayMessage(logMsg, 10, 1);
      // Перенаправление с сообщением
      server.sendHeader("Location", "/?message=Будильник+" + String(index + 1) + "+установлен");
      server.send(303, "text/plain", "");
      return;
    }
  }
  server.send(400, "text/plain", "Bad Request");
}

// Перегрузка функций для трёх будильников
void handleSetAlarm0() { handleSetAlarm(0); }
void handleSetAlarm1() { handleSetAlarm(1); }
void handleSetAlarm2() { handleSetAlarm(2); }

// Функция проверки и срабатывания будильников
void checkAlarms(int currentHour, int currentMinute) {
  for(int i=0; i<3; i++) {
    if(alarmSet[i] && alarms[i].hour == currentHour && alarms[i].minute == currentMinute) {
      String logMsg = "Срабатывание будильника " + String(i + 1) + " в " 
        + String(currentHour) + ":" 
        + (currentMinute < 10 ? "0" + String(currentMinute) : String(currentMinute));
      addLog(logMsg);
      displayMessage(logMsg, 30, 1);

      // Активируем зуммер и мигаем дисплеем
      for(int j=0; j<20; j++) { // 10 секунд с миганием 2 раза в секунду
        u8g2.clearBuffer();
        u8g2.sendBuffer();
        digitalWrite(buzzerPin, HIGH);
        delay(100); // Быстрое мигание
        // Восстанавливаем отображение времени и даты
        String currentTime = timeClient.getFormattedTime().substring(0,5); // ЧЧ:ММ
        String currentDate = getFormattedDate();
        displayTimeAndDate(currentTime, currentDate);
        digitalWrite(buzzerPin, LOW);
        delay(100);
      }
      // Сброс будильника
      alarms[i].hour = 0;
      alarms[i].minute = 0;
      alarmSet[i] = false;
      String resetMsg = "Будильник " + String(i + 1) + " отключён после срабатывания";
      addLog(resetMsg);
      displayMessage(resetMsg, 50, 1);
    }
  }
}

void setup() {
  // Инициализация встроенного светодиода как выхода
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH); // Встроенный светодиод обычно активен при LOW, но зависит от вашей платы

  // Инициализация зуммера
  pinMode(buzzerPin, OUTPUT);
  digitalWrite(buzzerPin, LOW);

  // Инициализация I2C
  Wire.begin(4,5); // SDA = GPIO4 (D2), SCL = GPIO5 (D1)

  // Инициализация дисплея
  u8g2.begin();
  u8g2.enableUTF8Print(); // Включаем поддержку UTF-8
  displayMessage("Запуск устройства...", 10, 1);
  delay(2000);

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

  // Мигание встроенным светодиодом при запуске сервера на 10 секунд
  unsigned long startBlink = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startBlink < 10000) {
    digitalWrite(ledPin, LOW); // Включаем светодиод
    delay(100);
    digitalWrite(ledPin, HIGH); // Выключаем светодиод
    delay(100);
    
    // Обновляем сообщение с миганием точек
    static int dot_count = 0;
    dot_count = (dot_count + 1) % 4;
    String dots = "";
    for(int i=0; i<dot_count; i++) {
      dots += ".";
    }
    displayMessage("Подключение к Wi-Fi" + dots, 10, 1);
  }

  if (WiFi.status() == WL_CONNECTED) {
    // После успешного подключения
    String connectedMsg = "Wi-Fi подключено!\nIP: " + WiFi.localIP().toString();
    displayMessage(connectedMsg, 10, 1);
    addLog(connectedMsg);
  } else {
    // Если не удалось подключиться за 10 секунд
    displayMessage("Не удалось подключиться в\nтечение 10 секунд", 10, 1);
    addLog("Ошибка: Не удалось подключиться к Wi-Fi");
  }

  delay(1000);

  // Настройка NTP клиента
  timeClient.begin();
  timeClient.update();

  // Настройка маршрутов веб-сервера
  server.on("/", handleRoot);
  server.on("/setAlarm0", HTTP_POST, handleSetAlarm0);
  server.on("/setAlarm1", HTTP_POST, handleSetAlarm1);
  server.on("/setAlarm2", HTTP_POST, handleSetAlarm2);

  // Запуск веб-сервера
  server.begin();
  String serverStartMsg = "HTTP сервер запущен на " + WiFi.localIP().toString();
  displayMessage(serverStartMsg, 10, 1);
  addLog(serverStartMsg);
  delay(1000);

  // Первоначальное отображение времени и даты
  String currentTime = timeClient.getFormattedTime().substring(0,5); // ЧЧ:ММ
  String currentDate = getFormattedDate();
  displayTimeAndDate(currentTime, currentDate);
}

void loop() {
  server.handleClient();

  // Обновление времени каждую секунду
  if(timeClient.update()) {
    String currentTime = timeClient.getFormattedTime().substring(0,5); // ЧЧ:ММ
    String currentDate = getFormattedDate();

    displayTimeAndDate(currentTime, currentDate);

    // Добавляем лог обновления времени
    String logMsg = "Текущее время: " + currentTime + ", дата: " + currentDate;
    addLog(logMsg);

    // Проверка будильников
    int currentHour = timeClient.getHours();
    int currentMinute = timeClient.getMinutes();
    checkAlarms(currentHour, currentMinute);
  }

  delay(1000);
}