Respan Dataset Explorer

Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.

5,167,182physical rows
86shards
0.00%qualified row coverage
0.00%qualified cell coverage
Random row JSON API

turns-00030.parquet:16030

4458d11301aa2c31642c3704
turn 1/1o1-mini-2024-09-12EnglishRussia4802 words
degenerate_repetitionAbsentFinal dense release
USER
//код для esp-01s позволяюший при отсутсвии интернет соединения ,
// зайти на маску esp и поменять сеть wi-fi на нужную 
// для этого нужно подключиться к сети esp_ap и перейти по адресу 192.168.4.1




#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <U8g2lib.h>
#include <ArduinoOTA.h>
#include <time.h>
#include <EEPROM.h> // Добавлено для работы с EEPROM

// Определяем пины для 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 будут храниться в EEPROM
String ssid = "Beeline_2G_A7F18A";
String password = "iD2tAXJN3e";

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

// Учетные данные для OTA
const char* otaHostname = "ESP01S_AlarmClock";
const char* otaPassword = "OTApassword123";

// Инициализация веб-сервера на порту 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 мс)

// Переменные для мигания времени и надписи "ALARM"
bool blinkState = false;
unsigned long previousMillisBlink = 0;
const unsigned long blinkInterval = 500; // Интервал мигания (мс)

// Буфер для хранения логов
String logBuffer = "";

// Максимальный размер буфера логов
const int maxLogSize = 2048;

// Переменная для контроля режима (STA или AP)
bool apMode = false;

// Прототипы функций
void logPrint(String message);
void logPrintln(String message);
void handleRoot();
void handleSetAlarm();
void handleTime();
void handleLog();
void checkAlarm();
void handleActiveAlarm();
void drawAlarmIcon(int x, int y);
void displayTime();
void startAPMode();
void handleRootAP();
void handleSaveWiFi();
void handleStatus();
void writeStringToEEPROM(int addrOffset, const String &strToWrite);
String readStringFromEEPROM(int addrOffset);

// HTML-страница, хранимая в PROGMEM
const char htmlPage[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<head>
  <title>Будильник</title>
  <style>
    body {
      background-color: #1E1E1E;
      color: #FFFFFF;
      font-family: Arial, sans-serif;
      text-align: center;
      padding-top: 20px;
    }
    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;
    }
    #log {
      background-color: #252526;
      color: #FFFFFF;
      text-align: left;
      padding: 10px;
      margin: 20px auto;
      width: 80%;
      height: 200px;
      overflow-y: scroll;
      font-family: monospace;
      font-size: 12px;
      border: 1px solid #333;
      white-space: pre-line;
    }
  </style>
</head>
<body>
  <h1>Будильник</h1>
  <div id="time">--:--:--</div>
  <div id="date">--.--.----</div>
  <form action="/setAlarm" method="POST">
    <label for="alarm">Время будильника:</label><br>
    <input type="time" id="alarm" name="alarm" required><br>
    <input type="submit" value="Установить будильник">
  </form>
  <div id="log">Загрузка логов...</div>
  <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();

    function updateLog() {
      fetch('/log')
        .then(response => response.text())
        .then(logData => {
          document.getElementById('log').textContent = logData;
          const logDiv = document.getElementById('log');
          logDiv.scrollTop = logDiv.scrollHeight;
        })
        .catch(err => console.error(err));
    }
    setInterval(updateLog, 1000);
    updateLog();
  </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); // Зуммер выключен

  // Инициализация EEPROM
  EEPROM.begin(512);

  // Чтение сохраненных Wi-Fi данных из EEPROM
  ssid = readStringFromEEPROM(0);
  password = readStringFromEEPROM(100);

  // Проверка наличия сохраненных SSID и пароля
  if (ssid.length() == 0 || password.length() == 0) {
    logPrintln("Нет сохраненных данных Wi-Fi. Запуск точки доступа.");
    apMode = true;
  } else {
    // Подключение к Wi-Fi
    logPrint("Подключение к Wi-Fi...");
    WiFi.begin(ssid.c_str(), password.c_str());

    unsigned long startAttemptTime = millis();
    const unsigned long wifiConnectingTimeout = 20000; // 20 секунд

    // Ожидание подключения
    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < wifiConnectingTimeout) {
      delay(500);
      logPrint(".");
    }

    if (WiFi.status() == WL_CONNECTED) {
      logPrintln("\nПодключено к Wi-Fi сети.");
      logPrint("Внутренний IP адрес: ");
      logPrintln(WiFi.localIP().toString());

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

      // Настройка обработчиков веб-сервера
      server.on("/", handleRoot);
      server.on("/setAlarm", HTTP_POST, handleSetAlarm);
      server.on("/time", handleTime);
      server.on("/log", handleLog);

      // Настройка OTA
      ArduinoOTA.setHostname(otaHostname);
      ArduinoOTA.setPassword(otaPassword);
      ArduinoOTA.begin();
      logPrintln("OTA настроен и готов к использованию.");

      // Запуск веб-сервера
      server.begin();
      logPrintln("Веб-сервер запущен на порту 80");

      apMode = false;
    } else {
      logPrintln("\nНе удалось подключиться к Wi-Fi за 20 секунд. Запуск режима точки доступа.");
      apMode = true;
    }
  }

  if (apMode) {
    startAPMode();
  }
}

// Функция запуска режима точки доступа
void startAPMode() {
  WiFi.disconnect();
  WiFi.mode(WIFI_AP);
  WiFi.softAP("ESP_AP");
  
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("Точка доступа запущена. IP адрес: ");
  Serial.println(myIP);
  
  // Настраиваем обработчики веб-сервера
  server.on("/", handleRootAP);
  server.on("/saveWiFi", handleSaveWiFi);
  server.on("/status", handleStatus);
  
  server.begin();
  logPrintln("HTTP сервер запущен.");
}

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

  if (!apMode) {
    // Обработка OTA
    ArduinoOTA.handle();

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

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

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

    // Мигание светодиода
    unsigned long currentMillis = millis();
    if (currentMillis - previousMillisLED >= intervalLED) {
      previousMillisLED = currentMillis;
      ledState = !ledState;
//      digitalWrite(LED_PIN, ledState ? LOW : HIGH);
    }
  } else {
    // В режиме AP можно реализовать другую логику, если требуется
    // Например, отображать на дисплее информацию о точке доступа
    u8g2.firstPage();
    do {
      u8g2.setFont(u8g2_font_helvR12_tr);
      u8g2.drawStr(0, 20, "Режим точки");
      u8g2.drawStr(0, 40, "доступа");
      u8g2.drawStr(0, 60, "SSID: ESP_AP");
    } while (u8g2.nextPage());
  }
}

