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
Загрузка...
Поиск...
Не найдено
routes_service.cpp
См. документацию.
1
6
13#include "../wifi_manager.h"
14#include "../modbus_sensor.h"
15#include "../mqtt_client.h"
16#include <ArduinoJson.h>
18
19extern WebServer webServer;
21
22// Объявления внешних функций
23extern String navHtml();
24extern String getApSsid();
25
26// Внешние переменные для статусов
27extern String sensorLastError;
28
29// Объявления внешних функций
30extern const char* getThingSpeakLastPublish();
31extern const char* getThingSpeakLastError();
32
33// --- API v1 helpers ---
34static void sendHealthJson();
35static void sendServiceStatusJson();
36
37// Локальные функции
38String formatUptime(unsigned long milliseconds);
39
41{
42 logDebug("Настройка сервисных маршрутов");
43
44 // Health endpoint (legacy) and API v1
45 webServer.on("/health", HTTP_GET, sendHealthJson);
47
48 // Service status endpoint legacy + API v1
49 webServer.on("/service_status", HTTP_GET, sendServiceStatusJson);
51
52 // Красивая страница сервисов (оригинальный дизайн)
53 webServer.on(
54 "/service", HTTP_GET,
55 []()
56 {
57 logWebRequest("GET", "/service", webServer.client().remoteIP().toString());
58
60 {
61 webServer.send(200, "text/html; charset=utf-8", generateApModeUnavailablePage("Сервис", UI_ICON_SERVICE));
62 return;
63 }
64
65 String html = generatePageHeader("Сервис", UI_ICON_SERVICE);
66 html += navHtml();
67 html += "<h1>" UI_ICON_SERVICE " Сервис</h1>";
68 html += "<div class='info-block' id='status-block'>Загрузка статусов...</div>";
69 html += "<div class='info-block'><b>Производитель:</b> " + String(DEVICE_MANUFACTURER) +
70 "<br><b>Модель:</b> " + String(DEVICE_MODEL) + "<br><b>Версия:</b> " + String(FIRMWARE_VERSION) +
71 "</div>";
72 html += "<div class='section' style='margin-top:20px;'>";
73 html += "<form method='post' action='/reset' style='margin-bottom:10px'>";
74 html += generateButton(ButtonType::DANGER, UI_ICON_RESET, "Сбросить настройки", "") + "</form>";
75 html += "<form method='post' action='/reboot' style='margin-bottom:10px'>";
76 html += generateButton(ButtonType::SECONDARY, "🔄", "Перезагрузить", "") + "</form>";
77 html += "</div>";
78 html +=
79 "<div class='section' style='margin-top:15px;font-size:14px;color:#555'><b>API:</b> <a "
80 "href='/service_status' target='_blank'>/service_status</a> (JSON, статусы сервисов) | <a "
81 "href='/health' target='_blank'>/health</a> (JSON, подробная диагностика)</div>";
82 html += "<script>";
83 html += "function dot(status){";
84 html += "if(status===true)return'<span class=\"status-dot dot-ok\"></span>';";
85 html += "if(status===false)return'<span class=\"status-dot dot-err\"></span>';";
86 html += "if(status==='warn')return'<span class=\"status-dot dot-warn\"></span>';";
87 html += "return'<span class=\"status-dot dot-off\"></span>';";
88 html += "}";
89 html += "function updateStatus(){fetch('/service_status').then(r=>r.json()).then(d=>{let html='';";
90 html +=
91 "html+=dot(d.wifi_connected)+'<b>WiFi:</b> '+(d.wifi_connected?'Подключено ('+d.wifi_ip+', "
92 "'+d.wifi_ssid+', RSSI '+d.wifi_rssi+' dBm)':'Не подключено')+'<br>';";
93 html +=
94 "html+=dot(d.mqtt_enabled?d.mqtt_connected:false)+'<b>MQTT:</b> "
95 "'+(d.mqtt_enabled?(d.mqtt_connected?'Подключено':'Ошибка'+(d.mqtt_last_error?' "
96 "('+d.mqtt_last_error+')':'')):'Отключено')+'<br>';";
97 html +=
98 "html+=dot(d.thingspeak_enabled?(d.thingspeak_last_error?false:true):false)+'<b>ThingSpeak:</b> "
99 "'+(d.thingspeak_enabled?(d.thingspeak_last_error?'Ошибка: "
100 "'+d.thingspeak_last_error:((d.thingspeak_last_pub && d.thingspeak_last_pub!=='0')?'Последняя "
101 "публикация: '+d.thingspeak_last_pub:'Нет публикаций')):'Отключено')+'<br>';";
102 html +=
103 "if(d.thingspeak_enabled && d.thingspeak_last_error){showToast('Ошибка ThingSpeak: "
104 "'+d.thingspeak_last_error,'error');}";
105 html +=
106 "html+=dot(d.hass_enabled)+'<b>Home Assistant:</b> '+(d.hass_enabled?'Включено':'Отключено')+'<br>';";
107 html +=
108 "html+=dot(d.sensor_ok)+'<b>Датчик:</b> '+(d.sensor_ok?'Ок':'Ошибка'+(d.sensor_last_error?' "
109 "('+d.sensor_last_error+')':''));";
110 html += "document.getElementById('status-block').innerHTML=html;";
111 html += "});}setInterval(updateStatus," + String(config.webUpdateInterval) + ");updateStatus();";
112 html += "</script>";
113 html += generatePageFooter();
114 webServer.send(200, "text/html; charset=utf-8", html);
115 });
116
117 // POST обработчики для сервисных функций
118 webServer.on("/reset", HTTP_POST,
119 []()
120 {
121 logWebRequest("POST", webServer.uri(), webServer.client().remoteIP().toString());
122
124 {
125 webServer.send(403, "text/plain", "Недоступно в режиме точки доступа");
126 return;
127 }
128
129 resetConfig();
130 String html =
131 "<!DOCTYPE html><html><head><meta charset='UTF-8'><meta http-equiv='refresh' "
132 "content='2;url=/service'><title>Сброс</title></head><body "
133 "style='font-family:Arial,sans-serif;text-align:center;padding-top:40px'><h2>Настройки "
134 "сброшены</h2><p>Перезагрузка...<br>Сейчас вы будете перенаправлены на страницу "
135 "сервисов.</p></body></html>";
136 webServer.send(200, "text/html; charset=utf-8", html);
137 delay(2000);
138 ESP.restart();
139 });
140
141 webServer.on(API_SYSTEM_RESET, HTTP_POST, [](){ webServer.sendHeader("Location", "/reset", true); webServer.send(307, "text/plain", "Redirect"); });
142
143 webServer.on("/reboot", HTTP_POST,
144 []()
145 {
146 logWebRequest("POST", webServer.uri(), webServer.client().remoteIP().toString());
147
149 {
150 webServer.send(403, "text/plain", "Недоступно в режиме точки доступа");
151 return;
152 }
153
154 String html =
155 "<!DOCTYPE html><html><head><meta charset='UTF-8'><meta http-equiv='refresh' "
156 "content='2;url=/service'><title>Перезагрузка</title></head><body "
157 "style='font-family:Arial,sans-serif;text-align:center;padding-top:40px'><h2>Перезагрузка...</"
158 "h2><p>Сейчас вы будете перенаправлены на страницу сервисов.</p></body></html>";
159 webServer.send(200, "text/html; charset=utf-8", html);
160 delay(2000);
161 ESP.restart();
162 });
163
164 webServer.on(API_SYSTEM_REBOOT, HTTP_POST, [](){ webServer.sendHeader("Location", "/reboot", true); webServer.send(307, "text/plain", "Redirect"); });
165
166 // Старый маршрут /ota более не нужен – сделаем редирект на новую страницу
167 webServer.on("/ota", HTTP_ANY, []() { webServer.sendHeader("Location", "/updates", true); webServer.send(302, "text/plain", "Redirect"); });
168
169 logSuccess("Сервисные маршруты настроены");
170}
171
172// Вспомогательная функция для форматирования времени работы
173String formatUptime(unsigned long milliseconds)
174{
175 unsigned long seconds = milliseconds / 1000;
176 unsigned long minutes = seconds / 60;
177 unsigned long hours = minutes / 60;
178 unsigned long days = hours / 24;
179
180 seconds %= 60;
181 minutes %= 60;
182 hours %= 24;
183
184 String uptime = "";
185 if (days > 0) uptime += String(days) + "д ";
186 if (hours > 0) uptime += String(hours) + "ч ";
187 if (minutes > 0) uptime += String(minutes) + "м ";
188 uptime += String(seconds) + "с";
189
190 return uptime;
191}
192
194 logWebRequest("GET", webServer.uri(), webServer.client().remoteIP().toString());
195 StaticJsonDocument<1024> doc;
196
197 // System info
198 doc["device"]["manufacturer"] = DEVICE_MANUFACTURER;
199 doc["device"]["model"] = DEVICE_MODEL;
200 doc["device"]["version"] = FIRMWARE_VERSION;
201 doc["device"]["uptime"] = millis() / 1000;
202 doc["device"]["free_heap"] = ESP.getFreeHeap();
203 doc["device"]["chip_model"] = ESP.getChipModel();
204 doc["device"]["chip_revision"] = ESP.getChipRevision();
205 doc["device"]["cpu_freq"] = ESP.getCpuFreqMHz();
206
207 // Memory info
208 doc["memory"]["free_heap"] = ESP.getFreeHeap();
209 doc["memory"]["largest_free_block"] = ESP.getMaxAllocHeap();
210 doc["memory"]["heap_size"] = ESP.getHeapSize();
211 doc["memory"]["psram_size"] = ESP.getPsramSize();
212 doc["memory"]["free_psram"] = ESP.getFreePsram();
213
214 // WiFi status
215 doc["wifi"]["connected"] = wifiConnected;
216 if (wifiConnected)
217 {
218 doc["wifi"]["ssid"] = WiFi.SSID();
219 doc["wifi"]["ip"] = WiFi.localIP().toString();
220 doc["wifi"]["rssi"] = WiFi.RSSI();
221 doc["wifi"]["mac"] = WiFi.macAddress();
222 doc["wifi"]["gateway"] = WiFi.gatewayIP().toString();
223 doc["wifi"]["dns"] = WiFi.dnsIP().toString();
224 }
225
226 // MQTT status
227 doc["mqtt"]["enabled"] = (bool)config.flags.mqttEnabled;
228 if (config.flags.mqttEnabled)
229 {
230 doc["mqtt"]["connected"] = mqttClient.connected();
231 doc["mqtt"]["server"] = config.mqttServer;
232 doc["mqtt"]["port"] = config.mqttPort;
233 doc["mqtt"]["last_error"] = getMqttLastError();
234 }
235
236 // ThingSpeak status
237 doc["thingspeak"]["enabled"] = (bool)config.flags.thingSpeakEnabled;
238 if (config.flags.thingSpeakEnabled)
239 {
240 doc["thingspeak"]["last_publish"] = getThingSpeakLastPublish();
241 doc["thingspeak"]["last_error"] = getThingSpeakLastError();
242 doc["thingspeak"]["interval"] = config.thingSpeakInterval;
243 }
244
245 // Home Assistant status
246 doc["homeassistant"]["enabled"] = (bool)config.flags.hassEnabled;
247
248 // Sensor status
249 doc["sensor"]["enabled"] = (bool)config.flags.useRealSensor;
250 doc["sensor"]["valid"] = sensorData.valid;
251 doc["sensor"]["last_read"] = sensorData.last_update;
252 if (sensorLastError.length() > 0)
253 {
254 doc["sensor"]["last_error"] = sensorLastError;
255 }
256
257 // Current readings
258 doc["readings"]["temperature"] = format_temperature(sensorData.temperature);
259 doc["readings"]["humidity"] = format_moisture(sensorData.humidity);
260 doc["readings"]["ec"] = format_ec(sensorData.ec);
261 doc["readings"]["ph"] = format_ph(sensorData.ph);
262 doc["readings"]["nitrogen"] = format_npk(sensorData.nitrogen);
263 doc["readings"]["phosphorus"] = format_npk(sensorData.phosphorus);
264 doc["readings"]["potassium"] = format_npk(sensorData.potassium);
265
266 // Timestamps
267 doc["timestamp"] = millis();
268 doc["boot_time"] = millis();
269
270 String json;
271 serializeJson(doc, json);
272 webServer.send(200, "application/json", json);
273}
274
276 logWebRequest("GET", webServer.uri(), webServer.client().remoteIP().toString());
277 StaticJsonDocument<512> doc;
278 doc["wifi_connected"] = wifiConnected;
279 doc["wifi_ip"] = WiFi.localIP().toString();
280 doc["wifi_ssid"] = WiFi.SSID();
281 doc["wifi_rssi"] = WiFi.RSSI();
282 doc["mqtt_enabled"] = (bool)config.flags.mqttEnabled;
283 doc["mqtt_connected"] = (bool)config.flags.mqttEnabled && mqttClient.connected();
284 doc["mqtt_last_error"] = getMqttLastError();
285 doc["thingspeak_enabled"] = (bool)config.flags.thingSpeakEnabled;
286 doc["thingspeak_last_pub"] = getThingSpeakLastPublish();
287 doc["thingspeak_last_error"] = getThingSpeakLastError();
288 doc["hass_enabled"] = (bool)config.flags.hassEnabled;
289 doc["sensor_ok"] = sensorData.valid;
290 doc["sensor_last_error"] = sensorLastError;
291
292 String json;
293 serializeJson(doc, json);
294 webServer.send(200, "application/json", json);
295}
Config config
Определения config.cpp:34
void resetConfig()
Определения config.cpp:212
void logWebRequest(const String &method, const String &uri, const String &clientIP)
Логирование веб-запросов
Определения error_handlers.cpp:152
const char * FIRMWARE_VERSION
std::string format_ec(float value)
Определения jxct_format_utils.cpp:18
std::string format_moisture(float value)
Определения jxct_format_utils.cpp:4
std::string format_ph(float value)
Определения jxct_format_utils.cpp:25
std::string format_temperature(float value)
Определения jxct_format_utils.cpp:11
std::string format_npk(float value)
Определения jxct_format_utils.cpp:32
#define API_SYSTEM_RESET
Определения jxct_strings.h:14
#define API_SYSTEM_REBOOT
Определения jxct_strings.h:15
#define API_SYSTEM_HEALTH
Определения jxct_strings.h:12
#define API_SYSTEM_STATUS
Определения jxct_strings.h:13
String generateButton(ButtonType type, const char *icon, const char *text, const char *action)
Определения jxct_ui_system.cpp:286
#define UI_ICON_SERVICE
Определения jxct_ui_system.h:49
#define UI_ICON_RESET
Определения jxct_ui_system.h:43
@ SECONDARY
Определения jxct_ui_system.h:66
@ DANGER
Определения jxct_ui_system.h:67
void logDebug(const char *format,...)
Определения logger.cpp:112
void logSuccess(const char *format,...)
Определения logger.cpp:129
Система логгирования с красивым форматированием
String sensorLastError
Определения modbus_sensor.cpp:20
SensorData sensorData
Определения modbus_sensor.cpp:18
const char * getMqttLastError()
Определения mqtt_client.cpp:28
PubSubClient mqttClient
WebServer webServer
WiFiMode currentWiFiMode
Определения wifi_manager.cpp:26
void setupServiceRoutes()
Настройка сервисных маршрутов (/health, /service_status, /reset, /reboot, /ota)
Определения routes_service.cpp:40
static void sendHealthJson()
Определения routes_service.cpp:193
String formatUptime(unsigned long milliseconds)
Определения routes_service.cpp:173
static void sendServiceStatusJson()
Определения routes_service.cpp:275
String navHtml()
Определения wifi_manager.cpp:82
const char * getThingSpeakLastPublish()
Определения thingspeak_client.cpp:40
const char * getThingSpeakLastError()
Определения thingspeak_client.cpp:44
String getApSsid()
Определения wifi_manager.cpp:206
static const char DEVICE_MANUFACTURER[]
Определения version.h:14
static const char DEVICE_MODEL[]
Определения version.h:15
String generateApModeUnavailablePage(const String &title, const String &icon)
Генерация страницы "Недоступно в AP режиме".
Определения web_templates.cpp:175
String generatePageHeader(const String &title, const String &icon)
Генерация заголовка HTML страницы
Определения web_templates.cpp:8
String generatePageFooter()
Генерация футера HTML страницы
Определения web_templates.cpp:19
bool wifiConnected
Определения wifi_manager.cpp:25
WiFiMode
Определения wifi_manager.h:12
@ AP
Определения wifi_manager.h:13
@ STA
Определения wifi_manager.h:14