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 будет сохранять и предоставлять информацию о последнем обновлении прошивки, что значительно облегчит управление и мониторинг устройства.