// Функция логирования сообщений
void logPrint(String message) {
  // Получаем текущее время (если подключены к Wi-Fi)
  String timeStr = "";
  if (!apMode && timeClient.isTimeSet()) {
    time_t rawTime = timeClient.getEpochTime();
    struct tm* timeInfo = localtime(&rawTime);
    char timeArray[9];
    strftime(timeArray, sizeof(timeArray), "%H:%M:%S", timeInfo);
    timeStr = String(timeArray);
  } else {
    timeStr = "--:--:--";
  }

  String logMessage = timeStr + " - " + message;

  Serial.print(logMessage);
  logBuffer += logMessage;

  // Ограничиваем размер буфера логов
  if (logBuffer.length() > maxLogSize) {
    logBuffer.remove(0, logBuffer.length() - maxLogSize);
  }
}

void logPrintln(String message) {
  logPrint(message + "\n");
}

// Функция отображения времени и даты
void displayTime() {
  // Получение текущего времени
  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);

  u8g2.firstPage();
  do {
    // Шрифт для времени
    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 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);
    }
  } while (u8g2.nextPage());
}

// Функция рисования иконки будильника
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) {
      logPrintln("Будильник сработал!");
      alarmActive = true;
      alarmStartTime = millis();
      alarmTime = ""; // Сбрасываем будильник после срабатывания
      buzzerState = 0;
      buzzerPreviousMillis = millis();
      blinkState = false;
      previousMillisBlink = millis();
    }
  }
}

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

  // Управление миганием
  if (currentMillis - previousMillisBlink >= blinkInterval) {
    previousMillisBlink = currentMillis;
    blinkState = !blinkState;
  }

  u8g2.firstPage();
  do {
    if (blinkState) {
      // Отображаем время и дату
      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);

      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);
    } else {
      // Отображаем "ALARM"
      u8g2.setFont(u8g2_font_fub25_tr);
      const char* alarmText = "ALARM";
      int16_t textWidth = u8g2.getStrWidth(alarmText);
      int16_t textX = (128 - textWidth) / 2;
      int16_t textY = 36;
      u8g2.drawStr(textX, textY, alarmText);
    }
  } while (u8g2.nextPage());

  // Управление зуммером
  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.clearDisplay();
  }
}

// Обработчики веб-сервера в режиме STA
void handleRoot() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }
  String html((__FlashStringHelper*)htmlPage);
  server.send(200, "text/html; charset=utf-8", html);
}

void handleSetAlarm() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }

  if (server.hasArg("alarm")) {
    alarmTime = server.arg("alarm");
    logPrintln("Будильник установлен на: " + alarmTime);
    server.sendHeader("Location", "/");
    server.send(303);
  } else {
    server.send(400, "text/plain; charset=utf-8", "Неверный запрос");
  }
}

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];
  char dateString[11];

  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; charset=utf-8", dateTimeString);
}

void handleLog() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }
  server.send(200, "text/plain; charset=utf-8", logBuffer);
}

// Обработчики веб-сервера в режиме AP
void handleRootAP() {
  String html = "<html>\
  <head><title>Настройка Wi-Fi</title></head>\
  <body>\
  <h1>Настройка Wi-Fi</h1>\
  <form action='/saveWiFi' method='post'>\
  SSID:<br><input type='text' name='ssid'><br>\
  Пароль:<br><input type='password' name='password'><br><br>\
  <input type='submit' value='Сохранить'>\
  </form>\
  </body>\
  </html>";
      
  server.send(200, "text/html; charset=utf-8", html);
}

void handleSaveWiFi() {
  String newSSID = server.arg("ssid");
  String newPassword = server.arg("password");

  // Сохраняем новые данные в EEPROM
  writeStringToEEPROM(0, newSSID);
  writeStringToEEPROM(100, newPassword);
  EEPROM.commit();

  String html = "<html><body><h1>Настройки сохранены. Перезагрузка устройства...</h1></body></html>";
  server.send(200, "text/html; charset=utf-8", html);

  delay(2000);
  ESP.restart();
}

