7#include <PubSubClient.h>
8#include <ArduinoJson.h>
11#include <WiFiClient.h>
75void mqttCallback(
char* topic,
byte* payload,
unsigned int length);
88 mac[3], mac[4], mac[5]);
96 if (strlen(
config.mqttDeviceName) > 0)
98 return config.mqttDeviceName;
144 const char* payload = online ?
"online" :
"offline";
145 DEBUG_PRINTF(
"[publishAvailability] Публикация статуса: %s в топик %s\n", payload, topic);
156 DEBUG_PRINTF(
"IP-адрес: %s\n", WiFi.localIP().toString().c_str());
157 DEBUG_PRINTF(
"Маска подсети: %s\n", WiFi.subnetMask().toString().c_str());
158 DEBUG_PRINTF(
"Шлюз: %s\n", WiFi.gatewayIP().toString().c_str());
167 if (!
config.flags.mqttEnabled || strlen(
config.mqttServer) == 0)
169 ERROR_PRINTLN(
"[ОШИБКА] MQTT не может быть инициализирован");
175 if (mqttServerIP == IPAddress(0, 0, 0, 0))
182 DEBUG_PRINTF(
"[DNS] MQTT сервер %s -> %s\n",
config.mqttServer, mqttServerIP.toString().c_str());
188 INFO_PRINTLN(
"[MQTT] Инициализация завершена с DNS кэшированием");
193 DEBUG_PRINTLN(
"[КРИТИЧЕСКАЯ ОТЛАДКА] Попытка подключения к MQTT");
196 if (WiFi.status() != WL_CONNECTED)
203 if (strlen(
config.mqttServer) == 0)
229 DEBUG_PRINTF(
"[MQTT] Результат подключения: %d\n", result);
292 DEBUG_PRINTF(
"[MQTT] Подписались на топик команд: %s\n", commandTopic);
296 DEBUG_PRINTF(
"[MQTT] Подписались на OTA команды: %s\n", otaCmdTopic);
302 if (
config.flags.hassEnabled)
313 if (!
config.flags.mqttEnabled)
318 static bool wasConnected =
false;
322 if (wasConnected && !isConnected)
324 logWarn(
"MQTT подключение потеряно!");
326 else if (!wasConnected && isConnected)
330 wasConnected = isConnected;
334 static unsigned long lastReconnectAttempt = 0;
335 if (millis() - lastReconnectAttempt > 5000)
337 lastReconnectAttempt = millis();
338 logMQTT(
"Попытка переподключения...");
347 static char lastOtaStatus[64] =
"";
348 static unsigned long lastOtaPublish = 0;
349 const unsigned long OTA_STATUS_INTERVAL = 5000;
350 if (millis() - lastOtaPublish > OTA_STATUS_INTERVAL)
353 if (strcmp(cur, lastOtaStatus) != 0)
356 strlcpy(lastOtaStatus, cur,
sizeof(lastOtaStatus));
358 lastOtaPublish = millis();
366 static int skipCounter = 0;
375 if (++skipCounter >=
config.forcePublishCycles)
383 bool hasSignificantChange =
false;
389 hasSignificantChange =
true;
395 hasSignificantChange =
true;
401 hasSignificantChange =
true;
407 hasSignificantChange =
true;
413 hasSignificantChange =
true;
419 hasSignificantChange =
true;
425 hasSignificantChange =
true;
428 if (hasSignificantChange)
434 DEBUG_PRINTLN(
"[DELTA] Изменения незначительные, пропускаем публикацию");
437 return hasSignificantChange;
454 unsigned long currentTime = millis();
455 bool needToRebuildJson =
false;
461 needToRebuildJson =
true;
464 if (needToRebuildJson)
467 StaticJsonDocument<256> doc;
470 doc[
"t"] = round(
sensorData.temperature * 10) / 10.0;
471 doc[
"h"] = round(
sensorData.humidity * 10) / 10.0;
484 DEBUG_PRINTLN(
"[MQTT] Компактный JSON датчика пересоздан и закэширован");
488 static char stateTopicBuffer[128] =
"";
489 static bool stateTopicCached =
false;
490 if (!stateTopicCached)
492 snprintf(stateTopicBuffer,
sizeof(stateTopicBuffer),
"%s/state",
config.mqttTopicPrefix);
493 stateTopicCached =
true;
513 DEBUG_PRINTLN(
"[MQTT] Данные опубликованы, предыдущие значения обновлены");
523 DEBUG_PRINTLN(
"[publishHomeAssistantConfig] Публикация discovery-конфигов Home Assistant...");
526 DEBUG_PRINTLN(
"[publishHomeAssistantConfig] Условия не выполнены, публикация отменена");
531 const char* deviceId = deviceIdStr.c_str();
534 bool needToRebuildConfigs =
false;
538 needToRebuildConfigs =
true;
539 DEBUG_PRINTLN(
"[HA] Кэш конфигураций устарел, пересоздаем...");
542 if (needToRebuildConfigs)
549 StaticJsonDocument<256> deviceInfo;
550 deviceInfo[
"identifiers"] = deviceId;
554 deviceInfo[
"name"] = deviceId;
557 StaticJsonDocument<512> tempConfig;
558 tempConfig[
"name"] =
"JXCT Temperature";
559 tempConfig[
"device_class"] =
"temperature";
560 tempConfig[
"state_topic"] = String(
config.mqttTopicPrefix) +
"/state";
561 tempConfig[
"unit_of_measurement"] =
"°C";
562 tempConfig[
"value_template"] =
"{{ value_json.t }}";
563 tempConfig[
"unique_id"] = String(deviceId) +
"_temp";
564 tempConfig[
"availability_topic"] = String(
config.mqttTopicPrefix) +
"/status";
565 tempConfig[
"device"] = deviceInfo;
568 StaticJsonDocument<512> humConfig;
569 humConfig[
"name"] =
"JXCT Humidity";
570 humConfig[
"device_class"] =
"humidity";
571 humConfig[
"state_topic"] = String(
config.mqttTopicPrefix) +
"/state";
572 humConfig[
"unit_of_measurement"] =
"%";
573 humConfig[
"value_template"] =
"{{ value_json.h }}";
574 humConfig[
"unique_id"] = String(deviceId) +
"_hum";
575 humConfig[
"availability_topic"] = String(
config.mqttTopicPrefix) +
"/status";
576 humConfig[
"device"] = deviceInfo;
579 StaticJsonDocument<512> ecConfig;
580 ecConfig[
"name"] =
"JXCT EC";
581 ecConfig[
"device_class"] =
"conductivity";
582 ecConfig[
"state_topic"] = String(
config.mqttTopicPrefix) +
"/state";
583 ecConfig[
"unit_of_measurement"] =
"µS/cm";
584 ecConfig[
"value_template"] =
"{{ value_json.e }}";
585 ecConfig[
"unique_id"] = String(deviceId) +
"_ec";
586 ecConfig[
"availability_topic"] = String(
config.mqttTopicPrefix) +
"/status";
587 ecConfig[
"device"] = deviceInfo;
590 StaticJsonDocument<512> phConfig;
591 phConfig[
"name"] =
"JXCT pH";
592 phConfig[
"device_class"] =
"ph";
593 phConfig[
"state_topic"] = String(
config.mqttTopicPrefix) +
"/state";
594 phConfig[
"unit_of_measurement"] =
"pH";
595 phConfig[
"value_template"] =
"{{ value_json.p }}";
596 phConfig[
"unique_id"] = String(deviceId) +
"_ph";
597 phConfig[
"availability_topic"] = String(
config.mqttTopicPrefix) +
"/status";
598 phConfig[
"device"] = deviceInfo;
601 StaticJsonDocument<512> nitrogenConfig;
602 nitrogenConfig[
"name"] =
"JXCT Nitrogen";
603 nitrogenConfig[
"state_topic"] = String(
config.mqttTopicPrefix) +
"/state";
604 nitrogenConfig[
"unit_of_measurement"] =
"mg/kg";
605 nitrogenConfig[
"value_template"] =
"{{ value_json.n }}";
606 nitrogenConfig[
"unique_id"] = String(deviceId) +
"_nitrogen";
607 nitrogenConfig[
"availability_topic"] = String(
config.mqttTopicPrefix) +
"/status";
608 nitrogenConfig[
"device"] = deviceInfo;
611 StaticJsonDocument<512> phosphorusConfig;
612 phosphorusConfig[
"name"] =
"JXCT Phosphorus";
613 phosphorusConfig[
"state_topic"] = String(
config.mqttTopicPrefix) +
"/state";
614 phosphorusConfig[
"unit_of_measurement"] =
"mg/kg";
615 phosphorusConfig[
"value_template"] =
"{{ value_json.r }}";
616 phosphorusConfig[
"unique_id"] = String(deviceId) +
"_phosphorus";
617 phosphorusConfig[
"availability_topic"] = String(
config.mqttTopicPrefix) +
"/status";
618 phosphorusConfig[
"device"] = deviceInfo;
621 StaticJsonDocument<512> potassiumConfig;
622 potassiumConfig[
"name"] =
"JXCT Potassium";
623 potassiumConfig[
"state_topic"] = String(
config.mqttTopicPrefix) +
"/state";
624 potassiumConfig[
"unit_of_measurement"] =
"mg/kg";
625 potassiumConfig[
"value_template"] =
"{{ value_json.k }}";
626 potassiumConfig[
"unique_id"] = String(deviceId) +
"_potassium";
627 potassiumConfig[
"availability_topic"] = String(
config.mqttTopicPrefix) +
"/status";
628 potassiumConfig[
"device"] = deviceInfo;
642 INFO_PRINTLN(
"[HA] Конфигурации созданы и закэшированы");
654 INFO_PRINTLN(
"[HA] Конфигурация Home Assistant опубликована из кэша");
661 const char* deviceId = deviceIdStr.c_str();
663 mqttClient.publish((
"homeassistant/sensor/" + String(deviceId) +
"_temperature/config").c_str(),
"",
true);
664 mqttClient.publish((
"homeassistant/sensor/" + String(deviceId) +
"_humidity/config").c_str(),
"",
true);
665 mqttClient.publish((
"homeassistant/sensor/" + String(deviceId) +
"_ec/config").c_str(),
"",
true);
666 mqttClient.publish((
"homeassistant/sensor/" + String(deviceId) +
"_ph/config").c_str(),
"",
true);
667 mqttClient.publish((
"homeassistant/sensor/" + String(deviceId) +
"_nitrogen/config").c_str(),
"",
true);
668 mqttClient.publish((
"homeassistant/sensor/" + String(deviceId) +
"_phosphorus/config").c_str(),
"",
true);
669 mqttClient.publish((
"homeassistant/sensor/" + String(deviceId) +
"_potassium/config").c_str(),
"",
true);
670 INFO_PRINTLN(
"[MQTT] Discovery-конфиги Home Assistant удалены");
682 else if (cmd ==
"reset")
687 else if (cmd ==
"publish_test")
691 else if (cmd ==
"publish_discovery")
695 else if (cmd ==
"remove_discovery")
699 else if (cmd ==
"ota_check")
704 else if (cmd ==
"ota_auto_on")
706 config.flags.autoOtaEnabled = 1;
710 else if (cmd ==
"ota_auto_off")
712 config.flags.autoOtaEnabled = 0;
724 String t = String(topic);
726 for (
unsigned int i = 0; i < length; i++) message += (
char)payload[i];
727 DEBUG_PRINTF(
"[mqttCallback] Получено сообщение: %s = %s\n", t.c_str(), message.c_str());
747 INFO_PRINTLN(
"[MQTT] Кэш Home Assistant конфигураций инвалидирован");
754 unsigned long currentTime = millis();
766 IPAddress resolvedIP;
767 if (WiFi.hostByName(hostname, resolvedIP))
774 DEBUG_PRINTF(
"[DNS] Новый IP %s для %s кэширован\n", resolvedIP.toString().c_str(), hostname);
778 return IPAddress(0, 0, 0, 0);
#define ERROR_PRINTF(fmt,...)
#define DEBUG_PRINTF(fmt,...)
Централизованные константы системы JXCT.
constexpr unsigned long DNS_CACHE_TTL
constexpr size_t HOSTNAME_BUFFER_SIZE
void logWarn(const char *format,...)
void logSuccess(const char *format,...)
void logMQTT(const char *format,...)
Система логгирования с красивым форматированием
static const char * getOtaCommandTopic()
static char clientIdBuffer[32]
static char otaCommandTopicBuffer[128]
static struct HomeAssistantConfigCache haConfigCache
static char mqttLastErrorBuffer[128]
const char * getMqttClientName()
static bool pubTopicCacheValid
void handleMqttCommand(const String &cmd)
void mqttCallback(char *topic, byte *payload, unsigned int length)
static char pubTopicCache[7][128]
void invalidateHAConfigCache()
void removeHomeAssistantConfig()
static unsigned long lastCachedSensorTime
static char commandTopicBuffer[128]
static char cachedSensorJson[256]
static char otaStatusTopicBuffer[128]
static const char * getOtaStatusTopic()
const char * getCommandTopic()
void publishAvailability(bool online)
static char statusTopicBuffer[128]
IPAddress getCachedIP(const char *hostname)
void publishHomeAssistantConfig()
struct DNSCache dnsCacheMqtt
const char * getClientId()
const char * getStatusTopic()
const char * getMqttLastError()
static bool sensorJsonCacheValid
const char * getOtaStatus()
char hostname[HOSTNAME_BUFFER_SIZE]
char phosphorusConfig[512]
char cachedTopicPrefix[64]
char potassiumConfig[512]
static const char DEVICE_MANUFACTURER[]
static const char DEVICE_SW_VERSION[]
static const char DEVICE_MODEL[]