JXCT Soil Sensor 7-in-1 v3.4.9 (June 2025)
Professional IoT soil monitoring system with ESP32, Modbus RTU, MQTT, and advanced compensation algorithms
Загрузка...
Поиск...
Не найдено
wifi_manager.cpp
См. документацию.
1
7#include "wifi_manager.h"
8#include "web_routes.h" // 🏗️ Модульная архитектура v2.4.5
9#include "modbus_sensor.h"
10#include "mqtt_client.h"
11#include "jxct_device_info.h"
12#include "jxct_config_vars.h"
13#include "jxct_format_utils.h"
14#include <NTPClient.h>
15#include "thingspeak_client.h"
16#include "logger.h"
17#include "jxct_ui_system.h" // 🎨 Единая система дизайна v2.3.1
18#include "jxct_constants.h"
19
20// Константы
21#define RESET_BUTTON_PIN 0 // GPIO0 для кнопки сброса
22#define WIFI_RECONNECT_INTERVAL 30000 // Интервал между попытками переподключения (30 секунд)
23
24// Глобальные переменные
25bool wifiConnected = false;
27WebServer webServer(DEFAULT_WEB_SERVER_PORT); // Используем константу из jxct_constants.h
28DNSServer dnsServer;
29
30// Переменные для светодиода
31unsigned long ledLastToggle = 0;
32bool ledState = false;
33unsigned long ledBlinkInterval = 0;
34bool ledFastBlink = false;
35
36extern NTPClient* timeClient;
37
38// Объявление функций
39void handleRoot();
40
42{
43 digitalWrite(STATUS_LED_PIN, HIGH);
45 ledFastBlink = false;
46}
47
49{
50 digitalWrite(STATUS_LED_PIN, LOW);
52 ledFastBlink = false;
53}
54
55void setLedBlink(unsigned long interval)
56{
57 ledBlinkInterval = interval;
58 ledFastBlink = false;
59}
60
62{
63 ledBlinkInterval = 100;
64 ledFastBlink = true;
65}
66
68{
69 if (ledBlinkInterval > 0)
70 {
71 unsigned long now = millis();
73 {
74 ledLastToggle = now;
76 digitalWrite(STATUS_LED_PIN, ledState ? HIGH : LOW);
77 }
78 }
79}
80
81// HTML для навигации
82String navHtml()
83{
84 String html = "<div class='nav'>";
85 html += "<a href='/'>" UI_ICON_CONFIG " Настройки</a>";
87 {
88 html += "<a href='/readings'>" UI_ICON_DATA " Показания</a>";
89 html += "<a href='/intervals'>" UI_ICON_INTERVALS " Интервалы</a>"; // v2.3.0
90
91 html += "<a href='/config_manager'>" UI_ICON_FOLDER " Конфигурация</a>"; // v2.3.0
92 html += "<a href='/updates'>🚀 Обновления</a>";
93 html += "<a href='/service'>" UI_ICON_SERVICE " Сервис</a>";
94 }
95 html += "</div>";
96 return html;
97}
98
100{
101 logPrintHeader("ИНИЦИАЛИЗАЦИЯ WiFi", COLOR_GREEN);
102
103 pinMode(STATUS_LED_PIN, OUTPUT);
104 setLedBlink(500);
105
106 // Сначала отключаем WiFi и очищаем настройки
107 WiFi.disconnect(true);
108 WiFi.mode(WIFI_OFF);
109 delay(100);
110
111 loadConfig();
112
113 logSystem("SSID: %s", config.ssid);
114 logDebug("Password: %s", strlen(config.password) > 0 ? "задан" : "не задан");
115
116 if (strlen(config.ssid) > 0 && strlen(config.password) > 0)
117 {
118 logWiFi("Переход в режим STA (клиент)");
119 startSTAMode();
120 }
121 else
122 {
123 logWiFi("Переход в режим AP (точка доступа)");
124 startAPMode();
125 }
126 logPrintSeparator("─", 60);
127}
128
130{
131 updateLed();
133 {
134 dnsServer.processNextRequest();
135 webServer.handleClient();
136
137 // Периодическая попытка вернуться в STA-режим, если точка доступа пуста
138 static unsigned long lastStaRetry = 0;
139 if (WiFi.softAPgetStationNum() == 0 && // никого не подключено
140 millis() - lastStaRetry >= WIFI_RECONNECT_INTERVAL && // прошло ≥ интервала
141 strlen(config.ssid) > 0 && strlen(config.password) > 0) // есть сохранённые уч. данные
142 {
143 lastStaRetry = millis();
144 logWiFi("AP режим: пробуем снова подключиться к WiFi \"%s\"", config.ssid);
145 startSTAMode(); // если не получится, функция сама вернёт нас в AP
146 return; // ждём следующего цикла
147 }
148
149 if (WiFi.softAPgetStationNum() > 0)
150 {
151 setLedOn();
152 }
153 else
154 {
156 }
157 }
158 else if (currentWiFiMode == WiFiMode::STA)
159 {
160 static unsigned long lastReconnectAttempt = 0;
161 static int reconnectAttempts = 0;
162 const int MAX_RECONNECT_ATTEMPTS = 3; // Максимальное количество попыток переподключения перед переходом в AP
163
164 if (WiFi.status() != WL_CONNECTED)
165 {
166 if (!wifiConnected || (millis() - lastReconnectAttempt >= WIFI_RECONNECT_INTERVAL))
167 {
168 wifiConnected = false;
170
171 if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS)
172 {
173 logWarn("Потеряно соединение с WiFi, попытка переподключения %d из %d",
174 reconnectAttempts + 1, MAX_RECONNECT_ATTEMPTS);
175
176 WiFi.disconnect(true);
177 delay(100);
178 WiFi.begin(config.ssid, config.password);
179
180 lastReconnectAttempt = millis();
181 reconnectAttempts++;
182 }
183 else
184 {
185 logError("Не удалось восстановить соединение после %d попыток, переход в AP",
186 MAX_RECONNECT_ATTEMPTS);
187 startAPMode();
188 reconnectAttempts = 0; // Сбрасываем счетчик для следующей сессии
189 }
190 }
191 }
192 else
193 {
194 if (!wifiConnected)
195 {
196 wifiConnected = true;
197 reconnectAttempts = 0; // Сбрасываем счетчик при успешном подключении
198 setLedOn();
199 logSuccess("Подключено к WiFi, IP: %s", WiFi.localIP().toString().c_str());
200 }
201 }
202 webServer.handleClient();
203 }
204}
205
206String getApSsid()
207{
208 uint8_t mac[6];
209 WiFi.macAddress(mac);
210 char buf[20];
211 snprintf(buf, sizeof(buf), "jxct-%02X%02X%02X", mac[3], mac[4], mac[5]);
212 for (int i = 0; buf[i]; ++i) buf[i] = tolower(buf[i]);
213 return String(buf);
214}
215
217{
219 WiFi.disconnect();
220 WiFi.mode(WIFI_AP);
221 String apSsid = getApSsid();
222 WiFi.softAP(apSsid.c_str(), JXCT_WIFI_AP_PASS);
223 dnsServer.start(53, "*", WiFi.softAPIP());
225 setLedBlink(500);
226 logWiFi("Режим точки доступа запущен");
227 logSystem("SSID: %s", apSsid.c_str());
228 logSystem("IP адрес: %s", WiFi.softAPIP().toString().c_str());
229}
230
232{
234 WiFi.disconnect(true); // Полное отключение с очисткой настроек
235 WiFi.mode(WIFI_STA);
236 delay(100); // Даем время на применение режима
237
238 String hostname = getApSsid();
239 WiFi.setHostname(hostname.c_str());
240
241 if (strlen(config.ssid) > 0)
242 {
243 logWiFi("Подключение к WiFi...");
244 WiFi.begin(config.ssid, config.password); // Явно вызываем подключение
245
246 int attempts = 0;
248 unsigned long startTime = millis();
249
250 while (WiFi.status() != WL_CONNECTED &&
251 attempts < WIFI_CONNECTION_ATTEMPTS &&
252 (millis() - startTime) < WIFI_CONNECTION_TIMEOUT)
253 {
254 delay(WIFI_RETRY_DELAY_MS);
255 updateLed();
256 attempts++;
257 logDebug("Попытка подключения %d из %d", attempts, WIFI_CONNECTION_ATTEMPTS);
258
259 // Проверяем кнопку сброса во время подключения
260 if (checkResetButton())
261 {
262 logWarn("Обнаружено длительное нажатие кнопки во время подключения");
263 startAPMode();
264 return;
265 }
266 }
267
268 if (WiFi.status() == WL_CONNECTED)
269 {
270 wifiConnected = true;
271 setLedOn();
272 logSuccess("Подключено к WiFi: %s", config.ssid);
273 logSystem("IP адрес: %s", WiFi.localIP().toString().c_str());
274 logSystem("MAC адрес: %s", WiFi.macAddress().c_str());
275 logSystem("Hostname: %s", hostname.c_str());
276 logSystem("RSSI: %d dBm", WiFi.RSSI());
277 // --- Первичная синхронизация времени NTP (блок до 5 сек) ---
278 if (timeClient == nullptr)
279 {
280 extern WiFiUDP ntpUDP;
281 timeClient = new NTPClient(ntpUDP, "pool.ntp.org", 0, 3600000);
282 timeClient->begin();
283 }
284 if (timeClient)
285 {
286 unsigned long ntpStart = millis();
287 while (!timeClient->forceUpdate() && millis() - ntpStart < 5000)
288 {
289 delay(100);
290 }
291 logSystem("NTP синхронизация: %s", timeClient->isTimeSet() ? "OK" : "не удалось");
292 }
293
295 }
296 else
297 {
298 logError("Не удалось подключиться к WiFi после %d попыток", attempts);
299 startAPMode();
300 }
301 }
302 else
303 {
304 logWarn("SSID не задан, переход в AP");
305 startAPMode();
306 }
307}
308
310{
311 static unsigned long pressStart = 0;
312 static bool wasPressed = false;
313 bool isPressed = digitalRead(RESET_BUTTON_PIN) == LOW;
314 if (isPressed && !wasPressed)
315 {
316 pressStart = millis();
317 wasPressed = true;
319 }
320 else if (!isPressed && wasPressed)
321 {
322 wasPressed = false;
323 setLedBlink(500);
324 return false;
325 }
326 else if (isPressed && wasPressed)
327 {
328 if (millis() - pressStart >= 5000)
329 {
330 return true;
331 }
332 }
333 return false;
334}
335
337{
338 logWarn("Перезагрузка ESP32...");
339 delay(1000);
340 ESP.restart();
341}
342
344 {
345 String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
346 html += "<title>" UI_ICON_STATUS " Статус JXCT</title>";
347 html += "<style>" + String(getUnifiedCSS()) + "</style></head><body><div class='container'>";
348 html += navHtml();
349 html += "<h1>" UI_ICON_STATUS " Статус системы</h1>";
350 html += "<div class='section'><h2>WiFi</h2><ul>";
351 html += "<li>Режим: " + String(currentWiFiMode == WiFiMode::AP ? "Точка доступа" : "Клиент") + "</li>";
353 {
354 html += "<li>SSID: " + String(config.ssid) + "</li>";
355 html += "<li>IP: " + WiFi.localIP().toString() + "</li>";
356 html += "<li>RSSI: " + String(WiFi.RSSI()) + " dBm</li>";
357 }
358 html += "</ul></div>";
359 html += "<div class='section'><h2>Система</h2><ul>";
360 html += "<li>Версия: " + String(DEVICE_SW_VERSION) + "</li>";
361 html += "<li>Время работы: " + String(millis() / 1000) + " сек</li>";
362 html += "<li>Свободная память: " + String(ESP.getFreeHeap()) + " байт</li>";
363 html += "</ul></div>";
364 html += "</div>" + String(getToastHTML()) + "</body></html>";
365 webServer.send(200, "text/html; charset=utf-8", html);
366}
367
369{
370 logInfo("🏗️ Настройка модульного веб-сервера v2.4.5...");
371
372 // ============================================================================
373 // МОДУЛЬНАЯ АРХИТЕКТУРА - Настройка всех маршрутов по группам
374 // ============================================================================
375
376 setupMainRoutes(); // Основные маршруты (/, /save, /status)
377 setupDataRoutes(); // Данные датчика (/readings, /sensor_json, /api/sensor)
378 setupConfigRoutes(); // Конфигурация (/intervals, /config_manager, /api/config/*)
379 setupServiceRoutes(); // Сервис
380 setupOtaRoutes(); // OTA (/updates, api)
381
382 setupErrorHandlers(); // Обработчики ошибок (404, 500) - должны быть последними
383
384 // ============================================================================
385 // ЗАПУСК СЕРВЕРА
386 // ============================================================================
387
388 webServer.begin();
389 logSuccess("🏗️ Модульный веб-сервер v2.4.5 запущен. Режим: %s", currentWiFiMode == WiFiMode::AP ? "AP" : "STA");
390 logSystem("✅ Активные модули: main, data, config, service, ota, error_handlers");
391 logSystem("📋 Полный набор маршрутов готов к использованию");
392}
393
395{
396 String html =
397 "<!DOCTYPE html><html><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, "
398 "initial-scale=1.0'>";
399 html += "<title>" UI_ICON_CONFIG " Настройки JXCT</title>";
400 html += "<style>" + String(getUnifiedCSS()) + "</style></head><body><div class='container'>";
401 html += navHtml();
402 html += "<h1>" UI_ICON_CONFIG " Настройки JXCT</h1>";
403 html += "<form action='/save' method='post'>";
404 html += "<div class='section'><h2>WiFi настройки</h2>";
405 html += "<div class='form-group'><label for='ssid'>SSID:</label><input type='text' id='ssid' name='ssid' value='" +
406 String(config.ssid) + "' required></div>";
407 html +=
408 "<div class='form-group'><label for='password'>Пароль:</label><input type='password' id='password' "
409 "name='password' value='" +
410 String(config.password) + "' required></div></div>";
411
412 // Показываем остальные настройки только в режиме STA
414 {
415 String mqttChecked = config.flags.mqttEnabled ? " checked" : "";
416 html += "<div class='section'><h2>MQTT настройки</h2>";
417 html +=
418 "<div class='form-group'><label for='mqtt_enabled'>Включить MQTT:</label><input type='checkbox' "
419 "id='mqtt_enabled' name='mqtt_enabled'" +
420 mqttChecked + "></div>";
421 html +=
422 "<div class='form-group'><label for='mqtt_server'>MQTT сервер:</label><input type='text' id='mqtt_server' "
423 "name='mqtt_server' value='" +
424 String(config.mqttServer) + "'" + (config.flags.mqttEnabled ? " required" : "") + "></div>";
425 html +=
426 "<div class='form-group'><label for='mqtt_port'>MQTT порт:</label><input type='text' id='mqtt_port' "
427 "name='mqtt_port' value='" +
428 String(config.mqttPort) + "'></div>";
429 html +=
430 "<div class='form-group'><label for='mqtt_user'>MQTT пользователь:</label><input type='text' "
431 "id='mqtt_user' name='mqtt_user' value='" +
432 String(config.mqttUser) + "'></div>";
433 html +=
434 "<div class='form-group'><label for='mqtt_password'>MQTT пароль:</label><input type='password' "
435 "id='mqtt_password' name='mqtt_password' value='" +
436 String(config.mqttPassword) + "'></div>";
437 String hassChecked = config.flags.hassEnabled ? " checked" : "";
438 html +=
439 "<div class='form-group'><label for='hass_enabled'>Интеграция с Home Assistant:</label><input "
440 "type='checkbox' id='hass_enabled' name='hass_enabled'" +
441 hassChecked + "></div></div>";
442 String tsChecked = config.flags.thingSpeakEnabled ? " checked" : "";
443 html += "<div class='section'><h2>ThingSpeak настройки</h2>";
444 html +=
445 "<div class='form-group'><label for='ts_enabled'>Включить ThingSpeak:</label><input type='checkbox' "
446 "id='ts_enabled' name='ts_enabled'" +
447 tsChecked + "></div>";
448 html +=
449 "<div class='form-group'><label for='ts_api_key'>API ключ:</label><input type='text' id='ts_api_key' "
450 "name='ts_api_key' value='" +
451 String(config.thingSpeakApiKey) + "'" + (config.flags.thingSpeakEnabled ? " required" : "") + "></div>";
452 html +=
453 "<div class='form-group'><label for='ts_channel_id'>Channel ID:</label><input type='text' "
454 "id='ts_channel_id' name='ts_channel_id' value='" +
455 String(config.thingSpeakChannelId) + "'></div>";
456 html +=
457 "<div style='color:#888;font-size:13px'>💡 Интервал публикации настраивается в разделе <a "
458 "href='/intervals' style='color:#4CAF50'>Интервалы</a></div></div>";
459 String realSensorChecked = config.flags.useRealSensor ? " checked" : "";
460 html += "<div class='section'><h2>Датчик</h2>";
461 html +=
462 "<div class='form-group'><label for='real_sensor'>Реальный датчик:</label><input type='checkbox' "
463 "id='real_sensor' name='real_sensor'" +
464 realSensorChecked + "></div>";
465
466 // ----------------- ⚙️ Компенсация датчиков -----------------
467 html += "<div class='section'><h2>⚙️ Компенсация датчиков</h2>";
468 String calibChecked = config.flags.calibrationEnabled ? " checked" : "";
469 html += "<div class='form-group'><label for='cal_enabled'>Включить компенсацию:</label><input type='checkbox' id='cal_enabled' name='cal_enabled'" + calibChecked + "></div>";
470 html += "<div class='form-group'><label for='irrig_th'>Порог ∆влажности (%):</label><input type='number' step='0.1' id='irrig_th' name='irrig_th' value='" + String(config.irrigationSpikeThreshold,1) + "'></div>";
471 html += "<div class='form-group'><label for='irrig_hold'>Удержание (мин):</label><input type='number' id='irrig_hold' name='irrig_hold' value='" + String(config.irrigationHoldMinutes) + "'></div>";
472 html += "</div>"; // конец секции компенсации
473
474 // ----------------- 🌱 Агрорекомендации -----------------
475 html += "<div class='section'><h2>🌱 Агрорекомендации</h2>";
476 // Координаты
477 html += "<div class='form-group'><label for='latitude'>Широта:</label><input type='number' step='0.0001' id='latitude' name='latitude' value='" + String(config.latitude,4) + "'></div>";
478 html += "<div class='form-group'><label for='longitude'>Долгота:</label><input type='number' step='0.0001' id='longitude' name='longitude' value='" + String(config.longitude,4) + "'></div>";
479 // Культура
480 html += "<div class='form-group'><label for='crop'>Культура:</label><select id='crop' name='crop'>";
481 html += String("<option value='none'") + (strcmp(config.cropId,"none")==0?" selected":"") + ">нет</option>";
482 html += String("<option value='tomato'") + (strcmp(config.cropId,"tomato")==0?" selected":"") + ">Томат</option>";
483 html += String("<option value='cucumber'") + (strcmp(config.cropId,"cucumber")==0?" selected":"") + ">Огурец</option>";
484 html += String("<option value='pepper'") + (strcmp(config.cropId,"pepper")==0?" selected":"") + ">Перец</option>";
485 html += String("<option value='lettuce'") + (strcmp(config.cropId,"lettuce")==0?" selected":"") + ">Салат</option>";
486 html += String("<option value='strawberry'") + (strcmp(config.cropId,"strawberry")==0?" selected":"") + ">Клубника</option>";
487 html += String("<option value='apple'") + (strcmp(config.cropId,"apple")==0?" selected":"") + ">Яблоня</option>";
488 html += String("<option value='pear'") + (strcmp(config.cropId,"pear")==0?" selected":"") + ">Груша</option>";
489 html += String("<option value='cherry'") + (strcmp(config.cropId,"cherry")==0?" selected":"") + ">Вишня/Черешня</option>";
490 html += String("<option value='raspberry'") + (strcmp(config.cropId,"raspberry")==0?" selected":"") + ">Малина</option>";
491 html += String("<option value='currant'") + (strcmp(config.cropId,"currant")==0?" selected":"") + ">Смородина</option>";
492 html += String("<option value='blueberry'") + (strcmp(config.cropId,"blueberry")==0?" selected":"") + ">Голубика</option>";
493 html += String("<option value='lawn'") + (strcmp(config.cropId,"lawn")==0?" selected":"") + ">Газон</option>";
494 html += String("<option value='grape'") + (strcmp(config.cropId,"grape")==0?" selected":"") + ">Виноград</option>";
495 html += String("<option value='conifer'") + (strcmp(config.cropId,"conifer")==0?" selected":"") + ">Хвойные деревья</option>";
496 html += "</select></div>";
497 // Тип среды выращивания v2.6.1
498 String selectedEnvOutdoor = config.environmentType == 0 ? " selected" : "";
499 String selectedEnvGreenhouse = config.environmentType == 1 ? " selected" : "";
500 String selectedEnvIndoor = config.environmentType == 2 ? " selected" : "";
501 html += "<div class='form-group'><label for='env_type'>Среда:</label><select id='env_type' name='env_type'>";
502 html += String("<option value='0'") + selectedEnvOutdoor + ">Открытый грунт</option>";
503 html += String("<option value='1'") + selectedEnvGreenhouse + ">Теплица</option>";
504 html += String("<option value='2'") + selectedEnvIndoor + ">Комнатная</option></select></div>";
505
506 // Сезонные коэффициенты
507 String seasonalChecked = config.flags.seasonalAdjustEnabled ? " checked" : "";
508 html += "<div class='form-group'><label for='season_adj'>Учитывать сезонность:</label><input type='checkbox' id='season_adj' name='season_adj'" + seasonalChecked + "></div>";
509
510 // Профиль почвы
511 const char* selectedSand = config.soilProfile == 0 ? " selected" : "";
512 const char* selectedLoam = config.soilProfile == 1 ? " selected" : "";
513 const char* selectedPeat = config.soilProfile == 2 ? " selected" : "";
514 const char* selectedClay = config.soilProfile == 3 ? " selected" : "";
515 const char* selectedSandPeat = config.soilProfile == 4 ? " selected" : "";
516 html += "<div class='form-group'><label for='soil_profile_sel'>Профиль почвы:</label><select id='soil_profile_sel' name='soil_profile_sel'>";
517 html += String("<option value='0'") + selectedSand + ">Песок</option>";
518 html += String("<option value='1'") + selectedLoam + ">Суглинок</option>";
519 html += String("<option value='2'") + selectedPeat + ">Торф</option>";
520 html += String("<option value='3'") + selectedClay + ">Глина</option>";
521 html += String("<option value='4'") + selectedSandPeat + ">Песчано-торфяной</option>";
522 html += "</select></div>";
523
524 html += "</div>"; // конец секции агрорекомендаций
525
526 html += "</div>"; // конец секции датчика
527 html += "<div class='section'><h2>NTP</h2>";
528 html +=
529 "<div class='form-group'><label for='ntp_server'>NTP сервер:</label><input type='text' id='ntp_server' "
530 "name='ntp_server' value='" +
531 String(config.ntpServer) + "' required></div>";
532 html +=
533 "<div class='form-group'><label for='ntp_interval'>Интервал обновления NTP (мс):</label><input "
534 "type='number' id='ntp_interval' name='ntp_interval' min='10000' max='86400000' value='" +
535 String(config.ntpUpdateInterval) + "'></div></div>";
536 }
537 html += generateButton(ButtonType::PRIMARY, UI_ICON_SAVE, "Сохранить настройки", "") + "</form>";
538
539 // Добавляем JavaScript для динамического изменения обязательных полей
541 {
542 html += "<script>";
543 html += "document.getElementById('mqtt_enabled').addEventListener('change', function() {";
544 html += " document.getElementById('mqtt_server').required = this.checked;";
545 html += "});";
546 html += "document.getElementById('ts_enabled').addEventListener('change', function() {";
547 html += " document.getElementById('ts_api_key').required = this.checked;";
548 html += "});";
549 html += "</script>";
550 }
551
552 html += "</div>" + String(getToastHTML()) + "</body></html>";
553 webServer.send(200, "text/html; charset=utf-8", html);
554}
Config config
Определения config.cpp:34
void loadConfig()
Определения config.cpp:39
void setupErrorHandlers()
Настройка обработчиков ошибок (404, 500, и т.
Определения error_handlers.cpp:8
#define WIFI_RETRY_DELAY_MS
Определения jxct_config_vars.h:20
Централизованные константы системы JXCT.
constexpr const char * JXCT_WIFI_AP_PASS
Определения jxct_constants.h:121
constexpr int DEFAULT_WEB_SERVER_PORT
Определения jxct_constants.h:41
constexpr unsigned long WIFI_CONNECTION_TIMEOUT
Определения jxct_constants.h:56
constexpr int WIFI_CONNECTION_ATTEMPTS
Определения jxct_constants.h:54
const char * getUnifiedCSS()
Определения jxct_ui_system.cpp:4
String generateButton(ButtonType type, const char *icon, const char *text, const char *action)
Определения jxct_ui_system.cpp:286
const char * getToastHTML()
Определения jxct_ui_system.cpp:320
#define UI_ICON_DATA
Определения jxct_ui_system.h:48
#define UI_ICON_INTERVALS
Определения jxct_ui_system.h:47
#define UI_ICON_STATUS
Определения jxct_ui_system.h:58
#define UI_ICON_SERVICE
Определения jxct_ui_system.h:49
#define UI_ICON_FOLDER
Определения jxct_ui_system.h:57
#define UI_ICON_CONFIG
Определения jxct_ui_system.h:46
#define UI_ICON_SAVE
Определения jxct_ui_system.h:42
@ PRIMARY
Определения jxct_ui_system.h:65
void logDebug(const char *format,...)
Определения logger.cpp:112
void logWarn(const char *format,...)
Определения logger.cpp:78
void logPrintHeader(const char *title, const char *color)
Определения logger.cpp:26
void logPrintSeparator(const char *symbol, int length)
Определения logger.cpp:38
void logSuccess(const char *format,...)
Определения logger.cpp:129
void logError(const char *format,...)
Определения logger.cpp:61
void logSystem(const char *format,...)
Определения logger.cpp:213
void logWiFi(const char *format,...)
Определения logger.cpp:162
void logInfo(const char *format,...)
Определения logger.cpp:95
Система логгирования с красивым форматированием
#define COLOR_GREEN
Определения logger.h:38
WiFiUDP ntpUDP
Определения main.cpp:42
NTPClient * timeClient
Определения main.cpp:43
WebServer webServer
void setupConfigRoutes()
Настройка маршрутов конфигурации (/intervals, /config_manager, /api/config/*)
Определения routes_config.cpp:27
WiFiMode currentWiFiMode
Определения wifi_manager.cpp:26
void setupDataRoutes()
Настройка маршрутов данных датчика (/readings, /sensor_json, /api/sensor)
Определения routes_data.cpp:274
void setupMainRoutes()
Настройка основных маршрутов (/, /save, /status)
Определения routes_main.cpp:7
void setupOtaRoutes()
Настройка маршрутов OTA (/updates, /api/ota/*, /ota/*)
Определения routes_ota.cpp:27
void setupServiceRoutes()
Настройка сервисных маршрутов (/health, /service_status, /reset, /reboot, /ota)
Определения routes_service.cpp:40
static const char DEVICE_SW_VERSION[]
Определения version.h:16
void startSTAMode()
Определения wifi_manager.cpp:231
void handleStatus()
Обработчик статуса (уже существует в wifi_manager.cpp)
Определения wifi_manager.cpp:343
void setLedOn()
Определения wifi_manager.cpp:41
void restartESP()
Определения wifi_manager.cpp:336
DNSServer dnsServer
Определения wifi_manager.cpp:28
#define RESET_BUTTON_PIN
Определения wifi_manager.cpp:21
void setLedBlink(unsigned long interval)
Определения wifi_manager.cpp:55
unsigned long ledBlinkInterval
Определения wifi_manager.cpp:33
void handleRoot()
Обработчик главной страницы (уже существует в wifi_manager.cpp)
Определения wifi_manager.cpp:394
void startAPMode()
Определения wifi_manager.cpp:216
void updateLed()
Определения wifi_manager.cpp:67
void setLedOff()
Определения wifi_manager.cpp:48
void setLedFastBlink()
Определения wifi_manager.cpp:61
bool ledFastBlink
Определения wifi_manager.cpp:34
bool wifiConnected
Определения wifi_manager.cpp:25
bool checkResetButton()
Определения wifi_manager.cpp:309
#define WIFI_RECONNECT_INTERVAL
Определения wifi_manager.cpp:22
void setupWiFi()
Определения wifi_manager.cpp:99
String navHtml()
Определения wifi_manager.cpp:82
bool ledState
Определения wifi_manager.cpp:32
unsigned long ledLastToggle
Определения wifi_manager.cpp:31
void setupWebServer()
Определения wifi_manager.cpp:368
void handleWiFi()
Определения wifi_manager.cpp:129
String getApSsid()
Определения wifi_manager.cpp:206
#define STATUS_LED_PIN
Определения wifi_manager.h:22
WiFiMode
Определения wifi_manager.h:12
@ AP
Определения wifi_manager.h:13
@ STA
Определения wifi_manager.h:14