void handleStatus() {
  String html = "<html>\
  <head><title>Статус оборудования</title></head>\
  <body>\
  <h1>Состояние оборудования</h1>";

  if (!apMode && timeClient.isTimeSet()) {
    time_t rawTime = timeClient.getEpochTime();
    struct tm* timeInfo = localtime(&rawTime);
    char timeString[9];
    char dateString[11];
    strftime(timeString, sizeof(timeString), "%H:%M:%S", timeInfo);
    strftime(dateString, sizeof(dateString), "%d.%m.%Y", timeInfo);

    html += "<p>Текущее время: " + String(timeString) + " " + String(dateString) + "</p>";
  } else {
    html += "<p>Время недоступно</p>";
  }

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

// Функции для работы с EEPROM
void writeStringToEEPROM(int addrOffset, const String &strToWrite) {
  byte len = strToWrite.length();
  EEPROM.write(addrOffset, len);
  for (int i = 0; i < len; i++) {
    EEPROM.write(addrOffset + 1 + i, strToWrite[i]);
  }
}

String readStringFromEEPROM(int addrOffset) {
  int len = EEPROM.read(addrOffset);
  if (len < 0 || len > 31) return ""; // Проверка на допустимую длину
  char data[len + 1];
  for (int i = 0; i < len; i++) {
    data[i] = EEPROM.read(addrOffset + 1 + i);
  }
  data[len] = '\0';
  return String(data);
} добавь в этот код хранение последней загруженной прошивки в текстовом файле на сервере 
ASSISTANT
Конечно! Я добавлю функциональность для хранения информации о последней загруженной прошивке в текстовом файле `firmware.txt`, доступном через веб-сервер на ESP-01S. Для этого мы будем использовать EEPROM для сохранения времени последнего обновления прошивки и предоставим маршрут `/firmware.txt` для доступа к этой информации.

Вот обновленный код с необходимыми изменениями и комментариями для пояснения:

```cpp
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <U8g2lib.h>
#include <ArduinoOTA.h>
#include <time.h>
#include <EEPROM.h> // Добавлено для работы с EEPROM

// Определяем пины для 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 будут храниться в EEPROM
String ssid = "Beeline_2G_A7F18A";
String password = "iD2tAXJN3e";

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

// Учетные данные для OTA
const char* otaHostname = "ESP01S_AlarmClock";
const char* otaPassword = "OTApassword123";

// Версия прошивки
const char* firmwareVersion = "1.0.0"; // Добавлено для версии прошивки

// Инициализация веб-сервера на порту 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 мс)

// Переменные для мигания времени и надписи "ALARM"
bool blinkState = false;
unsigned long previousMillisBlink = 0;
const unsigned long blinkInterval = 500; // Интервал мигания (мс)

// Буфер для хранения логов
String logBuffer = "";

// Максимальный размер буфера логов
const int maxLogSize = 2048;

// Переменная для контроля режима (STA или AP)
bool apMode = false;

// Адрес в EEPROM для хранения информации о прошивке
#define FIRMWARE_INFO_ADDR 200

// Прототипы функций
void logPrint(String message);
void logPrintln(String message);
void handleRoot();
void handleSetAlarm();
void handleTime();
void handleLog();
void handleFirmware(); // Добавлен прототип
void checkAlarm();
void handleActiveAlarm();
void drawAlarmIcon(int x, int y);
void displayTime();
void startAPMode();
void handleRootAP();
void handleSaveWiFi();
void handleStatus();
void writeStringToEEPROM(int addrOffset, const String &strToWrite);
String readStringFromEEPROM(int addrOffset);
void writeFirmwareInfo(const String &info); // Добавлена функция
String readFirmwareInfo(); // Добавлена функция

// HTML-страница, хранимая в PROGMEM
const char htmlPage[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<head>
  <title>Будильник</title>
  <style>
    body {
      background-color: #1E1E1E;
      color: #FFFFFF;
      font-family: Arial, sans-serif;
      text-align: center;
      padding-top: 20px;
    }
    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;
    }
    #log {
      background-color: #252526;
      color: #FFFFFF;
      text-align: left;
      padding: 10px;
      margin: 20px auto;
      width: 80%;
      height: 200px;
      overflow-y: scroll;
      font-family: monospace;
      font-size: 12px;
      border: 1px solid #333;
      white-space: pre-line;
    }
  </style>
</head>
<body>
  <h1>Будильник</h1>
  <div id="time">--:--:--</div>
  <div id="date">--.--.----</div>
  <form action="/setAlarm" method="POST">
    <label for="alarm">Время будильника:</label><br>
    <input type="time" id="alarm" name="alarm" required><br>
    <input type="submit" value="Установить будильник">
  </form>
  <div id="log">Загрузка логов...</div>
  <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();

    function updateLog() {
      fetch('/log')
        .then(response => response.text())
        .then(logData => {
          document.getElementById('log').textContent = logData;
          const logDiv = document.getElementById('log');
          logDiv.scrollTop = logDiv.scrollHeight;
        })
        .catch(err => console.error(err));
    }
    setInterval(updateLog, 1000);
    updateLog();
  </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); // Зуммер выключен

  // Инициализация EEPROM
  EEPROM.begin(512);

  // Чтение сохраненных Wi-Fi данных из EEPROM
  ssid = readStringFromEEPROM(0);
  password = readStringFromEEPROM(100);

  // Проверка наличия сохраненных SSID и пароля
  if (ssid.length() == 0 || password.length() == 0) {
    logPrintln("Нет сохраненных данных Wi-Fi. Запуск точки доступа.");
    apMode = true;
  } else {
    // Подключение к Wi-Fi
    logPrint("Подключение к Wi-Fi...");
    WiFi.begin(ssid.c_str(), password.c_str());

    unsigned long startAttemptTime = millis();
    const unsigned long wifiConnectingTimeout = 20000; // 20 секунд

    // Ожидание подключения
    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < wifiConnectingTimeout) {
      delay(500);
      logPrint(".");
    }

    if (WiFi.status() == WL_CONNECTED) {
      logPrintln("\nПодключено к Wi-Fi сети.");
      logPrint("Внутренний IP адрес: ");
      logPrintln(WiFi.localIP().toString());

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

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

      // Настройка callbacks для OTA
      ArduinoOTA.onStart([]() {
        String type;
        if (ArduinoOTA.getCommand() == U_FLASH)
          type = "sketch";
        else // U_SPIFFS
          type = "filesystem";

        logPrintln("Начало OTA обновления: " + type);
      });

      ArduinoOTA.onEnd([]() {
        logPrintln("\nOTA обновлена. Перезагрузка...");

        // Получение текущего времени для отметки времени обновления
        String uploadTime = "";
        if (!apMode && timeClient.isTimeSet()) {
          time_t rawTime = timeClient.getEpochTime();
          struct tm* timeInfo = localtime(&rawTime);
          char timeArray[20];
          strftime(timeArray, sizeof(timeArray), "%d.%m.%Y %H:%M:%S", timeInfo);
          uploadTime = String(timeArray);
        } else {
          uploadTime = "Неизвестно";
        }

        // Сохранение времени обновления в EEPROM
        writeFirmwareInfo(uploadTime);
        EEPROM.commit();
      });

      ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
        int percent = (progress / (total / 100));
        logPrint("Обновление OTA: " + String(percent) + "%\n");
      });

      ArduinoOTA.onError([](ota_error_t error) {
        logPrintln("Ошибка OTA: " + String(error));
      });

      ArduinoOTA.begin();
      logPrintln("OTA настроен и готов к использованию.");

      // Настройка обработчиков веб-сервера
      server.on("/", handleRoot);
      server.on("/setAlarm", HTTP_POST, handleSetAlarm);
      server.on("/time", handleTime);
      server.on("/log", handleLog);
      server.on("/firmware.txt", handleFirmware); // Добавлен обработчик

      // Запуск веб-сервера
      server.begin();
      logPrintln("Веб-сервер запущен на порту 80");

      apMode = false;
    } else {
      logPrintln("\nНе удалось подключиться к Wi-Fi за 20 секунд. Запуск режима точки доступа.");
      apMode = true;
    }
  }

  if (apMode) {
    startAPMode();
  }
}

// Функция запуска режима точки доступа
void startAPMode() {
  WiFi.disconnect();
  WiFi.mode(WIFI_AP);
  WiFi.softAP("ESP_AP");
  
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("Точка доступа запущена. IP адрес: ");
  Serial.println(myIP);
  
  // Настраиваем обработчики веб-сервера
  server.on("/", handleRootAP);
  server.on("/saveWiFi", handleSaveWiFi);
  server.on("/status", handleStatus);
  server.on("/firmware.txt", handleFirmware); // Добавлен обработчик в режиме AP
  
  server.begin();
  logPrintln("HTTP сервер запущен.");
}

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

  if (!apMode) {
    // Обработка OTA
    ArduinoOTA.handle();

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

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

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

    // Мигание светодиода
    unsigned long currentMillis = millis();
    if (currentMillis - previousMillisLED >= intervalLED) {
      previousMillisLED = currentMillis;
      ledState = !ledState;
//      digitalWrite(LED_PIN, ledState ? LOW : HIGH);
    }
  } else {
    // В режиме AP можно реализовать другую логику, если требуется
    // Например, отображать на дисплее информацию о точке доступа
    u8g2.firstPage();
    do {
      u8g2.setFont(u8g2_font_helvR12_tr);
      u8g2.drawStr(0, 20, "Режим точки");
      u8g2.drawStr(0, 40, "доступа");
      u8g2.drawStr(0, 60, "SSID: ESP_AP");
    } while (u8g2.nextPage());
  }
}

// Функция логирования сообщений
void logPrint(String message) {
  // Получаем текущее время (если подключены к Wi-Fi)
  String timeStr = "";
  if (!apMode && timeClient.isTimeSet()) {
    time_t rawTime = timeClient.getEpochTime();
    struct tm* timeInfo = localtime(&rawTime);
    char timeArray[9];
    strftime(timeArray, sizeof(timeArray), "%H:%M:%S", timeInfo);
    timeStr = String(timeArray);
  } else {
    timeStr = "--:--:--";
  }

  String logMessage = timeStr + " - " + message;

  Serial.print(logMessage);
  logBuffer += logMessage;

  // Ограничиваем размер буфера логов
  if (logBuffer.length() > maxLogSize) {
    logBuffer.remove(0, logBuffer.length() - maxLogSize);
  }
}

void logPrintln(String message) {
  logPrint(message + "\n");
}

// Функция отображения времени и даты
void displayTime() {
  // Получение текущего времени
  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);

  u8g2.firstPage();
  do {
    // Шрифт для времени
    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 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);
    }
  } while (u8g2.nextPage());
}

// Функция рисования иконки будильника
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) {
      logPrintln("Будильник сработал!");
      alarmActive = true;
      alarmStartTime = millis();
      alarmTime = ""; // Сбрасываем будильник после срабатывания
      buzzerState = 0;
      buzzerPreviousMillis = millis();
      blinkState = false;
      previousMillisBlink = millis();
    }
  }
}

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

  // Управление миганием
  if (currentMillis - previousMillisBlink >= blinkInterval) {
    previousMillisBlink = currentMillis;
    blinkState = !blinkState;
  }

  u8g2.firstPage();
  do {
    if (blinkState) {
      // Отображаем время и дату
      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);

      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);
    } else {
      // Отображаем "ALARM"
      u8g2.setFont(u8g2_font_fub25_tr);
      const char* alarmText = "ALARM";
      int16_t textWidth = u8g2.getStrWidth(alarmText);
      int16_t textX = (128 - textWidth) / 2;
      int16_t textY = 36;
      u8g2.drawStr(textX, textY, alarmText);
    }
  } while (u8g2.nextPage());

  // Управление зуммером
  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.clearDisplay();
  }
}

// Обработчики веб-сервера в режиме STA
void handleRoot() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }
  String html((__FlashStringHelper*)htmlPage);
  server.send(200, "text/html; charset=utf-8", html);
}

void handleSetAlarm() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }

  if (server.hasArg("alarm")) {
    alarmTime = server.arg("alarm");
    logPrintln("Будильник установлен на: " + alarmTime);
    server.sendHeader("Location", "/");
    server.send(303);
  } else {
    server.send(400, "text/plain; charset=utf-8", "Неверный запрос");
  }
}

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];
  char dateString[11];

  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; charset=utf-8", dateTimeString);
}

void handleLog() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }
  server.send(200, "text/plain; charset=utf-8", logBuffer);
}

// Обработчики веб-сервера в режиме AP
void handleRootAP() {
  String html = "<html>\
  <head><title>Настройка Wi-Fi</title></head>\
  <body>\
  <h1>Настройка Wi-Fi</h1>\
  <form action='/saveWiFi' method='post'>\
  SSID:<br><input type='text' name='ssid'><br>\
  Пароль:<br><input type='password' name='password'><br><br>\
  <input type='submit' value='Сохранить'>\
  </form>\
  <p><a href='/firmware.txt'>Информация о прошивке</a></p>\
  </body>\
  </html>";
      
  server.send(200, "text/html; charset=utf-8", html);
}

void handleSaveWiFi() {
  String newSSID = server.arg("ssid");
  String newPassword = server.arg("password");

  // Сохраняем новые данные в EEPROM
  writeStringToEEPROM(0, newSSID);
  writeStringToEEPROM(100, newPassword);
  EEPROM.commit();

  String html = "<html><body><h1>Настройки сохранены. Перезагрузка устройства...</h1><meta http-equiv='refresh' content='2;url=/firmware.txt'></body></html>";
  server.send(200, "text/html; charset=utf-8", html);

  delay(2000);
  ESP.restart();
}

void handleStatus() {
  String html = "<html>\
  <head><title>Статус оборудования</title></head>\
  <body>\
  <h1>Состояние оборудования</h1>";

  if (!apMode && timeClient.isTimeSet()) {
    time_t rawTime = timeClient.getEpochTime();
    struct tm* timeInfo = localtime(&rawTime);
    char timeString[9];
    char dateString[11];
    strftime(timeString, sizeof(timeString), "%H:%M:%S", timeInfo);
    strftime(dateString, sizeof(dateString), "%d.%m.%Y", timeInfo);

    html += "<p>Текущее время: " + String(timeString) + " " + String(dateString) + "</p>";
  } else {
    html += "<p>Время недоступно</p>";
  }

  html += "<p>Firmware version: " + String(firmwareVersion) + "</p>";

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

// Обработчик для firmware.txt
void handleFirmware() {
  if (!server.authenticate(www_username, www_password)) {
    return server.requestAuthentication();
  }
  String firmwareInfo = readFirmwareInfo();
  if (firmwareInfo.length() == 0) {
    firmwareInfo = "Информация о последней прошивке недоступна.";
  }
  String response = "Firmware version: " + String(firmwareVersion) + "\nLast firmware update: " + firmwareInfo;
  server.send(200, "text/plain; charset=utf-8", response);
}

// Функции для работы с EEPROM
void writeStringToEEPROM(int addrOffset, const String &strToWrite) {
  byte len = strToWrite.length();
  EEPROM.write(addrOffset, len);
  for (int i = 0; i < len; i++) {
    EEPROM.write(addrOffset + 1 + i, strToWrite[i]);
  }
}

String readStringFromEEPROM(int addrOffset) {
  int len = EEPROM.read(addrOffset);
  if (len < 0 || len > 31) return ""; // Проверка на допустимую длину
  char data[len + 1];
  for (int i = 0; i < len; i++) {
    data[i] = EEPROM.read(addrOffset + 1 + i);
  }
  data[len] = '\0';
  return String(data);
}

// Функция записи информации о прошивке в EEPROM
void writeFirmwareInfo(const String &info) {
  int addr = FIRMWARE_INFO_ADDR;
  byte len = info.length();
  if (len > 100) len = 100; // Ограничение длины, чтобы оставить место
  EEPROM.write(addr, len);
  for (int i = 0; i < len; i++) {
    EEPROM.write(addr + 1 + i, info[i]);
  }
}

// Функция чтения информации о прошивке из EEPROM
String readFirmwareInfo() {
  int addr = FIRMWARE_INFO_ADDR;
  byte len = EEPROM.read(addr);
  if (len == 0 || len > 100) return "";
  char data[len +1];
  for (int i = 0; i < len; i++) {
    data[i] = EEPROM.read(addr + 1 + i);
  }
  data[len] = '\0';
  return String(data);
}
```

### Пояснения к изменениям:

1. **Добавление версии прошивки:**
   ```cpp
   const char* firmwareVersion = "1.0.0"; // Добавлено для версии прошивки
   ```

2. **Определение адреса в EEPROM для хранения информации о прошивке:**
   ```cpp
   #define FIRMWARE_INFO_ADDR 200
   ```

3. **Добавление функций для записи и чтения информации о прошивке:**
   ```cpp
   void writeFirmwareInfo(const String &info) {
     int addr = FIRMWARE_INFO_ADDR;
     byte len = info.length();
     if (len > 100) len = 100; // Ограничение длины, чтобы оставить место
     EEPROM.write(addr, len);
     for (int i = 0; i < len; i++) {
       EEPROM.write(addr + 1 + i, info[i]);
     }
   }

   String readFirmwareInfo() {
     int addr = FIRMWARE_INFO_ADDR;
     byte len = EEPROM.read(addr);
     if (len == 0 || len > 100) return "";
     char data[len +1];
     for (int i = 0; i < len; i++) {
       data[i] = EEPROM.read(addr + 1 + i);
     }
     data[len] = '\0';
     return String(data);
   }
   ```

4. **Настройка колбэков для ArduinoOTA, особенно `onEnd`, чтобы сохранить информацию о последнем обновлении прошивки:**
   ```cpp
   ArduinoOTA.onEnd([]() {
     logPrintln("\nOTA обновлена. Перезагрузка...");

     // Получение текущего времени для отметки времени обновления
     String uploadTime = "";
     if (!apMode && timeClient.isTimeSet()) {
       time_t rawTime = timeClient.getEpochTime();
       struct tm* timeInfo = localtime(&rawTime);
       char timeArray[20];
       strftime(timeArray, sizeof(timeArray), "%d.%m.%Y %H:%M:%S", timeInfo);
       uploadTime = String(timeArray);
     } else {
       uploadTime = "Неизвестно";
     }

     // Сохранение времени обновления в EEPROM
     writeFirmwareInfo(uploadTime);
     EEPROM.commit();
   });
   ```

5. **Добавление обработчика для маршрута `/firmware.txt`, который возвращает версию прошивки и время последнего обновления:**
   ```cpp
   void handleFirmware() {
     if (!server.authenticate(www_username, www_password)) {
       return server.requestAuthentication();
     }
     String firmwareInfo = readFirmwareInfo();
     if (firmwareInfo.length() == 0) {
       firmwareInfo = "Информация о последней прошивке недоступна.";
     }
     String response = "Firmware version: " + String(firmwareVersion) + "\nLast firmware update: " + firmwareInfo;
     server.send(200, "text/plain; charset=utf-8", response);
   }
   ```

6. **Регистрация обработчика маршрута `/firmware.txt` как в режиме STA, так и в режиме AP:**
   ```cpp
   // В режиме STA
   server.on("/firmware.txt", handleFirmware); // Добавлен обработчик

   // В режиме AP
   server.on("/firmware.txt", handleFirmware); // Добавлен обработчик в режиме AP
   ```

7. **Обновление HTML-страницы режима AP для добавления ссылки на `firmware.txt`:**
   ```cpp
   String html = "<html>\
   <head><title>Настройка Wi-Fi</title></head>\
   <body>\
   <h1>Настройка Wi-Fi</h1>\
   <form action='/saveWiFi' method='post'>\
   SSID:<br><input type='text' name='ssid'><br>\
   Пароль:<br><input type='password' name='password'><br><br>\
   <input type='submit' value='Сохранить'>\
   </form>\
   <p><a href='/firmware.txt'>Информация о прошивке</a></p>\
   </body>\
   </html>";
   ```

8. **Обновление обработчика `/status` для отображения версии прошивки:**
   ```cpp
   html += "<p>Firmware version: " + String(firmwareVersion) + "</p>";
   ```

### Как это работает:

- **При OTA обновлении:**
  - Когда прошивка успешно обновляется через OTA, колбэк `onEnd` срабатывает. Он получает текущее время (если оно доступно через NTP) и сохраняет его в EEPROM с использованием функции `writeFirmwareInfo`.
  
- **Доступ к информации о прошивке:**
  - Пользователь может перейти по адресу `http://<IP_ESP>/firmware.txt` (или по ссылке на этой странице) и увидеть версию прошивки и время последнего обновления.
  
- **Хранение данных в EEPROM:**
  - Информация о прошивке хранится на адресе `FIRMWARE_INFO_ADDR (200)` в EEPROM. Первая байт содержит длину строки времени, а последующие байты содержат саму строку.

### Дополнительные рекомендации:

1. **Ограничение длины строки времени:**
   - В функции `writeFirmwareInfo` я установил ограничение длины строки до 100 символов, чтобы избежать переполнения EEPROM.

2. **Инициализация EEPROM:**
   - Убедитесь, что размер EEPROM (512 байт) достаточен для ваших нужд. ESP-01S имеет ограниченный объем памяти, поэтому используйте ее с осторожностью.

3. **Безопасность:**
   - Убедитесь, что доступ к маршруту `/firmware.txt` защищен с помощью аутентификации, как показано в обработчике.

4. **Обновление версии прошивки:**
   - При каждом изменении прошивки обновляйте значение `firmwareVersion`, чтобы точно отображать текущую версию.

5. **Таймеры и NTP:**
   - Убедитесь, что NTP-клиент успешно синхронизирует время для корректного отображения времени последнего обновления.

Теперь ваша ESP-01S будет сохранять и предоставлять информацию о последнем обновлении прошивки, что значительно облегчит управление и мониторинг устройства.

turns-00030.parquet:16031

902fddb2c23abf11f72b7514
turn 1/1o1-mini-2024-09-12ChineseUnited States161 words
degenerate_repetitionAbsentFinal dense release
USER
用 C# 写一段代码
ASSISTANT
当然可以!以下是一个简单的C#控制台应用程序示例,该程序询问用户的姓名并进行个性化问候:

```csharp
using System;

namespace HelloWorldApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // 欢迎消息
            Console.WriteLine("请输入您的姓名:");

            // 读取用户输入
            string name = Console.ReadLine();

            // 打印个性化问候
            Console.WriteLine($"你好, {name}!欢迎使用C#程序。");

            // 保持控制台窗口打开,直到用户按下任意键
            Console.WriteLine("按任意键退出...");
            Console.ReadKey();
        }
    }
}
```

### 代码说明

1. **命名空间和类定义**
    ```csharp
    namespace HelloWorldApp
    {
        class Program
        {
            // ...
        }
    }
    ```
    - `namespace` 用于组织代码,防止命名冲突。
    - `Program` 类包含程序的入口点。

2. **Main 方法**
    ```csharp
    static void Main(string[] args)
    {
        // ...
    }
    ```
    - `Main` 方法是C#应用程序的入口点。
    - `args` 参数用于接收命令行参数(本例中未使用)。

3. **输出和输入**
    ```csharp
    Console.WriteLine("请输入您的姓名:");
    string name = Console.ReadLine();
    Console.WriteLine($"你好, {name}!欢迎使用C#程序。");
    ```
    - `Console.WriteLine` 用于在控制台输出文本。
    - `Console.ReadLine` 读取用户输入的文本。
    - 使用字符串插值 (`$"..."`) 将用户输入整合到输出中。

4. **保持控制台窗口打开**
    ```csharp
    Console.WriteLine("按任意键退出...");
    Console.ReadKey();
    ```
    - `Console.ReadKey` 等待用户按下任意键,以防程序执行完毕后控制台窗口立即关闭。

### 如何运行代码

1. **使用Visual Studio**
    - 打开Visual Studio,创建一个新的“控制台应用程序”项目。
    - 将上述代码复制粘贴到`Program.cs`文件中。
    - 按`F5`键运行程序。

2. **使用命令行**
    - 确保已安装 [.NET SDK](https://dotnet.microsoft.com/download)。
    - 创建一个新的控制台项目:
        ```bash
        dotnet new console -n HelloWorldApp
        ```
    - 进入项目目录:
        ```bash
        cd HelloWorldApp
        ```
    - 替换`Program.cs`内容为上述代码。
    - 运行程序:
        ```bash
        dotnet run
        ```

### 示例输出

```
请输入您的姓名:
张伟
你好, 张伟!欢迎使用C#程序。
按任意键退出...
```

如果您有特定的需求或希望实现更复杂的功能,请告诉我,我可以为您提供更详细的代码示例!

turns-00030.parquet:16032

dd9fc3d082ae097f46263ef6
turn 1/1gpt-4o-mini-2024-07-18EnglishRussia20 words
degenerate_repetitionAbsentFinal dense release
USER
переведи You may not access Notion from a prohibited jurisdiction.
ASSISTANT
Вы не можете получить доступ к Notion из запрещенной юрисдикции.

turns-00030.parquet:16033

c1f21802ba1e822565eb063d
turn 1/4gpt-4o-2024-08-06EnglishIndia90 words
degenerate_repetitionAbsentFinal dense release
USER
Rewrite:
TeamViewer’s Remote client software for Windows has discovered a critical security vulnerability. This vulnerability could potentially allow attackers to elevate their privileges on affected systems.

The flaw, identified as CVE-2024-7479 and CVE-2024-7481, affects multiple versions of TeamViewer’s Windows Remote full client and Remote Host products
ASSISTANT
A critical security vulnerability has been identified in TeamViewer’s Remote client software for Windows, potentially enabling attackers to gain elevated privileges on affected systems.

Designated as CVE-2024-7479 and CVE-2024-7481, this flaw impacts various versions of TeamViewer’s Windows Remote full client and Remote Host products.

turns-00030.parquet:16034

44716867510a91c28916c91a
turn 2/4gpt-4o-2024-08-06EnglishIndia96 words
degenerate_repetitionAbsentFinal dense release
USER
Rewrite:
The vulnerability stems from improper verification of cryptographic signatures in the TeamViewer_service.exe component.

This weakness could enable an attacker with local, unprivileged access to a Windows system to escalate their privileges and install drivers. The severity of this flaw is underscored by its high CVSS3.1 base score of 8.8.
ASSISTANT
The vulnerability arises from inadequate verification of cryptographic signatures within the TeamViewer_service.exe component.

This weakness might allow an attacker with local, unprivileged access to a Windows system to elevate their privileges and install drivers. Its severity is highlighted by a high CVSS3.1 base score of 8.8.

turns-00030.parquet:16035

d98e379d36cc094248d11fec
turn 3/4gpt-4o-2024-08-06EnglishIndia56 words
degenerate_repetitionAbsentFinal dense release
USER
Rewrite:
Affected versions include TeamViewer Remote full client and Remote Host for Windows versions earlier than 15.58.4 and several older major versions dating back to version 11.
ASSISTANT
The affected versions include TeamViewer Remote full client and Remote Host for Windows versions prior to 15.58.4, as well as several earlier major versions dating back to version 11.

turns-00030.parquet:16036

da48e624ec635ad49f192c1d
turn 4/4gpt-4o-2024-08-06EnglishIndia93 words
degenerate_repetitionAbsentFinal dense release
USER
rewrite:
This vulnerability could potentially allow attackers to elevate their privileges on affected systems.
The vulnerability stems from improper verification of cryptographic signatures in the TeamViewer_service.exe component.

This weakness could enable an attacker with local, unprivileged access to a Windows system to escalate their privileges and install drivers.
ASSISTANT
This vulnerability might enable attackers to escalate their privileges on affected systems. It originates from improper verification of cryptographic signatures in the TeamViewer_service.exe component.

This flaw could allow an attacker with local, unprivileged access to a Windows system to elevate their privileges and install drivers.

turns-00030.parquet:16037

adc6b8409274cb42c4ed563a
turn 1/1o1-mini-2024-09-12EnglishSingapore686 words
degenerate_repetitionAbsentFinal dense release
USER
写一个python代码计算makedown表格的行与列:```markdown
|          | pH (H2O) | EC (ms/cm) | SOC (g/kg) | TN (g/kg) | TP (g/kg) | TK (g/kg) | AN (mg/kg) | AP (mg/kg) | AK (mg/kg) |
|----------|----------|------------|------------|-----------|-----------|-----------|------------|------------|------------|
| CK       | 3.96 (0.04) bc | 0.82 (0.01) b | 12.71 (0.69) b | 1.46 (0.03) b | 0.54 (0.01) b | 14.34 (0.03) c | 196.02 (19.7) d | 15.55 (1.27) d | 84.67 (8.63) |
| Low PE   | 3.89 (0.04) cd | 0.81 (0.02) b | 13.32 (0.73) b | 1.45 (0.02) b | 0.53 (0.02) b | 14.31 (0.20) c | 194.73 (14.1) c | 86.33 (8.61) | 3.51 (0.33) b |
| High PE  | 3.86 (0.05) d | 0.83 (0.02) b | 13.37 (0.53) b | 1.44 (0.03) b | 0.53 (0.03) b | 14.21 (0.12) c | 79.07 (7.05) b | 14.84 (1.46) b | 86.01 (8.61) |
| BC       | 4.08 (0.04) a | 1.06 (0.07) a | 19.14 (1.16) a | 1.66 (0.02) a | 0.60 (0.04) a | 15.05 (1.32) b | 215.69 (24.4) a | 95.33 (9.76) a | 4.00 (0.33) b |
| Low PE + BC | 4.06 (0.04) a | 1.07 (0.04) a | 19.83 (1.49) a | 1.66 (0.02) a | 0.56 (0.02) ab | 15.45 (1.40) a | 213.92 (19.31) a | 97.67 (9.76) a | 4.51 (0.49) a |
| High PE + BC | 4.00 (0.06) ab | 1.11 (0.01) a | 19.65 (0.37) a | 1.66 (0.05) a | 0.55 (0.04) ab | 15.41 (1.54) a | 211.63 (16.38) b | 98.67 (9.86) a | 4.51 (1.99) bc |
```
ASSISTANT
当然,您可以使用以下Python代码来计算给定Markdown表格的行数和列数。这个脚本将解析表格内容,忽略代码块标记,并准确计算表格中的行和列。

```python
def count_markdown_table_dimensions(markdown_text):
    """
    计算Markdown表格的行数和列数。

    参数:
    markdown_text (str): 包含Markdown表格的字符串。

    返回:
    tuple: (行数, 列数)
    """
    # 将输入文本按行分割
    lines = markdown_text.strip().split('\n')
    
    # 移除代码块标记(```markdown 和 ```),如果存在
    if lines and lines[0].startswith('```'):
        lines = lines[1:]
    if lines and lines[-1].startswith('```'):
        lines = lines[:-1]
        
    # 过滤出表格相关的行(包含|符号的行)
    table_lines = [line.strip() for line in lines if '|' in line]
    
    if len(table_lines) < 2:
        raise ValueError("表格格式不正确,至少需要包含标题行和分隔行。")
    
    # 第1行是标题,第2行是分隔符,后续是数据行
    header = table_lines[0]
    separator = table_lines[1]
    
    # 计算列数
    # 分割标题行,移除首尾的|,并按|分隔
    columns = [col.strip() for col in header.strip('|').split('|')]
    num_columns = len(columns)
    
    # 计算数据行数(总行数减去标题和分隔行)
    data_rows = table_lines[2:]
    num_rows = len(data_rows)
    
    return num_rows, num_columns

# 示例使用
markdown_table = """```markdown
|          | pH (H2O) | EC (ms/cm) | SOC (g/kg) | TN (g/kg) | TP (g/kg) | TK (g/kg) | AN (mg/kg) | AP (mg/kg) | AK (mg/kg) |
|----------|----------|------------|------------|-----------|-----------|-----------|------------|------------|------------|
| CK       | 3.96 (0.04) bc | 0.82 (0.01) b | 12.71 (0.69) b | 1.46 (0.03) b | 0.54 (0.01) b | 14.34 (0.03) c | 196.02 (19.7) d | 15.55 (1.27) d | 84.67 (8.63) |
| Low PE   | 3.89 (0.04) cd | 0.81 (0.02) b | 13.32 (0.73) b | 1.45 (0.02) b | 0.53 (0.02) b | 14.31 (0.20) c | 194.73 (14.1) c | 86.33 (8.61) | 3.51 (0.33) b |
| High PE  | 3.86 (0.05) d | 0.83 (0.02) b | 13.37 (0.53) b | 1.44 (0.03) b | 0.53 (0.03) b | 14.21 (0.12) c | 79.07 (7.05) b | 14.84 (1.46) b | 86.01 (8.61) |
| BC       | 4.08 (0.04) a | 1.06 (0.07) a | 19.14 (1.16) a | 1.66 (0.02) a | 0.60 (0.04) a | 15.05 (1.32) b | 215.69 (24.4) a | 95.33 (9.76) a | 4.00 (0.33) b |
| Low PE + BC | 4.06 (0.04) a | 1.07 (0.04) a | 19.83 (1.49) a | 1.66 (0.02) a | 0.56 (0.02) ab | 15.45 (1.40) a | 213.92 (19.31) a | 97.67 (9.76) a | 4.51 (0.49) a |
| High PE + BC | 4.00 (0.06) ab | 1.11 (0.01) a | 19.65 (0.37) a | 1.66 (0.05) a | 0.55 (0.04) ab | 15.41 (1.54) a | 211.63 (16.38) b | 98.67 (9.86) a | 4.51 (1.99) bc |
```"""

rows, cols = count_markdown_table_dimensions(markdown_table)
print(f"行数: {rows}, 列数: {cols}")
```

### 输出结果
```
行数: 6, 列数: 10
```

### 代码说明

1. **移除代码块标记**:
   - 检查并移除开头和结尾的代码块标记(```markdown 和 ```),确保只处理表格内容。

2. **过滤表格行**:
   - 仅保留包含`|`符号的行,因为Markdown表格的每一行都包含`|`。

3. **验证表格格式**:
   - 确保表格至少有标题行和分隔行。如果不足,抛出错误提示。

4. **计算列数**:
   - 通过分割标题行来计算列数。移除首尾的`|`后按`|`分割,并去除多余的空格。

5. **计算行数**:
   - 数据行数等于总的表格行数减去标题行和分隔行。

### 注意事项

- **表格格式**:确保表格的每一行都使用`|`符号分隔,并且有一个分隔行(通常由`---`组成)。
- **代码块标记**:如果您的表格不在代码块中,可以相应地调整代码以跳过移除代码块标记的步骤。
- **异常处理**:脚本中包含基本的错误处理,如果输入的表格格式不正确,会抛出一个错误提示。

通过这种方法,您可以轻松地计算任何符合Markdown语法的表格的行数和列数。

turns-00030.parquet:16038

5cb645070c269c53fc1b2f2a
turn 1/1gpt-4o-mini-2024-07-18FrenchThe Netherlands164 words
degenerate_repetitionAbsentFinal dense release
USER
Que signifie "Motown" dans le domaine de la musique ?
ASSISTANT
"Motown" désigne à la fois un label de musique et un genre musical spécifique qui a émergé aux États-Unis dans les années 1960. Fondé en 1959 par Berry Gordy Jr. à Détroit, dans le Michigan, le label Motown a joué un rôle crucial dans le développement et la popularisation de la musique soul et R&B, en mettant en avant des artistes Afro-Américains.

Le terme "Motown" est également un jeu de mots sur "motor town", un surnom donné à Détroit en raison de son industrie automobile. Motown a produit de nombreux artistes emblématiques tels que Stevie Wonder, Marvin Gaye, The Supremes, The Temptations et bien d'autres. Le son Motown est caractérisé par des mélodies accrocheuses, des arrangements sophistiqués et des harmonies vocales riches, influençant profondément la musique populaire américaine et internationale. Le label a contribué à briser des barrières raciales dans l'industrie de la musique et reste un symbole important de la culture musicale américaine.

turns-00030.parquet:16039

eb91d22c066877bba0990b8e
turn 1/4gpt-4o-mini-2024-07-18RussianRussia441 words
degenerate_repetitionAbsentFinal dense release
USER
Привет. Распиши мне следующую тему по четырем основным направлениям изучения: "Классификации и сравнительный анализ существующих методологических подходов к тестированию наземных комплексов и БПЛА и областей применения таких испытаний".
ASSISTANT
Привет! Ниже представлено разделение темы "Классификации и сравнительный анализ существующих методологических подходов к тестированию наземных комплексов и БПЛА и областей применения таких испытаний" по четырем основным направлениям изучения.

### 1. Классификация методологических подходов к тестированию

- **Традиционные методы тестирования**:
  - Лабораторные испытания: проведение тестов в контролируемых условиях для определения характеристик и функциональности.
  - Полевые испытания: проверка в реальных условиях эксплуатации, включая различные климатические и рельефные условия.

- **Моделирование и симуляция**:
  - Использование программного обеспечения для виртуального моделирования поведения систем.
  - Симуляции для анализа сценариев, которые сложно или невозможно проверить в реальной жизни (например, в условиях экстремальных нагрузок).

- **Комплексные и системные методы**:
  - Подходы, основанные на системной инженерии, которые учитывают взаимодействие различных компонентов системы.
  - Анализ жизненного цикла (LCA) для оценки воздействия на окружающую среду и жизнеспособности.

- **Адаптивные и гибкие методы**:
  - Подходы, которые могут изменяться в зависимости от целей тестирования и требований заказчика.
  - Методики, позволяющие интегрировать обратную связь в процессе испытаний.

### 2. Сравнительный анализ существующих методологических подходов

- **Эффективность**:
  - Плюсы и минусы каждого подхода: традиционные методы могут быть более надежными, но менее затратными по времени, тогда как симуляции могут экономить ресурсы на первом этапе.

- **Точность и достоверность**:
  - Сравнение точности результатов, получаемых из лабораторных испытаний и симуляций. Анализ возможных погрешностей.

- **Гибкость**:
  - Как различные подходы могут адаптироваться к изменениям в технологиях и требованиям рынка.

- **Затраты**:
  - Финансовые и временные затраты на разные методологии, включая необходимость привлечения специалистов и использования оборудования.

### 3. Области применения испытаний наземных комплексов и БПЛА

- **Оборонные и военные технологии**:
  - Применение тестирования для обеспечения эффективной работы военных БПЛА и наземных систем, таких как роботизированные платформы.

- **Гражданская авиация и логистика**:
  - Тестирование БПЛА для доставки товаров, мониторинга и наблюдения.

- **Сельское хозяйство**:
  - Применение БПЛА для мониторинга посевов и проведения аэрофотосъемки.

- **Строительство и индустрия**:
  - Применение наземных комплексов и БПЛА в строительных проектах для аэротеки, инспекции и контроля.

### 4. Проблемы и перспективы развития

- **Технологические вызовы**:
  - Проблемы, с которыми сталкиваются современные методологии, включая надежность программного обеспечения, сброс данных и безопасность.

- **Регуляторные ограничения**:
  - Изменение правовых норм в разных странах, касающихся использования БПЛА и наземных комплексов.

- **Будущее тестирования**:
  - Потенциальные направления развития методологий: внедрение ИИ и машинного обучения для улучшения процессов тестирования, увеличение автономности систем.

- **Интердисциплинарные подходы**:
  - Синергия различных областей знания (инженерия, экология, социология) для создания более эффективных систем тестирования.

Эта структура позволяет глубже изучить тему, сопоставить различные подходы и выявить возможности для дальнейших исследований и разработок в области тестирования наземных комплексов и БПЛА.