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
Загрузка...
Поиск...
Не найдено
modbus_sensor.cpp
См. документацию.
1
7#include <Arduino.h>
8#include "modbus_sensor.h"
9#include "jxct_device_info.h"
10#include "jxct_config_vars.h"
11#include "debug.h" // ✅ Добавляем систему условной компиляции
12#include "logger.h"
13#include "jxct_constants.h" // ✅ Централизованные константы
14#include "sensor_compensation.h"
15#include "calibration_manager.h"
16
17ModbusMaster modbus;
20String sensorLastError = "";
21
22static unsigned long lastIrrigationTs = 0;
23
24void debugPrintBuffer(const char* prefix, uint8_t* buffer, size_t length)
25{
26 if (currentLogLevel < LOG_DEBUG) return;
27
28 String hex_str = "";
29 for (size_t i = 0; i < length; i++)
30 {
31 if (buffer[i] < 0x10) hex_str += "0";
32 hex_str += String(buffer[i], HEX);
33 hex_str += " ";
34 }
35 logDebug("%s%s", prefix, hex_str.c_str());
36}
37
46{
47 logSystem("=== ТЕСТИРОВАНИЕ SP3485E ===");
48
49 // Проверяем пины
50 pinMode(MODBUS_DE_PIN, OUTPUT); // Driver Enable - управление передатчиком
51 pinMode(MODBUS_RE_PIN, OUTPUT); // Receiver Enable - управление приемником
52
53 // Тест 1: Включаем передачу, выключаем прием
54 digitalWrite(MODBUS_DE_PIN, HIGH); // Активируем передатчик
55 digitalWrite(MODBUS_RE_PIN, HIGH); // Отключаем приемник
56 delay(10);
57
58 // Тест 2: Включаем прием, выключаем передачу
59 digitalWrite(MODBUS_DE_PIN, LOW); // Деактивируем передатчик
60 digitalWrite(MODBUS_RE_PIN, LOW); // Активируем приемник
61
62 // Проверяем состояние
63 if (digitalRead(MODBUS_DE_PIN) == LOW && digitalRead(MODBUS_RE_PIN) == LOW)
64 {
65 logSuccess("SP3485E DE/RE пины работают корректно");
66 }
67 else
68 {
69 logWarn("Нет ответа от SP3485E (это нормально без датчика)");
70 }
71
72 logSystem("=== ТЕСТ SP3485E ЗАВЕРШЕН ===");
73}
74
81{
82 logPrintHeader("ИНИЦИАЛИЗАЦИЯ MODBUS", COLOR_CYAN);
83
84 // Устанавливаем пины управления SP3485E
85 logSystem("Настройка пинов SP3485E...");
86 pinMode(MODBUS_DE_PIN, OUTPUT); // Driver Enable - GPIO4
87 pinMode(MODBUS_RE_PIN, OUTPUT); // Receiver Enable - GPIO5
88
89 // Начальное состояние: прием включен, передача выключена
90 digitalWrite(MODBUS_DE_PIN, LOW); // Передатчик в высокоимпедансном состоянии
91 digitalWrite(MODBUS_RE_PIN, LOW); // Приемник активен
92
93 logSystem("DE пин: %d, RE пин: %d", MODBUS_DE_PIN, MODBUS_RE_PIN);
94 logSuccess("Пины SP3485E настроены");
95
96 // Инициализация UART для Modbus
97 Serial2.begin(9600, SERIAL_8N1, MODBUS_RX_PIN, MODBUS_TX_PIN);
98
99 // Настройка Modbus с обработчиками переключения режима
100 modbus.begin(JXCT_MODBUS_ID, Serial2);
101 modbus.preTransmission(preTransmission); // Вызывается перед передачей
102 modbus.postTransmission(postTransmission); // Вызывается после передачи
103
104 logSuccess("Modbus инициализирован");
105 logPrintHeader("MODBUS ГОТОВ ДЛЯ ПОЛНОГО ТЕСТИРОВАНИЯ", COLOR_GREEN);
106}
107
108// Функция для расчета CRC16 Modbus
109uint16_t calculateCRC16(uint8_t* data, size_t length)
110{
111 uint16_t crc = 0xFFFF;
112
113 for (size_t i = 0; i < length; i++)
114 {
115 crc ^= (uint16_t)data[i];
116 for (int j = 0; j < 8; j++)
117 {
118 if (crc & 0x0001)
119 {
120 crc = (crc >> 1) ^ 0xA001;
121 }
122 else
123 {
124 crc = crc >> 1;
125 }
126 }
127 }
128
129 return crc;
130}
131
133{
134 if (data.temperature < MIN_TEMPERATURE || data.temperature > MAX_TEMPERATURE) return false;
135 if (data.humidity < MIN_HUMIDITY || data.humidity > MAX_HUMIDITY) return false;
136 if (data.ec < MIN_EC || data.ec > MAX_EC) return false;
137 if (data.ph < MIN_PH || data.ph > MAX_PH) return false;
138 if (data.nitrogen < MIN_NPK || data.nitrogen > MAX_NPK) return false;
139 if (data.phosphorus < MIN_NPK || data.phosphorus > MAX_NPK) return false;
140 if (data.potassium < MIN_NPK || data.potassium > MAX_NPK) return false;
141 return true;
142}
143
145{
146 if (!sensorCache.is_valid) return false;
147 if (millis() - sensorCache.timestamp > MODBUS_CACHE_TIMEOUT) return false;
148
149 data = sensorCache.data;
150 return true;
151}
152
154{
155 logSensor("Запрос версии прошивки датчика...");
156 uint8_t result = modbus.readHoldingRegisters(0x07, 1);
157
158 if (result == modbus.ku8MBSuccess)
159 {
160 uint16_t version = modbus.getResponseBuffer(0);
161 logSuccess("Версия прошивки датчика: %d.%d", (version >> 8) & 0xFF, version & 0xFF);
162 return true;
163 }
164 else
165 {
166 logError("Ошибка чтения версии прошивки: %d", result);
167 printModbusError(result);
168 return false;
169 }
170}
171
173{
174 uint8_t result = modbus.readHoldingRegisters(REG_ERROR_STATUS, 1);
175 if (result == modbus.ku8MBSuccess)
176 {
177 sensorData.error_status = modbus.getResponseBuffer(0);
178 return true;
179 }
180 return false;
181}
182
183bool changeDeviceAddress(uint8_t new_address)
184{
185 if (new_address < 1 || new_address > 247) return false;
186
187 uint8_t result = modbus.writeSingleRegister(REG_DEVICE_ADDRESS, new_address);
188 if (result == modbus.ku8MBSuccess)
189 {
190 // ✅ Неблокирующая задержка через vTaskDelay
191 delay(100); // ОТКАТ: Критичный timing для Modbus
192 modbus.begin(new_address, Serial2);
193 return true;
194 }
195 return false;
196}
197
198// Добавляем функцию диагностики Modbus связи
200{
201 logSystem("=== ТЕСТ MODBUS СОЕДИНЕНИЯ ===");
202
203 // Проверяем пины
204 logSystem("DE пин: %d, RE пин: %d", MODBUS_DE_PIN, MODBUS_RE_PIN);
205
206 // Тест 1: Проверка конфигурации пинов
207 logSystem("Тест 1: Проверка конфигурации пинов...");
208 pinMode(MODBUS_DE_PIN, OUTPUT);
209 pinMode(MODBUS_RE_PIN, OUTPUT);
210 if (digitalRead(MODBUS_DE_PIN) == LOW && digitalRead(MODBUS_RE_PIN) == LOW)
211{
212 logSuccess("Пины в правильном начальном состоянии (прием)");
213 }
214 else
215 {
216 logError("Неверное начальное состояние пинов");
217 return false;
218 }
219
220 // Тест 2: Проверка временных задержек
221 logSystem("Тест 2: Проверка временных задержек...");
222 unsigned long start_time = micros();
224 unsigned long pre_delay = micros() - start_time;
225
226 start_time = micros();
228 unsigned long post_delay = micros() - start_time;
229
230 logSystem("Задержка preTransmission: %lu мкс", pre_delay);
231 logSystem("Задержка postTransmission: %lu мкс", post_delay);
232
233 if (pre_delay >= 50 && post_delay >= 50)
234 {
235 logSuccess("Временные задержки в норме");
236 }
237 else
238 {
239 logWarn("Временные задержки меньше рекомендованных (50 мкс)");
240 }
241
242 // Тест 3: Проверка конфигурации UART
243 logSystem("Тест 3: Проверка конфигурации UART...");
244 if (Serial2.baudRate() == 9600)
245 {
246 logSuccess("Скорость UART настроена правильно: 9600");
247 }
248 else
249 {
250 logError("Неверная скорость UART: %d", Serial2.baudRate());
251 return false;
252 }
253
254 // Тест 4: Попытка чтения регистра версии прошивки
255 logSystem("Тест 4: Чтение версии прошивки...");
256 uint8_t result = modbus.readHoldingRegisters(0x00, 1);
257 if (result == modbus.ku8MBSuccess)
258 {
259 logSuccess("Успешно прочитан регистр версии");
260 }
261 else
262 {
263 logError("Ошибка чтения регистра версии: %02X", result);
264 return false;
265 }
266
267 logSuccess("=== ТЕСТ MODBUS ЗАВЕРШЕН УСПЕШНО ===");
268 return true;
269 }
270
271// ============================================================================
272// ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ СНИЖЕНИЯ ЦИКЛОМАТИЧЕСКОЙ СЛОЖНОСТИ
273// ============================================================================
274
284bool readSingleRegister(uint16_t reg_addr, const char* reg_name, float multiplier, void* target, bool is_float)
285{
286 logDebug("Чтение %s (0x%04X)...", reg_name, reg_addr);
287 uint8_t result = modbus.readHoldingRegisters(reg_addr, 1);
288
289 if (result == modbus.ku8MBSuccess)
290 {
291 uint16_t raw_value = modbus.getResponseBuffer(0);
292
293 if (is_float)
294 {
295 float* float_target = static_cast<float*>(target);
296 *float_target = convertRegisterToFloat(raw_value, multiplier);
297 logDebug("%s: %.2f", reg_name, *float_target);
298 }
299 else
300 {
301 uint16_t* int_target = static_cast<uint16_t*>(target);
302 *int_target = raw_value;
303 logDebug("%s: %d", reg_name, *int_target);
304 }
305 return true;
306 }
307 else
308 {
309 logError("Ошибка чтения %s: %d", reg_name, result);
310 printModbusError(result);
311 return false;
312 }
313}
314
320{
321 int success_count = 0;
322
323 // pH (÷ 100)
324 if (readSingleRegister(REG_PH, "pH", 0.01f, &sensorData.ph, true)) success_count++;
325
326 // Влажность (÷ 10)
327 if (readSingleRegister(REG_SOIL_MOISTURE, "Влажность", 0.1f, &sensorData.humidity, true)) success_count++;
328
329 // Температура (÷ 10)
330 if (readSingleRegister(REG_SOIL_TEMP, "Температура", 0.1f, &sensorData.temperature, true)) success_count++;
331
332 // EC (без деления)
333 if (readSingleRegister(REG_CONDUCTIVITY, "EC", 1.0f, &sensorData.ec, true)) success_count++;
334
335 return success_count;
336}
337
343{
344 int success_count = 0;
345
346 // Азот
347 if (readSingleRegister(REG_NITROGEN, "Азот", 1.0f, &sensorData.nitrogen, true)) success_count++;
348
349 // Фосфор
350 if (readSingleRegister(REG_PHOSPHORUS, "Фосфор", 1.0f, &sensorData.phosphorus, true)) success_count++;
351
352 // Калий
353 if (readSingleRegister(REG_POTASSIUM, "Калий", 1.0f, &sensorData.potassium, true)) success_count++;
354
355 return success_count;
356 }
357
358// ------------------------------------------------------------
359// 🔽 Helper functions to reduce cyclomatic complexity
360// ------------------------------------------------------------
361
363{
366 d.raw_ec = d.ec;
367 d.raw_ph = d.ph;
371}
372
374{
375 constexpr uint8_t WIN = 6;
376 static float buf[WIN] = {NAN};
377 static uint8_t idx = 0, filled = 0, persist = 0;
378
379 float baseline = d.humidity;
380 for (uint8_t i = 0; i < filled; ++i)
381 baseline = (buf[i] < baseline) ? buf[i] : baseline;
382
383 bool spike = (filled == WIN) &&
384 (d.humidity - baseline >= config.irrigationSpikeThreshold) &&
385 (d.humidity > 25.0f);
386 persist = spike ? persist + 1 : 0;
387 if (persist >= 2) {
388 lastIrrigationTs = millis();
389 persist = 0;
390 }
391
392 buf[idx] = d.humidity;
393 idx = (idx + 1) % WIN;
394 if (filled < WIN) ++filled;
395
396 d.recentIrrigation = (millis() - lastIrrigationTs) <= (unsigned long)config.irrigationHoldMinutes * 60000UL;
397}
398
400{
401 if (!config.flags.calibrationEnabled) return;
402
405 switch (config.soilProfile) {
406 case 0: soil = SoilType::SAND; profile = SoilProfile::SAND; break;
407 case 1: soil = SoilType::LOAM; profile = SoilProfile::LOAM; break;
408 case 2: soil = SoilType::PEAT; profile = SoilProfile::PEAT; break;
409 case 3: soil = SoilType::CLAY; profile = SoilProfile::CLAY; break;
410 case 4: soil = SoilType::SANDPEAT; profile = SoilProfile::SANDPEAT; break;
411 }
412
413 // Шаг 1: Применяем калибровочную таблицу CSV (лабораторная поверка)
414 float tempCalibrated = CalibrationManager::applyCalibration(d.temperature, profile);
415 float humCalibrated = CalibrationManager::applyCalibration(d.humidity, profile);
416 float ecCalibrated = CalibrationManager::applyCalibration(d.ec, profile);
417 float phCalibrated = CalibrationManager::applyCalibration(d.ph, profile);
418 float nCalibrated = CalibrationManager::applyCalibration(d.nitrogen, profile);
419 float pCalibrated = CalibrationManager::applyCalibration(d.phosphorus, profile);
420 float kCalibrated = CalibrationManager::applyCalibration(d.potassium, profile);
421
422 // Шаг 2: Применяем математическую компенсацию (температурная, влажностная)
423 float ec25 = ecCalibrated / (1.0f + 0.021f * (tempCalibrated - 25.0f));
424 d.ec = correctEC(ec25, tempCalibrated, humCalibrated, soil);
425
426 d.ph = correctPH(phCalibrated, tempCalibrated);
427
428 d.nitrogen = nCalibrated;
429 d.phosphorus = pCalibrated;
430 d.potassium = kCalibrated;
431 correctNPK(tempCalibrated, humCalibrated, d.nitrogen, d.phosphorus, d.potassium, soil);
432
433 // Обновляем остальные значения
434 d.temperature = tempCalibrated;
435 d.humidity = humCalibrated;
436}
437
442void finalizeSensorData(bool success)
443{
444 sensorData.valid = success;
445 sensorData.last_update = millis();
446
447 if (!success) {
448 logError("❌ Не удалось прочитать один или несколько параметров");
449 return;
450 }
451
455
457 sensorData.temperature,
458 sensorData.humidity,
459 sensorData.ec,
460 sensorData.ph,
461 sensorData.nitrogen,
462 sensorData.phosphorus,
463 sensorData.potassium);
464
466 logSuccess("✅ Все параметры прочитаны и валидны");
467 sensorCache = {sensorData, true, millis()};
468 } else {
469 logWarn("⚠️ Данные прочитаны, но не прошли валидацию");
470 sensorData.valid = false;
471 }
472}
473
474// ============================================================================
475// ОСНОВНАЯ ФУНКЦИЯ ЧТЕНИЯ ДАТЧИКА (РЕФАКТОРИНГ)
476// ============================================================================
477
479{
480 logSensor("Чтение всех параметров JXCT 7-в-1 датчика...");
481
482 // Читаем основные параметры (4 параметра)
483 int basic_success = readBasicParameters();
484
485 // Читаем NPK параметры (3 параметра)
486 int npk_success = readNPKParameters();
487
488 // Общий успех - все 7 параметров прочитаны
489 bool total_success = (basic_success == 4) && (npk_success == 3);
490
491 // Финализируем данные
492 finalizeSensorData(total_success);
493}
494
495float convertRegisterToFloat(uint16_t value, float multiplier)
496{
497 return value * multiplier;
498}
499
506{
507 digitalWrite(MODBUS_DE_PIN, HIGH); // Активируем передатчик
508 digitalWrite(MODBUS_RE_PIN, HIGH); // Отключаем приемник для предотвращения эха
509 delayMicroseconds(50); // Ждем стабилизации сигналов
510}
511
518{
519 delayMicroseconds(50); // Ждем завершения передачи последнего байта
520 digitalWrite(MODBUS_DE_PIN, LOW); // Отключаем передатчик
521 digitalWrite(MODBUS_RE_PIN, LOW); // Включаем приемник
522}
523
524// ✅ Неблокирующая задача реального датчика с ДИАГНОСТИКОЙ
525void realSensorTask(void* pvParameters)
526{
527 logPrintHeader("ПРОСТОЕ ЧТЕНИЕ ДАТЧИКА JXCT", COLOR_CYAN);
528 logSystem("🔥 Использую РАБОЧИЕ параметры: 9600 bps, 8N1, адрес 1");
529 logSystem("📊 Функция: периодическое чтение всех регистров датчика");
530
531 for (;;)
532 {
533 // Простое чтение всех параметров датчика с рабочими настройками
535
536 // Пауза между чтениями (настраиваемая в config в миллисекундах)
537 vTaskDelay(pdMS_TO_TICKS(config.sensorReadInterval));
538 }
539}
540
542{
543 xTaskCreate(realSensorTask, "RealSensor", 4096, NULL, 1, NULL);
544}
545
546// Функция для вывода ошибок Modbus
547void printModbusError(uint8_t errNum)
548{
549 switch (errNum)
550 {
551 case ModbusMaster::ku8MBSuccess:
552 logSuccess("Modbus операция успешна");
553 break;
554 case ModbusMaster::ku8MBIllegalFunction:
555 logError("Modbus: Illegal Function Exception");
556 break;
557 case ModbusMaster::ku8MBIllegalDataAddress:
558 logError("Modbus: Illegal Data Address Exception");
559 break;
560 case ModbusMaster::ku8MBIllegalDataValue:
561 logError("Modbus: Illegal Data Value Exception");
562 break;
563 case ModbusMaster::ku8MBSlaveDeviceFailure:
564 logError("Modbus: Slave Device Failure");
565 break;
566 case ModbusMaster::ku8MBInvalidSlaveID:
567 logError("Modbus: Invalid Slave ID");
568 break;
569 case ModbusMaster::ku8MBInvalidFunction:
570 logError("Modbus: Invalid Function");
571 break;
572 case ModbusMaster::ku8MBResponseTimedOut:
573 logError("Modbus: Response Timed Out");
574 break;
575 case ModbusMaster::ku8MBInvalidCRC:
576 logError("Modbus: Invalid CRC");
577 break;
578 default:
579 logError("Modbus: Неизвестная ошибка %d", errNum);
580 break;
581 }
582}
583
584// ========================================
585// v2.3.0: РЕАЛИЗАЦИЯ СКОЛЬЗЯЩЕГО СРЕДНЕГО
586// ========================================
587
589{
590 // Инициализируем буферы нулями
591 for (int i = 0; i < 15; i++)
592 {
593 data.temp_buffer[i] = 0.0;
594 data.hum_buffer[i] = 0.0;
595 data.ec_buffer[i] = 0.0;
596 data.ph_buffer[i] = 0.0;
597 data.n_buffer[i] = 0.0;
598 data.p_buffer[i] = 0.0;
599 data.k_buffer[i] = 0.0;
600 }
601 data.buffer_index = 0;
602 data.buffer_filled = 0;
603 DEBUG_PRINTLN("[MOVING_AVG] Буферы скользящего среднего инициализированы");
604}
605
606void addToMovingAverage(SensorData& data, float temp, float hum, float ec, float ph, float n, float p, float k)
607{
608 uint8_t window_size = config.movingAverageWindow;
609 if (window_size < 5) window_size = 5;
610 if (window_size > 15) window_size = 15;
611
612 // ---------- O(1) running sum для каждого параметра ----------
613 static float sum_temp = 0, sum_hum = 0, sum_ec = 0, sum_ph = 0, sum_n = 0, sum_p = 0, sum_k = 0;
614
615 // Если буфер заполнен – вычитаем значение, которое покинет окно
616 if (data.buffer_filled >= window_size) {
617 sum_temp -= data.temp_buffer[data.buffer_index];
618 sum_hum -= data.hum_buffer[data.buffer_index];
619 sum_ec -= data.ec_buffer[data.buffer_index];
620 sum_ph -= data.ph_buffer[data.buffer_index];
621 sum_n -= data.n_buffer[data.buffer_index];
622 sum_p -= data.p_buffer[data.buffer_index];
623 sum_k -= data.k_buffer[data.buffer_index];
624 }
625
626 // Записываем новые значения в буфер и добавляем их к сумме
627 data.temp_buffer[data.buffer_index] = temp; sum_temp += temp;
628 data.hum_buffer[data.buffer_index] = hum; sum_hum += hum;
629 data.ec_buffer[data.buffer_index] = ec; sum_ec += ec;
630 data.ph_buffer[data.buffer_index] = ph; sum_ph += ph;
631 data.n_buffer[data.buffer_index] = n; sum_n += n;
632 data.p_buffer[data.buffer_index] = p; sum_p += p;
633 data.k_buffer[data.buffer_index] = k; sum_k += k;
634
635 // Обновляем индексы кольцевого буфера
636 data.buffer_index = (data.buffer_index + 1) % window_size;
637 if (data.buffer_filled < window_size) data.buffer_filled++;
638
639 uint8_t effective_window = (data.buffer_filled < window_size) ? data.buffer_filled : window_size;
640
641 if (effective_window >= 3 && config.filterAlgorithm == 0) {
642 // Среднее (O(1))
643 data.temperature = sum_temp / effective_window;
644 data.humidity = sum_hum / effective_window;
645 data.ec = sum_ec / effective_window;
646 data.ph = sum_ph / effective_window;
647 data.nitrogen = sum_n / effective_window;
648 data.phosphorus = sum_p / effective_window;
649 data.potassium = sum_k / effective_window;
650
651 DEBUG_PRINTF("[AVG O1] win=%d temp=%.1f\n", effective_window, data.temperature);
652 }
653 else if (effective_window >= 3) {
654 // Оставляем старый алгоритм для медианы и др.
655 data.temperature = calculateMovingAverage(data.temp_buffer, window_size, data.buffer_filled);
656 data.humidity = calculateMovingAverage(data.hum_buffer, window_size, data.buffer_filled);
657 data.ec = calculateMovingAverage(data.ec_buffer, window_size, data.buffer_filled);
658 data.ph = calculateMovingAverage(data.ph_buffer, window_size, data.buffer_filled);
659 data.nitrogen = calculateMovingAverage(data.n_buffer, window_size, data.buffer_filled);
660 data.phosphorus = calculateMovingAverage(data.p_buffer, window_size, data.buffer_filled);
661 data.potassium = calculateMovingAverage(data.k_buffer, window_size, data.buffer_filled);
662 } else {
663 // Пока мало данных – возвращаем последние значения
664 data.temperature = temp;
665 data.humidity = hum;
666 data.ec = ec;
667 data.ph = ph;
668 data.nitrogen = n;
669 data.phosphorus = p;
670 data.potassium = k;
671 }
672}
673
674float calculateMovingAverage(float* buffer, uint8_t window_size, uint8_t filled)
675{
676 if (filled == 0) return 0.0;
677
678 // Берем последние filled элементов (или window_size, если filled >= window_size)
679 uint8_t elements_to_use = (filled < window_size) ? filled : window_size;
680
681 // v2.4.1: Используем настраиваемый алгоритм (среднее или медиана)
682 extern Config config;
683
684 if (config.filterAlgorithm == 1)
685 { // FILTER_ALGORITHM_MEDIAN
686 // Создаем временный массив для медианы
687 float temp_values[15]; // Максимальный размер окна
688 for (uint8_t i = 0; i < elements_to_use; i++)
689 {
690 temp_values[i] = buffer[i];
691 }
692
693 // Простая сортировка для медианы
694 for (uint8_t i = 0; i < elements_to_use - 1; i++)
695 {
696 for (uint8_t j = 0; j < elements_to_use - i - 1; j++)
697 {
698 if (temp_values[j] > temp_values[j + 1])
699 {
700 float temp = temp_values[j];
701 temp_values[j] = temp_values[j + 1];
702 temp_values[j + 1] = temp;
703 }
704 }
705 }
706
707 // Возвращаем медиану
708 if (elements_to_use % 2 == 0)
709 {
710 return (temp_values[elements_to_use / 2 - 1] + temp_values[elements_to_use / 2]) / 2.0f;
711 }
712 else
713 {
714 return temp_values[elements_to_use / 2];
715 }
716 }
717 else
718 {
719 // FILTER_ALGORITHM_MEAN (по умолчанию)
720 float sum = 0.0;
721 for (uint8_t i = 0; i < elements_to_use; i++)
722 {
723 sum += buffer[i];
724 }
725 return sum / elements_to_use;
726 }
727}
728
729// Функция для получения текущих данных датчика
731{
732 // Возвращаем копию текущих данных датчика
733 SensorData result = sensorData;
734
735 // Обновляем поле isValid для совместимости с веб-интерфейсом
736 result.isValid = result.valid;
737 result.timestamp = result.last_update;
738
739 // Копируем значения в поля с правильными именами для веб-интерфейса
740 result.conductivity = result.ec;
741 result.moisture = result.humidity;
742
743 return result;
744}
Config config
Определения config.cpp:34
#define DEBUG_PRINTLN(x)
Определения debug.h:18
#define DEBUG_PRINTF(fmt,...)
Определения debug.h:19
#define JXCT_MODBUS_ID
Определения jxct_config_vars.h:35
Централизованные константы системы JXCT.
constexpr int MODBUS_RX_PIN
Определения jxct_constants.h:147
constexpr unsigned long MODBUS_CACHE_TIMEOUT
Определения jxct_constants.h:20
constexpr int MODBUS_TX_PIN
Определения jxct_constants.h:148
constexpr int MODBUS_DE_PIN
Определения jxct_constants.h:149
constexpr int MODBUS_RE_PIN
Определения jxct_constants.h:150
LogLevel currentLogLevel
Определения logger.cpp:11
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 logSensor(const char *format,...)
Определения logger.cpp:145
void logSuccess(const char *format,...)
Определения logger.cpp:129
void logError(const char *format,...)
Определения logger.cpp:61
void logSystem(const char *format,...)
Определения logger.cpp:213
Система логгирования с красивым форматированием
#define COLOR_CYAN
Определения logger.h:42
@ LOG_DEBUG
Определения logger.h:17
#define COLOR_GREEN
Определения logger.h:38
void setupModbus()
Инициализация Modbus и SP3485E.
Определения modbus_sensor.cpp:80
uint16_t calculateCRC16(uint8_t *data, size_t length)
Определения modbus_sensor.cpp:109
void preTransmission()
Подготовка к передаче данных
Определения modbus_sensor.cpp:505
void debugPrintBuffer(const char *prefix, uint8_t *buffer, size_t length)
Определения modbus_sensor.cpp:24
void postTransmission()
Завершение передачи данных
Определения modbus_sensor.cpp:517
bool readSingleRegister(uint16_t reg_addr, const char *reg_name, float multiplier, void *target, bool is_float)
Чтение одного регистра с обработкой ошибок
Определения modbus_sensor.cpp:284
SensorData getSensorData()
Определения modbus_sensor.cpp:730
static void applyCompensationIfEnabled(SensorData &d)
Определения modbus_sensor.cpp:399
void startRealSensorTask()
Определения modbus_sensor.cpp:541
void realSensorTask(void *pvParameters)
Определения modbus_sensor.cpp:525
SensorCache sensorCache
Определения modbus_sensor.cpp:19
int readNPKParameters()
Чтение NPK параметров (азот, фосфор, калий)
Определения modbus_sensor.cpp:342
bool readFirmwareVersion()
Определения modbus_sensor.cpp:153
int readBasicParameters()
Чтение основных параметров (температура, влажность, pH, EC)
Определения modbus_sensor.cpp:319
static unsigned long lastIrrigationTs
Определения modbus_sensor.cpp:22
void finalizeSensorData(bool success)
Финализация данных датчика (валидация, кэширование, скользящее среднее)
Определения modbus_sensor.cpp:442
static void saveRawSnapshot(SensorData &d)
Определения modbus_sensor.cpp:362
static void updateIrrigationFlag(SensorData &d)
Определения modbus_sensor.cpp:373
bool readErrorStatus()
Определения modbus_sensor.cpp:172
bool getCachedData(SensorData &data)
Определения modbus_sensor.cpp:144
ModbusMaster modbus
Определения modbus_sensor.cpp:17
bool validateSensorData(SensorData &data)
Определения modbus_sensor.cpp:132
float calculateMovingAverage(float *buffer, uint8_t window_size, uint8_t filled)
Определения modbus_sensor.cpp:674
bool changeDeviceAddress(uint8_t new_address)
Определения modbus_sensor.cpp:183
void testSP3485E()
Тестирование работы SP3485E.
Определения modbus_sensor.cpp:45
void readSensorData()
Определения modbus_sensor.cpp:478
float convertRegisterToFloat(uint16_t value, float multiplier)
Определения modbus_sensor.cpp:495
String sensorLastError
Определения modbus_sensor.cpp:20
void printModbusError(uint8_t errNum)
Определения modbus_sensor.cpp:547
void initMovingAverageBuffers(SensorData &data)
Определения modbus_sensor.cpp:588
SensorData sensorData
Определения modbus_sensor.cpp:18
bool testModbusConnection()
Определения modbus_sensor.cpp:199
void addToMovingAverage(SensorData &data, float temp, float hum, float ec, float ph, float n, float p, float k)
Определения modbus_sensor.cpp:606
#define MAX_NPK
Определения modbus_sensor.h:31
#define REG_SOIL_TEMP
Определения modbus_sensor.h:11
#define REG_PH
Определения modbus_sensor.h:9
#define REG_POTASSIUM
Определения modbus_sensor.h:15
#define MAX_HUMIDITY
Определения modbus_sensor.h:25
#define MAX_PH
Определения modbus_sensor.h:29
#define REG_PHOSPHORUS
Определения modbus_sensor.h:14
#define MAX_EC
Определения modbus_sensor.h:27
#define REG_DEVICE_ADDRESS
Определения modbus_sensor.h:19
#define REG_NITROGEN
Определения modbus_sensor.h:13
#define MIN_HUMIDITY
Определения modbus_sensor.h:24
#define REG_ERROR_STATUS
Определения modbus_sensor.h:18
#define MIN_TEMPERATURE
Определения modbus_sensor.h:22
#define MIN_EC
Определения modbus_sensor.h:26
#define MIN_NPK
Определения modbus_sensor.h:30
#define REG_CONDUCTIVITY
Определения modbus_sensor.h:12
#define REG_SOIL_MOISTURE
Определения modbus_sensor.h:10
#define MIN_PH
Определения modbus_sensor.h:28
#define MAX_TEMPERATURE
Определения modbus_sensor.h:23
float applyCalibration(float rawValue, SoilProfile profile)
Определения calibration_manager.cpp:111
float k
Определения sensor_compensation.cpp:7
void correctNPK(float T, float theta, float &N, float &P, float &K, SoilType soil)
Определения sensor_compensation.cpp:45
float correctPH(float phRaw, float T)
Определения sensor_compensation.cpp:38
float correctEC(float ecRaw, float T, float theta, SoilType soil)
Определения sensor_compensation.cpp:26
Алгоритмы коррекции показаний датчиков
SoilType
Определения sensor_compensation.h:23
@ LOAM
Определения sensor_compensation.h:23
@ CLAY
Определения sensor_compensation.h:23
@ PEAT
Определения sensor_compensation.h:23
@ SANDPEAT
Определения sensor_compensation.h:23
@ SAND
Определения sensor_compensation.h:23
SoilProfile
Определения sensor_compensation.h:14
@ LOAM
Определения sensor_compensation.h:16
@ CLAY
Определения sensor_compensation.h:18
@ PEAT
Определения sensor_compensation.h:17
@ SANDPEAT
Определения sensor_compensation.h:19
@ SAND
Определения sensor_compensation.h:15
Определения modbus_sensor.h:86
Определения modbus_sensor.h:35
float raw_humidity
Определения modbus_sensor.h:75
float phosphorus
Определения modbus_sensor.h:41
float temperature
Определения modbus_sensor.h:36
uint8_t buffer_index
Определения modbus_sensor.h:70
float nitrogen
Определения modbus_sensor.h:40
bool recentIrrigation
Определения modbus_sensor.h:81
float hum_buffer[15]
Определения modbus_sensor.h:64
bool valid
Определения modbus_sensor.h:47
float humidity
Определения modbus_sensor.h:37
float n_buffer[15]
Определения modbus_sensor.h:67
float moisture
Определения modbus_sensor.h:43
float ec_buffer[15]
Определения modbus_sensor.h:65
float k_buffer[15]
Определения modbus_sensor.h:69
float raw_ph
Определения modbus_sensor.h:77
float raw_potassium
Определения modbus_sensor.h:80
bool isValid
Определения modbus_sensor.h:48
float ph_buffer[15]
Определения modbus_sensor.h:66
float raw_nitrogen
Определения modbus_sensor.h:78
float p_buffer[15]
Определения modbus_sensor.h:68
float ec
Определения modbus_sensor.h:38
float temp_buffer[15]
Определения modbus_sensor.h:63
float raw_phosphorus
Определения modbus_sensor.h:79
float conductivity
Определения modbus_sensor.h:44
unsigned long timestamp
Определения modbus_sensor.h:50
float raw_ec
Определения modbus_sensor.h:76
uint8_t buffer_filled
Определения modbus_sensor.h:71
float raw_temperature
Определения modbus_sensor.h:74
unsigned long last_update
Определения modbus_sensor.h:49
float ph
Определения modbus_sensor.h:39
float potassium
Определения modbus_sensor.h:42