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
Загрузка...
Поиск...
Не найдено
main.cpp
См. документацию.
1
5
6// *** УНИКАЛЬНЫЙ ИДЕНТИФИКАТОР СБОРКИ v3.1.7-DEBUG-20250620-1501 ***
7// *** ЕСЛИ ВЫ ВИДИТЕ ЭТО СООБЩЕНИЕ, ПРОШИВКА ОБНОВИЛАСЬ ***
8
9#include <WiFiClientSecure.h>
10#include <WiFiClient.h>
11#include <Arduino.h>
12#include "version.h" // ✅ Централизованное управление версией
13#include "wifi_manager.h"
14#include "modbus_sensor.h"
15#include "mqtt_client.h"
16#include "thingspeak_client.h"
17#include "jxct_config_vars.h"
18#include <WiFiUdp.h>
19#include <NTPClient.h>
20#include <esp_task_wdt.h>
21#include "fake_sensor.h"
22#include "debug.h" // ✅ Добавляем систему условной компиляции
23#include "logger.h"
24#include "sensor_factory.h"
25#include "ota_manager.h"
26#include <esp_ota_ops.h>
27
28// Переменные для отслеживания времени
29unsigned long lastDataPublish = 0;
30unsigned long lastNtpUpdate = 0;
31
32// Объявления функций
33bool initPreferences();
34void setupWiFi();
35void setupModbus();
36void loadConfig();
39void handleWiFi();
40void handleMQTT();
41
42WiFiUDP ntpUDP;
43NTPClient* timeClient = nullptr;
44
45// Константы
46const int RESET_BUTTON_PIN = 0; // GPIO0 для кнопки сброса
47const unsigned long STATUS_PRINT_INTERVAL = 30000; // 30 секунд
48
49// Переменные
50unsigned long lastStatusPrint = 0;
51
52// ✅ Неблокирующая задача мониторинга кнопки сброса
53void resetButtonTask(void* parameter)
54{
55 pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);
56 static unsigned long buttonPressTime = 0;
57 static bool buttonPressed = false;
58
59 while (true)
60 {
61 bool currentState = (digitalRead(RESET_BUTTON_PIN) == LOW);
62
63 if (currentState && !buttonPressed)
64 {
65 // Кнопка только что нажата
66 buttonPressed = true;
67 buttonPressTime = millis();
68 logWarn("Кнопка сброса нажата! Сброс настроек через 2 сек...");
69 }
70 else if (!currentState && buttonPressed)
71 {
72 // Кнопка отпущена раньше времени
73 buttonPressed = false;
74 DEBUG_PRINTLN("Кнопка сброса отпущена");
75 }
76 else if (currentState && buttonPressed && (millis() - buttonPressTime >= BUTTON_HOLD_TIME_MS))
77 {
78 // Кнопка удерживалась 2 секунды
79 logError("Выполняется сброс настроек!");
81 ESP.restart();
82 }
83
84 // ✅ Неблокирующая задержка - проверяем кнопку каждые 50мс
85 vTaskDelay(50 / portTICK_PERIOD_MS);
86 }
87}
88
89#ifndef PIO_UNIT_TESTING
90
91void setup()
92{
93 Serial.begin(115200);
94
95 // *** КРИТИЧЕСКОЕ ОТЛАДОЧНОЕ СООБЩЕНИЕ ***
96 Serial.printf("*** УНИКАЛЬНЫЙ ИДЕНТИФИКАТОР СБОРКИ v%s ***\n", JXCT_FULL_VERSION_STRING);
97 Serial.println("*** ЕСЛИ ВЫ ВИДИТЕ ЭТО СООБЩЕНИЕ, ПРОШИВКА ОБНОВИЛАСЬ УСПЕШНО ***");
98
99 // Красивый баннер запуска
100 logPrintBanner("JXCT 7-в-1 Датчик v" JXCT_VERSION_STRING " - Запуск системы");
101
102 /*-----------------------------------------------------------
103 * Подтверждаем OTA-образ ДО тяжёлой инициализации Wi-Fi/FS.
104 * Нужно успеть до истечения тайм-аутa загрузчика (~5 с).
105 *----------------------------------------------------------*/
106 const esp_partition_t* runningNow = esp_ota_get_running_partition();
107 esp_ota_img_states_t otaStateNow;
108 if (esp_ota_get_state_partition(runningNow, &otaStateNow) == ESP_OK && otaStateNow == ESP_OTA_IMG_PENDING_VERIFY)
109 {
110 logSystem("OTA image pending verify → подтверждаем (ранний этап)");
111 if (esp_ota_mark_app_valid_cancel_rollback() == ESP_OK)
112 logSuccess("OTA image подтверждена, откат отменён");
113 else
114 logError("Не удалось подтвердить OTA image (ранний этап)!");
115 }
116
117 logPrintHeader("ИНИЦИАЛИЗАЦИЯ СИСТЕМЫ", COLOR_CYAN);
118
119 // Настройка Watchdog Timer
120 logSystem("Настройка Watchdog Timer (30 сек)...");
121 esp_task_wdt_init(WATCHDOG_TIMEOUT_SEC, true);
122 esp_task_wdt_add(NULL);
123 logSuccess("Watchdog Timer активирован");
124
125 // Инициализация Preferences
126 if (!initPreferences())
127 {
128 logError("Ошибка инициализации Preferences!");
129 return;
130 }
131 logSuccess("Preferences инициализирован");
132
133 // Загрузка конфигурации
134 loadConfig();
135 logSuccess("Конфигурация загружена");
136
137 // Информация о режиме работы
138 logSystem("Режим датчика: %s", config.flags.useRealSensor ? "РЕАЛЬНЫЙ" : "ЭМУЛЯЦИЯ");
139 logSystem("Интервал чтения: %d мс", config.sensorReadInterval);
140
141 // Инициализация WiFi
142 setupWiFi();
143
144 // Инициализация ThingSpeak
145 if (config.flags.thingSpeakEnabled)
146 {
147 extern WiFiClient espClient; // объявлен в mqtt_client.cpp
149 logSuccess("ThingSpeak инициализирован");
150 }
151
152 // Инициализация MQTT
153 if (config.flags.mqttEnabled)
154 {
155 setupMQTT();
156 logSuccess("MQTT инициализирован");
157 }
158
159 // Инициализация OTA 2.0 (проверка манифеста раз в час) через HTTPS
160 static WiFiClientSecure otaClient;
161 otaClient.setInsecure(); // временно отключаем проверку сертификата
162 // Всегда устанавливаем URL манифеста для ручной и автоматической проверки
163 // ИСПРАВЛЕНО - используем правильный URL манифеста
164 // ВОЗВРАЩАЮ КАК РАБОТАЛО - /latest/download/
165 setupOTA("https://github.com/Gfermoto/soil-sensor-7in1/releases/latest/download/manifest.json", otaClient);
166
167 // Создаём экземпляр абстрактного сенсора
168 static std::unique_ptr<ISensor> gSensor = createSensorInstance();
169 gSensor->begin();
170
171 // Legacy: оставляем старые задачи для поточного обновления sensorData
172 if (config.flags.useRealSensor)
174 else
176
177 // Запуск задачи мониторинга кнопки сброса
178 xTaskCreate(resetButtonTask, "ResetButton", 2048, NULL, 1, NULL);
179
180 // Если мы загружаемся после OTA и система ждёт подтверждения, отменяем откат после успешного старта
181 const esp_partition_t* running = esp_ota_get_running_partition();
182 esp_ota_img_states_t ota_state;
183 if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK && ota_state == ESP_OTA_IMG_PENDING_VERIFY)
184 {
185 logSystem("OTA image pending verify → помечаем как valid");
186 if (esp_ota_mark_app_valid_cancel_rollback() == ESP_OK)
187 logSuccess("OTA image подтверждена, откат отменён");
188 else
189 logError("Не удалось подтвердить OTA image!");
190 }
191
192 logSuccess("Инициализация завершена успешно!");
193 logPrintSeparator("─", 60);
194}
195
197{
198 return preferences.begin("jxct", false);
199}
200
201// ✅ Неблокирующий главный цикл с оптимизированными интервалами
202void loop()
203{
204 // Текущее время
205 unsigned long currentTime = millis();
206
207 // Сброс watchdog
208 esp_task_wdt_reset();
209
210 // Обновление NTP каждые 6 часов
211 if (timeClient && millis() - lastNtpUpdate > 6 * 3600 * 1000)
212 {
213 timeClient->forceUpdate();
214 lastNtpUpdate = millis();
215 logSystem("NTP обновление: %s", timeClient->isTimeSet() ? "OK" : "не удалось");
216 }
217
218 // ✅ Вывод статуса системы каждые 30 секунд (неблокирующий)
219 if (currentTime - lastStatusPrint >= STATUS_PRINT_INTERVAL)
220 {
221 logPrintHeader("СТАТУС СИСТЕМЫ", COLOR_GREEN);
222
223 logUptime();
226 logSystem("Режим датчика: %s", config.flags.useRealSensor ? "РЕАЛЬНЫЙ" : "ЭМУЛЯЦИЯ");
227
228 // Статус данных датчика
229 if (sensorData.valid)
230 {
231 logData("Последние измерения получены %.1f сек назад", (currentTime - sensorData.last_update) / 1000.0);
232 }
233 else
234 {
235 logWarn("Данные датчика недоступны");
236 }
237
238 logPrintSeparator("─", 60);
239 lastStatusPrint = currentTime;
240 }
241
242 // ✅ ОПТИМИЗАЦИЯ 3.2: Интеллектуальный батчинг данных для группировки сетевых отправок
243 static unsigned long mqttBatchTimer = 0;
244 static unsigned long thingspeakBatchTimer = 0;
245 static bool pendingMqttPublish = false;
246 static bool pendingThingspeakPublish = false;
247
248 // Проверяем наличие новых данных датчика (НАСТРАИВАЕМО v2.3.0)
249 if (sensorData.valid && (currentTime - lastDataPublish >= config.sensorReadInterval))
250 {
251 // Помечаем что есть данные для отправки (не отправляем сразу)
252 pendingMqttPublish = true;
253 pendingThingspeakPublish = true;
254 lastDataPublish = currentTime;
255
256 DEBUG_PRINTLN("[BATCH] Новые данные помечены для групповой отправки");
257 }
258
259 // ✅ Групповая отправка MQTT (настраиваемо v2.3.0)
260 if (pendingMqttPublish && (currentTime - mqttBatchTimer >= config.mqttPublishInterval))
261 {
263 pendingMqttPublish = false;
264 mqttBatchTimer = currentTime;
265 DEBUG_PRINTLN("[BATCH] MQTT данные отправлены группой");
266 }
267
268 // ✅ Групповая отправка ThingSpeak (настраиваемо v2.3.0)
269 if (pendingThingspeakPublish && (currentTime - thingspeakBatchTimer >= config.thingSpeakInterval))
270 {
271 bool tsOk = sendDataToThingSpeak();
272 pendingThingspeakPublish = false;
273 if (tsOk)
274 {
275 thingspeakBatchTimer = currentTime; // Сбрасываем таймер только при успешной отправке
276 DEBUG_PRINTLN("[BATCH] ThingSpeak данные отправлены группой");
277 }
278 else
279 {
280 DEBUG_PRINTLN("[BATCH] ThingSpeak отправка не удалась, повтор через следующий интервал");
281 }
282 }
283
284 // ✅ Управление MQTT (каждые 100мс)
285 static unsigned long lastMqttCheck = 0;
286 if (currentTime - lastMqttCheck >= 100)
287 {
288 handleMQTT();
289 lastMqttCheck = currentTime;
290 }
291
292 // ✅ Управление WiFi (каждые 20 мс для более высокой отзывчивости веб-интерфейса)
293 static unsigned long lastWiFiCheck = 0;
294 if (currentTime - lastWiFiCheck >= 20)
295 {
296 handleWiFi();
297 lastWiFiCheck = currentTime;
298 }
299
300 // Проверяем OTA раз в час (или при принудительной проверке)
301 static unsigned long lastOtaCheck = 0;
302 if (config.flags.autoOtaEnabled && (currentTime - lastOtaCheck >= 3600000UL))
303 {
304 handleOTA();
305 lastOtaCheck = currentTime;
306 }
307
308 // ✅ Минимальная задержка для стабильности (10мс вместо 100мс)
309 vTaskDelay(10 / portTICK_PERIOD_MS);
310}
311
312#endif // PIO_UNIT_TESTING
Config config
Определения config.cpp:34
Preferences preferences
Определения config.cpp:35
void resetConfig()
Определения config.cpp:212
#define DEBUG_PRINTLN(x)
Определения debug.h:18
#define BUTTON_HOLD_TIME_MS
Определения jxct_config_vars.h:21
#define WATCHDOG_TIMEOUT_SEC
Определения jxct_config_vars.h:23
void logUptime()
Определения logger.cpp:231
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 logMemoryUsage()
Определения logger.cpp:236
void logWiFiStatus()
Определения logger.cpp:241
void logPrintBanner(const char *text)
Определения logger.cpp:48
void logData(const char *format,...)
Определения logger.cpp:196
Система логгирования с красивым форматированием
#define COLOR_CYAN
Определения logger.h:42
#define COLOR_GREEN
Определения logger.h:38
void setupModbus()
Инициализация Modbus и SP3485E.
Определения modbus_sensor.cpp:80
WiFiUDP ntpUDP
Определения main.cpp:42
unsigned long lastStatusPrint
Определения main.cpp:50
unsigned long lastDataPublish
Определения main.cpp:29
void startRealSensorTask()
Определения modbus_sensor.cpp:541
void setup()
Определения main.cpp:91
void resetButtonTask(void *parameter)
Определения main.cpp:53
void handleMQTT()
Определения mqtt_client.cpp:311
const unsigned long STATUS_PRINT_INTERVAL
Определения main.cpp:47
NTPClient * timeClient
Определения main.cpp:43
bool initPreferences()
Определения main.cpp:196
void startFakeSensorTask()
Определения fake_sensor.cpp:98
unsigned long lastNtpUpdate
Определения main.cpp:30
void setupWiFi()
Определения wifi_manager.cpp:99
void loadConfig()
Определения config.cpp:39
void handleWiFi()
Определения wifi_manager.cpp:129
void loop()
Определения main.cpp:202
SensorData sensorData
Определения modbus_sensor.cpp:18
void setupMQTT()
Определения mqtt_client.cpp:149
void publishSensorData()
Определения mqtt_client.cpp:440
WiFiClient espClient
Определения mqtt_client.cpp:22
void handleOTA()
Определения ota_manager.cpp:455
void setupOTA(const char *manifestUrl, WiFiClient &client)
Определения ota_manager.cpp:47
static std::unique_ptr< ISensor > createSensorInstance()
Определения sensor_factory.h:8
bool sendDataToThingSpeak()
Определения thingspeak_client.cpp:54
void setupThingSpeak(WiFiClient &client)
Определения thingspeak_client.cpp:49
#define JXCT_FULL_VERSION_STRING
Определения version.h:28
#define JXCT_VERSION_STRING
Определения version.h:12
#define RESET_BUTTON_PIN
Определения wifi_manager.cpp:21