From 88251bc8d652040bd7d2976896f3aa47ce8db98c Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 07:47:18 +0300 Subject: [PATCH 1/9] Add files via upload --- components/aux_ac/aux_ac.h | 200 +++++++++++++------------------------ 1 file changed, 70 insertions(+), 130 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 49768b4..1738976 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -19,7 +19,7 @@ #warning "Saving presets does not work with ESP8266" #endif -#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны +//#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны namespace esphome { namespace aux_ac { @@ -40,7 +40,6 @@ public: static const std::string MUTE; static const std::string TURBO; static const std::string CLEAN; - static const std::string FEEL; static const std::string HEALTH; static const std::string ANTIFUNGUS; @@ -58,12 +57,11 @@ public: const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; const char *const Constants::TAG = "AirCon"; -const std::string Constants::MUTE = "Mute"; -const std::string Constants::TURBO = "Turbo"; -const std::string Constants::CLEAN = "Clean"; -const std::string Constants::FEEL = "Feel"; -const std::string Constants::HEALTH = "Health"; -const std::string Constants::ANTIFUNGUS = "Antifugnus"; +const std::string Constants::MUTE = "mute"; +const std::string Constants::TURBO = "turbo"; +const std::string Constants::CLEAN = "clean"; +const std::string Constants::HEALTH = "health"; +const std::string Constants::ANTIFUNGUS = "antifungus"; const float Constants::AC_MIN_TEMPERATURE = 16.0; const float Constants::AC_MAX_TEMPERATURE = 32.0; const float Constants::AC_TEMPERATURE_STEP = 0.5; @@ -546,7 +544,6 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_health health;\ ac_mode mode;\ ac_sleep sleep;\ - ac_ifeel iFeel;\ ac_louver louver;\ ac_fanspeed fanSpeed;\ ac_fanturbo fanTurbo;\ @@ -556,11 +553,11 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_timer timer;\ uint8_t timer_hours;\ uint8_t timer_minutes;\ - bool temp_target_matter\ + bool temp_target_matter // чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше // из-за такого приема нужно контролировать размер копируемых данных руками -#define AC_COMMAND_BASE_SIZE 21 +#define AC_COMMAND_BASE_SIZE 20 struct ac_command_t { /* @@ -569,7 +566,6 @@ struct ac_command_t { ac_health health; // включение ионизатора ac_mode mode; ac_sleep sleep; - ac_ifeel iFeel; ac_louver louver; ac_fanspeed fanSpeed; ac_fanturbo fanTurbo; @@ -593,6 +589,7 @@ struct ac_command_t { uint8_t invertor_power; // мощность инвертора uint8_t pressure; // предположительно давление bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) + ac_ifeel iFeel; }; // структура для сохранения данных @@ -1263,7 +1260,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _current_ac_state.sleep = (ac_sleep)stateByte; stateByte = small_info_body->mode & AC_IFEEL_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.iFeel != (ac_ifeel)stateByte); _current_ac_state.iFeel = (ac_ifeel)stateByte; stateByte = small_info_body->status & AC_POWER_MASK; @@ -1318,10 +1314,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // temp = big_info_body->outdoor_temperature - 0x20; // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах { - const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; - const float antkoef = 1.0 - koef; - static float temp = _current_ac_state.temp_outdoor; - temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); + const float koef = ((float)OUTDOOR_FILTER_PESCENT); + const float antkoef = 100 - koef; + static float temp = _current_ac_state.temp_outdoor*100; + temp = (temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20))/100; stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); _current_ac_state.temp_outdoor = temp; } @@ -2201,100 +2197,63 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ - if ( _current_ac_state.power == AC_POWER_ON){ // ЕСЛИ КОНДЕЙ ВКЛЮЧЕН - /*************************** iFEEL CUSTOM PRESET ***************************/ - // режим поддержки температуры в районе пульта, работает только при включенном конедее - switch (_current_ac_state.iFeel) { - case AC_IFEEL_ON: - //if(_current_ac_state.mode != AC_MODE_FAN){ // в режиме FAN такой опции нет - this->custom_preset = Constants::FEEL; - //} else { - //// if (this->custom_preset == Constants::FEEL) this->custom_preset = (std::string)""; - //} - break; - case AC_IFEEL_OFF: - default: - if (this->custom_preset == Constants::FEEL) { - this->custom_preset = (std::string)""; - } - break; - } - _debugMsg(F("Climate iFEEL preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.iFeel); - - /*************************** HEALTH CUSTOM PRESET ***************************/ - // режим работы ионизатора - if(_current_ac_state.health == AC_HEALTH_ON) { - this->custom_preset = Constants::HEALTH; - } else { - if (this->custom_preset == Constants::HEALTH) { - this->custom_preset = (std::string)""; - } - } - - _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); - - /*************************** SLEEP PRESET ***************************/ - // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - switch (_current_ac_state.sleep) { - case AC_SLEEP_ON: - this->preset = climate::CLIMATE_PRESET_SLEEP; - break; - case AC_SLEEP_OFF: - default: - if (this->preset == climate::CLIMATE_PRESET_SLEEP) this->preset = climate::CLIMATE_PRESET_NONE; - break; - } - - _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); - - } else if( _current_ac_state.power == AC_POWER_OFF){ // ЕСЛИ КОНДЕЙ ВЫКЛЮЧЕН - - if (this->custom_preset == Constants::FEEL) this->custom_preset = (std::string)""; - if (this->custom_preset == Constants::HEALTH) this->custom_preset = (std::string)""; - if (this->preset == climate::CLIMATE_PRESET_SLEEP) this->preset = climate::CLIMATE_PRESET_NONE; - - /*************************** CLEAN CUSTOM PRESET ***************************/ - // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF - switch (_current_ac_state.clean) { - case AC_CLEAN_ON: - this->custom_preset = Constants::CLEAN; - break; - case AC_CLEAN_OFF: - default: - if (this->custom_preset == Constants::CLEAN) this->custom_preset = (std::string)""; - break; - } - - _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); - - /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ - // пресет просушки кондиционера после выключения - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - // - // Brokly: - // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет - // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии - // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки - // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало - // и затыкается. - switch (_current_ac_state.mildew) { - case AC_MILDEW_ON: - this->custom_preset = Constants::ANTIFUNGUS; - break; - case AC_MILDEW_OFF: - default: - if (this->custom_preset == Constants::ANTIFUNGUS) this->custom_preset = (std::string)""; - break; - } - - _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); + /*************************** SLEEP PRESET ***************************/ + // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. + // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. + // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. + // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. + if(_current_ac_state.sleep == AC_SLEEP_ON && + _current_ac_state.power == AC_POWER_ON ) { + this->preset = climate::CLIMATE_PRESET_SLEEP; + } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { + this->preset = climate::CLIMATE_PRESET_NONE; } + + _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); + + /*************************** HEALTH CUSTOM PRESET ***************************/ + // режим работы ионизатора + if(_current_ac_state.health == AC_HEALTH_ON && + _current_ac_state.power == AC_POWER_ON ) { + this->custom_preset = Constants::HEALTH; + } else if (this->custom_preset == Constants::HEALTH) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); + + /*************************** CLEAN CUSTOM PRESET ***************************/ + // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF + if(_current_ac_state.clean == AC_CLEAN_ON && + _current_ac_state.power == AC_POWER_OFF ) { + this->custom_preset = Constants::CLEAN; + } else if (this->custom_preset == Constants::CLEAN) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); + + /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ + // пресет просушки кондиционера после выключения + // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. + // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. + // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. + // + // Brokly: + // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет + // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии + // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки + // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало + // и затыкается. + if(_current_ac_state.mildew == AC_MILDEW_ON && + _current_ac_state.power == AC_POWER_OFF ) { + this->custom_preset = Constants::ANTIFUNGUS; + } else if (this->custom_preset == Constants::ANTIFUNGUS) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); /*************************** LOUVERs ***************************/ @@ -2385,7 +2344,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { ESP_LOGCONFIG(Constants::TAG, " [x] Show action: %s", TRUEFALSE(this->get_show_action())); ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", TRUEFALSE(this->get_display_inverted())); ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); - ESP_LOGCONFIG(Constants::TAG, " [?] Detect invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "unread"); + ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); if ((this->sensor_indoor_temperature_) != nullptr) { ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); if (!(this->sensor_indoor_temperature_)->get_device_class().empty()) { @@ -2651,12 +2610,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_FAN) || (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_FAN)){ // блокировка ввода температуры в режиме FAN this->target_temperature = _current_ac_state.temp_ambient; - //this->mode = climate::CLIMATE_MODE_FAN_ONLY; this->publish_state(); } else if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_AUTO) || (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_AUTO)){ // блокировка ввода температуры в режиме АВТО this->target_temperature = 25; - //this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->publish_state(); } else { // User requested target temperature change @@ -2772,7 +2729,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.sleep = AC_SLEEP_ON; - cmd.iFeel = AC_IFEEL_OFF; // для логики пресетов cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; this->preset = preset; @@ -2786,7 +2742,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - cmd.iFeel = AC_IFEEL_OFF; // для логики пресетов this->preset = preset; _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); break; @@ -2796,22 +2751,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } else if (call.get_custom_preset().has_value()) { std::string custom_preset = *call.get_custom_preset(); - if (custom_preset == Constants::FEEL) { - // Пульт для работы этого режима не нужен. Как описывает производитель: - // температура выбирается не четкой логикой, анализируя действий - // пользователясвязанных с изменением температуры температуры. - // При этом этот режим можно установить только с пульта. Фигня какая то. - hasCommand = true; - cmd.iFeel = AC_IFEEL_ON; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::HEALTH) { + if (custom_preset == Constants::HEALTH) { hasCommand = true; cmd.health = AC_HEALTH_ON; cmd.health_status = AC_HEALTH_STATUS_ON; - cmd.iFeel = AC_IFEEL_ON; // зависимость от health cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health cmd.sleep = AC_SLEEP_OFF; // для логики пресетов @@ -2834,7 +2777,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (call.get_target_temperature().has_value()) { // блокировка изменения температуры в выключеном состоянии this->target_temperature = _current_ac_state.temp_ambient; - //this->mode = climate::CLIMATE_MODE_OFF; this->publish_state(); } @@ -2878,8 +2820,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.clean = AC_CLEAN_OFF; // для логики пресетов hasCommand = true; this->custom_preset = custom_preset; - } else if (custom_preset == Constants::FEEL) { - _debugMsg(F("iFEEL is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } else if (custom_preset == Constants::HEALTH) { _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } From 94dbb27f03dee144c9cd6bc87d5242f737af689c Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 07:51:21 +0300 Subject: [PATCH 2/9] Add files via upload --- components/aux_ac/aux_ac.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 1738976..ca1f645 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -1317,9 +1317,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { const float koef = ((float)OUTDOOR_FILTER_PESCENT); const float antkoef = 100 - koef; static float temp = _current_ac_state.temp_outdoor*100; - temp = (temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20))/100; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); - _current_ac_state.temp_outdoor = temp; + temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp/100); + _current_ac_state.temp_outdoor = temp/100; } // температура входящей магистрали From 99193935100e2b2c1723cedbda431ab77699f8ea Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:05:31 +0300 Subject: [PATCH 3/9] Add files via upload --- components/aux_ac/aux_ac.h | 12 ++++++------ components/aux_ac/climate.py | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index ca1f645..9c6a569 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -529,7 +529,7 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // настройка усреднения фильтра температуры. Это значение - взнос нового измерения // в усредненные показания в процентах -#define OUTDOOR_FILTER_PESCENT 0.2 +#define OUTDOOR_FILTER_PESCENT 0.5 /** команда для кондиционера * @@ -1314,12 +1314,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // temp = big_info_body->outdoor_temperature - 0x20; // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах { - const float koef = ((float)OUTDOOR_FILTER_PESCENT); - const float antkoef = 100 - koef; - static float temp = _current_ac_state.temp_outdoor*100; + const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; + const float antkoef = 1.0 - koef; + static float temp = _current_ac_state.temp_outdoor; temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp/100); - _current_ac_state.temp_outdoor = temp/100; + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); + _current_ac_state.temp_outdoor = temp; } // температура входящей магистрали diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 414d17a..58a9ab2 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -98,7 +98,6 @@ validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) CUSTOM_PRESETS = { "CLEAN": Capabilities.CLEAN, - "FEEL": Capabilities.FEEL, "HEALTH": Capabilities.HEALTH, "ANTIFUNGUS": Capabilities.ANTIFUNGUS, } From d2efbd33dca1722e6c638f4f8cf134fd53d82101 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:16:01 +0300 Subject: [PATCH 4/9] Delete automation.h --- automation.h | 65 ---------------------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 automation.h diff --git a/automation.h b/automation.h deleted file mode 100644 index a62fb27..0000000 --- a/automation.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/core/automation.h" -#include "aux_ac.h" - -namespace esphome { -namespace aux_ac { - - template - class AirConDisplayOffAction : public Action - { - public: - explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {} - - void play(Ts... x) override { this->ac_->displayOffSequence(); } - - protected: - AirCon *ac_; - }; - - template - class AirConDisplayOnAction : public Action - { - public: - explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {} - - void play(Ts... x) override { this->ac_->displayOnSequence(); } - - protected: - AirCon *ac_; - }; - - template - class AirConSendTestPacketAction : public Action - { - public: - explicit AirConSendTestPacketAction(AirCon *ac) : ac_(ac) {} - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - this->static_ = false; - } - void set_data_static(const std::vector &data) { - this->data_static_ = data; - this->static_ = true; - } - - void play(Ts... x) override { - if (this->static_) { - this->ac_->sendTestPacket(this->data_static_); - } else { - auto val = this->data_func_(x...); - this->ac_->sendTestPacket(val); - } - } - - protected: - AirCon *ac_; - bool static_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; - }; - -} // namespace aux_ac -} // namespace esphome \ No newline at end of file From d3ca62bff848e797d952f662c9746db8a89456ee Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:16:13 +0300 Subject: [PATCH 5/9] Delete aux_ac.h --- aux_ac.h | 3194 ------------------------------------------------------ 1 file changed, 3194 deletions(-) delete mode 100644 aux_ac.h diff --git a/aux_ac.h b/aux_ac.h deleted file mode 100644 index e8efa3e..0000000 --- a/aux_ac.h +++ /dev/null @@ -1,3194 +0,0 @@ -// Custom ESPHome component for AUX-based air conditioners -// Need some soldering skills -// Source code and detailed instructions are available on github: https://github.com/GrKoR/esphome_aux_ac_component -/// немного переработанная версия старого компонента -#pragma once - -#include -#include "esphome.h" -#include -#include "esphome/core/component.h" -#include "esphome/components/climate/climate.h" -#include "esphome/components/uart/uart.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/core/helpers.h" -#if defined(ESP32) - #include "esphome/core/preferences.h" -#else - #warning "Saving presets does not work with ESP8266" -#endif - -//#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны - -namespace esphome { -namespace aux_ac { - - -using climate::ClimatePreset; -using climate::ClimateTraits; -using climate::ClimateMode; -using climate::ClimateSwingMode; -using climate::ClimateFanMode; - - -class Constants { -public: - static const std::string AC_FIRMWARE_VERSION; - - static const char *const TAG; - static const std::string MUTE; - static const std::string TURBO; - static const std::string CLEAN; - static const std::string HEALTH; - static const std::string ANTIFUNGUS; - - /// минимальная и максимальная температура в градусах Цельсия, ограничения самого кондиционера - static const float AC_MIN_TEMPERATURE; - static const float AC_MAX_TEMPERATURE; - /// шаг изменения целевой температуры, градусы Цельсия - static const float AC_TEMPERATURE_STEP; - - // периодичность опроса кондиционера на предмет изменения состояния - // изменение параметров с пульта не сообщается в UART, поэтому надо запрашивать состояние, чтобы быть в курсе - // значение в миллисекундах - static const uint32_t AC_STATES_REQUEST_INTERVAL; -}; - -const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; -const char *const Constants::TAG = "AirCon"; -const std::string Constants::MUTE = "mute"; -const std::string Constants::TURBO = "turbo"; -const std::string Constants::CLEAN = "clean"; -const std::string Constants::HEALTH = "health"; -const std::string Constants::ANTIFUNGUS = "antifungus"; -const float Constants::AC_MIN_TEMPERATURE = 16.0; -const float Constants::AC_MAX_TEMPERATURE = 32.0; -const float Constants::AC_TEMPERATURE_STEP = 0.5; -const uint32_t Constants::AC_STATES_REQUEST_INTERVAL = 7000; - -class AirCon; - -// состояния конечного автомата компонента -enum acsm_state : uint8_t { - ACSM_IDLE = 0, // ничего не делаем, ждем, на что бы среагировать - ACSM_RECEIVING_PACKET, // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны - ACSM_PARSING_PACKET, // разбираем полученный пакет - ACSM_SENDING_PACKET, // отправляем пакет сплиту -}; - -/** - * Кондиционер отправляет пакеты следующей структуры: - * HEADER: 8 bytes - * BODY: 0..24 bytes - * CRC: 2 bytes - * Весь пакет максимум 34 байта - * По крайней мере все встреченные мной пакеты имели такой размер и структуру. - **/ -#define AC_HEADER_SIZE 8 -#define AC_MAX_BODY_SIZE 24 -#define AC_BUFFER_SIZE 35 - -/** - * таймаут загрузки пакета - * - * через такое количиство миллисекунд конечный автомат перейдет из состояния ACSM_RECEIVING_PACKET в ACSM_IDLE, если пакет не будет загружен - * расчетное время передачи 1 бита при скорости 4800 примерно 0,208 миллисекунд; - * 1 байт передается 11 битами (1 стартовый, 8 бит данных, 1 бит четности и 1 стоповый бит) или 2,30 мс. - * максимальный размер пакета AC_BUFFER_SIZE = 34 байта => 78,2 мсек. Плюс накладные расходы. - * Скорее всего на получение пакета должно хватать 100 мсек. - * - * По факту проверка показала: - * - если отрабатывать по 1 символу из UART на один вызов loop, то на 10 байт пинг-пакета требуется 166 мсек. - * То есть примерно по 16,6 мсек на байт. Примем 17 мсек. - * Значит на максимальный пакет потребуется 17*34 = 578 мсек. Примем 600 мсек. - * - если отрабатывать пакет целиком или хотя бы имеющимися в буфере UART кусками, то на 10 байт пинг-пакета требуется 27 мсек. - * То есть примерно по 2,7 мсек. на байт. Что близко к расчетным значениям. Примем 3 мсек. - * Значит на максимальный пакет потребуется 3*34 = 102 мсек. Примем 150 мсек. - * Опыт показал, что 150 мсек вполне хватает на большие пакеты - **/ -#define AC_PACKET_TIMEOUT 150 // 150 мсек - отработка буфера UART за раз, 600 мсек - отработка буфера UART по 1 байту за вызов loop - -// типы пакетов -#define AC_PTYPE_PING 0x01 // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает -#define AC_PTYPE_CMD 0x06 // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита -#define AC_PTYPE_INFO 0x07 // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля -#define AC_PTYPE_INIT 0x09 // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. -#define AC_PTYPE_UNKN 0x0b // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует - -// типы команд -#define AC_CMD_STATUS_BIG 0x21 // большой пакет статуса кондиционера -#define AC_CMD_STATUS_SMALL 0x11 // маленький пакет статуса кондиционера -#define AC_CMD_STATUS_PERIODIC 0x2C // иногда встречается, сплит её рассылает по своему разумению; (вроде бы может быть и другой код! надо больше данных) -#define AC_CMD_SET_PARAMS 0x01 // команда установки параметров кондиционера - -// значения байтов в пакетах -#define AC_PACKET_START_BYTE 0xBB // Стартовый байт любого пакета 0xBB, других не встречал -#define AC_PACKET_ANSWER 0x80 // признак ответа wifi-модуля - -// заголовок пакета -struct packet_header_t { - uint8_t start_byte; // стартовый бит пакета, всегда 0xBB - uint8_t _unknown1; // не расшифрован - uint8_t packet_type; // тип пакета: - // 0x01 - пинг - // 0x06 - команда кондиционеру - // 0x07 - информационный пакет со статусом кондиционера - // 0x09 - (не разбирался) инициирование коннекта wifi-модуля с приложением на телефоне, с ESP работает и без этого - // 0x0b - (не разбирался) wifi-модуль так сигналит, когда не получает пинги от кондиционера и в каких-то еще случаях - uint8_t wifi; // признак пакета от wifi-модуля - // 0x80 - для всех сообщений, посылаемых модулем - // 0x00 - для всех сообщений, посылаемых кондиционером - uint8_t ping_answer_01; // не расшифрован, почти всегда 0x00, только в ответе на ping этот байт равен 0x01 - uint8_t _unknown2; // не расшифрован - uint8_t body_length; // длина тела пакета в байтах - uint8_t _unknown3; // не расшифрован -}; - -// CRC пакета -union packet_crc_t { - uint16_t crc16; - uint8_t crc[2]; -}; - -struct packet_t { - uint32_t msec; // значение millis в момент определения корректности пакета - packet_header_t * header; - packet_crc_t * crc; - uint8_t * body; // указатель на первый байт тела; можно приведением типов указателей обращаться к отдельным битам как к полям соответсвующей структуры - uint8_t bytesLoaded; //количество загруженных в пакет байт, включая CRC - uint8_t data[AC_BUFFER_SIZE]; -}; - -// тело ответа на пинг -struct packet_ping_answer_body_t { - uint8_t byte_1C = 0x1C; // первый байт всегда 0x1C - uint8_t byte_27 = 0x27; // второй байт тела пинг-ответа всегда 0x27 - uint8_t zero1 = 0; // всегда 0x00 - uint8_t zero2 = 0; // всегда 0x00 - uint8_t zero3 = 0; // всегда 0x00 - uint8_t zero4 = 0; // всегда 0x00 - uint8_t zero5 = 0; // всегда 0x00 - uint8_t zero6 = 0; // всегда 0x00 -}; - -// тело большого информационного пакета -struct packet_big_info_body_t { - uint8_t byte_01; // всегда 0x01 - uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x21); - // пакет может рассылаться и в дежурном режиме (без запроса со стороны wifi-модуля) - // в этом случае тут могут быть значения, отличные от 0x21 - // БАЙТ2 - uint8_t reserv20 :5; // не расшифрован, всегда 0xC0 11000000 - bool is_invertor :1; // флаг инвертора - uint8_t reserv21 :2; // для RoyalClima18HNI: всегда 0xE0 11100000 - // Brokly: для Energolux Bern: 0xE0; иногда, с равными промежутками во времени проскакивает )xE4 - // предполагаю, что это байт конфига кондиционера (5 бит - инвертер), (2 бит - периодический мпульсный сигнал,период пимерно 500 сек) - // БАЙТ 3 - bool power:1; - bool sleep:1; - bool louver_V:1; - uint8_t louver_H:2; // у шторок лево-право, почему то два бита - uint8_t mode:3; // enum { AC_BIG_MODE_AUTO = 0, - // AC_BIG_MODE_COOL = 1, - // AC_BIG_MODE_DRY = 2, - // AC_BIG_MODE_HEAT = 4, - // AC_BIG_MODE_FAN = 6} - // - // - // Встречались такие значения: - // 0x04 100 - сплит выключен, до этого работал (статус держится 1 час после выкл.) - // 0x05 101 - режим AUTO - // 0x24 100100 - режим OFF - // 0x25 100101 - режим COOL - // 0x39 111001 - ?? - // 0x45 1000101 - режим DRY - // 0x85 10000101 - режим HEAT - // 0xC4 11000100 - режим OFF, выключен давно, зима - // 0xC5 11000101 - режим FAN - // Brokly: - // Встречались такие значения : - // 0x00 00000000 - OFF - // 0x01 00000001 - AUTO // режим авто, нет отдельного бита - // 0x41 1000001 - DRY - // 0x21 100001 - COOL - // 0x81 10000001 - HEAT - // 0x85 10000101 - HEAT+шторки верх-низ - // 0x99 10011001 - HEAT+шторки влево вправо - // 0xC1 11000001 - FAN // 7 и 6 бит связаны - // 0x80 10000000 - продувка после переключения из HEAT в OFF - // 0xC5 11000101 - FAN+шторки верх-низ - // 0xDD 11011101 - FAN+шторки лево-право/верх-низ - // 0xD9 11011001 - FAN+шторки лево-право - // 0xD8 11011000 - из FAN+шторки лево-право в OFF - // 0x39 111001 - COOL+шторки лево-право - // Очевидно битовые, но связные, поля, предположительные зависимости - // ВНИМАНИЕ : режимы номинальны, например в режиме АВТО нагрев или охлаждение не отображаются - // 7+6+5 4+3 2 1 0 - // MODE Louv_L Louv_H SLEEP ON/OFF - // - // ФУНКЦМЯ CLEEN, HEALTH, ANTIFUNGUS на данный байт не влияют - // - // #define AC_BIG_MASK_MODE b11100000 - // enum { AC_BIG_MODE_DRY = 0x40, - // AC_BIG_MODE_COOL = 0x20, - // AC_BIG_MODE_HEAT = 0x80, - // AC_BIG_MODE_FAN = 0xC0} - // #define AC_BIG_MASK_MODE b00011100 - // enum { AC_BIG_LOUVERS_H = 0x04, - // AC_BIG_LOUVERS_L = 0x18, - // AC_BIG_LOUVERS_BOTH = 0x1C} - // #define AC_BIG_MASK_POWER b00000001 - // #define AC_BIG_MASK_SLEEP b00000010 - // #define AC_BIG_MASK_COOL b00100000 - // - // БАЙТ 4 - uint8_t reserv40:4; // 8 - bool needDefrost:1; // 5 бит начало разморозки(накопление тепла) - bool defrostMode:1; // 6 бит режим разморозки внешнего блока (прогрев испарителя) - bool reserv41:1; // для RoyalClima18HNI: режим разморозки внешнего блока - 0x20, в других случаях 0x00 - bool cleen:1; // 8 бит CLEAN - - // Для кондея старт-стоп - // x xx - // C5 11000101 - // C4 11000100 - // 85 10000101 - // 84 10000100 - // 3D 00111101 - // 3C 00111100 - // 25 00100101 - // 24 00100100 - // 5 00000101 - // 4 00000100 - - - // БАЙТ5 - uint8_t realFanSpeed:3; // Brokly: Energolux Bern - подтверждаю, ВАЖНО !!!! Это реальная скорость фена - uint8_t reserv30:5; // та которая в данный момент. Например может быть установлен нагрев со скоростью - // вентилятора HI, но кондей еще не произвел достаточно тепла, и крутит на LOW, - // Тут будет отображаться LOW - // fanSpeed: OFF=0x00, MUTE=0x01, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07 - // в дежурных пакетах тут похоже что-то другое - // БАЙТ6 - bool reserv60:1; - uint8_t fanPWM:7; // скорость шима вентилятора - // 126...128 - turbo - // 100...113 - hi - // 84...85 - mid - // 59...62 - low - // 0 - off - // - // БАЙТ7 - uint8_t ambient_temperature_int; // Brokly: ПОДТВЕРЖДАЮ это точно показания датчика под крышкой внутреннего блока, - // физически доступен для пользователя - // целая часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // перевод по формуле T = Тin - 0x20 + Tid/10 - // где - // Tin - целая часть температуры - // Tid - десятичная часть температуры - - // В ВЫКЛЮЧЕНОМ СОСТОНИИ занчение 7 8 9 10 равны !!!!!! - // А значит это термодатчики внутри внутреннего блока !!!!!! - - // БАЙТ8 - uint8_t zero3; // Brokly: полностью повторяет значение 9 байта - - // БАЙТ9 - uint8_t in_temperature_int; // Brokly: скорее всего это действительно какая то температура или дельта температур - // СКОРЕЕ ВСЕГО ЭТО ТЕМПЕРАТУРА ПОДАЧИ !!!!!! При охлаждении - холодная, при нагреве теплая - - // холоднее или горячее температуры в команте. В выключеном состоянии стремится к комнатной темп. - // у меня на трех инверторных кондиционерах Energolux серии Bern - // в выключеном состоянии значение этого байта находится на уровне 57-68 (мощность 0%) - // зависит от мощности работы компрессора (измерения при 12гр на улице) - // в режиме охлаждения уменьшается и при мощности 47% = 40 / 73% = 38 - // в режиме нагрева увеличивается и при мощности 47% = 70 / 73% = 75 / 84% = 84 - // изменение этого значения более вялое, с западыванием относительно изменения мощности - // видимо является реакцией (следствием работы) на изменение мощности инвертора - // учитывая стиль записи температур имеет смысл рассматривать это значение как увеличенное на 0x20 - - - - // этот байт как-то связан с температурой во внешнем блоке. Требуются дополнительные исследования. - // При выключенном сплите характер изменения значения примерно соответствует изменению температуры на улице. - // При включенном сплите значение может очень сильно скакать. - // По схеме wiring diagram сплит-системы, во внешнем блоке есть термодатчик, отслеживающий температуру испарителя. - // Возможно, этот байт как раз и отражает изменение температуры на испарителе. - // Но я не смог разобраться, как именно перевести эти значения в градусы. - // Кроме того, зимой даже в минусовую температуру этот байт не уходит ниже 0x33 по крайней мере - // для температур в диапазоне -5..-10 градусов Цельсия. - // БАЙТ10 - uint8_t zero4; // Brokly: полностью повторяет значение 9 байта - - // БАЙТ11 - uint8_t zero5; // всегда = 100 (0x64) - - // БАЙТ12 - uint8_t outdoor_temperature; // Brokly Energolux Bern: Внешняя температура формула T=БАЙТ12 - 0x20 - // Датчик на радиаторе внешнего блока, доступен для пользователя, без разборки блока - // температура внешнего теплообменника влияет на это значение (при работе на обогрев - понижает, при охлаждении или при разморозке - повышает) - // для RoyalClima18HNI: похоже на какую-то температуру, точно неизвестно - - // БАЙТ13 - uint8_t out_temperature_int; // всегда 0x00 - // для RoyalClima18HNI: 0x20 - // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ13 - 0x20 - // При охлаждении растет, при нагреве падает, можно делать вывод о режиме COOL или HEAT - // ПОХОЖЕ НА ТЕМПЕРАТУРУ ОБРАТКИ !!! - - // БАЙТ14 - uint8_t strange_temperature_int;// всегда 0x00 - // для RoyalClima18HNI: 0x20 - // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ14 - 0x20 - // показания РАСТЕТ ПРИ ВКЛЮЧЕНИИ ИНВЕРТОРА, при выключении падают до комнатной - // от режима охлаждения или нагрева не зависит !!! - - - // БАЙТ15 - uint8_t zero9; // всегда 0x00, Brokly: Energolux Bern, всегда 0x39 111001 - // БАЙТ16 - uint8_t invertor_power; // МОщность инвертера (Brokly: подтверждаю) - // для RoyalClima18HNI: мощность инвертора (от 0 до 100) в % - // например, разморозка внешнего блока происходит при 80% - // БАЙТ17 - uint8_t zero11; // всегда 0x00 - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 22, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика достаточно плавна - - // БАЙТ18 - uint8_t zero12; // - // Brokly: Energolux Bern : наложение на показания инвертора (от 144 до 174, когда инвертор отключен - // показания немного скачут в районе 149...154, при включении инвертора быстро растет, при выключении - // моментально падает до 149...154, бывают опускания ниже этих значений до 144, чаще в момент первоначального - // включения инвертора, а потом вверх, не всегда. При включении уходит в 0 на одну посылку - - // БАЙТ19 - uint8_t zero13; // Brokly: Energolux Bern : включение 144 -> 124 -> 110 далее все время держим 110 - - // БАЙТ20 - uint8_t zero14; // - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 45, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика дрожащая нестабильная - // колебания в районе +-2...4 единицы - // БАЙТ21 - uint8_t zero15; // всегда 0x00 Brokly: подтверждаю - - // БАЙТ22 - uint8_t zero16; // всегда 0x00 Brokly: подтверждаю - - // БАЙТ23 - uint8_t ambient_temperature_frac:4; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // подробнее смотреть ambient_temperature_int - // для RoyalClima18HNI: старшие 4 бита - 0x2 - uint8_t reserv023:4; -}; - -// тело малого информационного пакета -struct packet_small_info_body_t { - uint8_t byte_01; // не расшифрован, всегда 0x01 - uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x11); - // в пакетах сплита другие варианты не встречаются - // в отправляемых wifi-модулем пакетах тут может быть 0x01, если требуется установить режим работы - uint8_t target_temp_int_and_v_louver; // целая часть целевой температуры и положение вертикальных жалюзи - // три младших бита - положение вертикальных жалюзи - // если они все = 0, то вертикальный SWING включен - // если они все = 1, то выключен вертикальный SWING - // протокол универсильный, другие комбинации битов могут задавать какие-то положения - // вертикальных жалюзи, но у меня на пульте таких возможностей нет, надо экспериментировать. - // пять старших бит - целая часть целевой температуры - // температура определяется по формуле: - // 8 + (target_temp_int_and_v_louver >> 3) + (0.5 * (target_temp_frac >> 7)) - uint8_t h_louver; // старшие 3 бита - положение горизонтальных жалюзи, остальное не изучено и всегда было 0 - // если все 3 бита = 0, то горизонтальный SWING включен - // если все 3 бита = 1, то горизонтальный SWING отключен - // надо изучить другие комбинации - uint8_t target_temp_frac; // старший бит - дробная часть целевой температуры - // остальные биты до конца не изучены: - // бит 6 был всегда 0 - // биты 0..5 растут на 1 каждую минуту, возможно внутренний таймер для включения/выключения по времени - uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны - // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 - uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны - // БФЙТ 7 - uint8_t mode; // режим работы сплита: - // AUTO : bits[7, 6, 5] = [0, 0, 0] - // COOL : bits[7, 6, 5] = [0, 0, 1] - // DRY : bits[7, 6, 5] = [0, 1, 0] - // HEAT : bits[7, 6, 5] = [1, 0, 1] - // FAN : bits[7, 6, 5] = [1, 1, 1] - // Sleep function : bit 2 = 1 - // iFeel function : bit 3 = 1 - uint8_t zero1; // всегда 0x00 - uint8_t zero2; // всегда 0x00 - uint8_t status; // бит 5 = 1: включен, обычный режим работы (когда можно включить нагрев, охлаждение и т.п.) - // бит 2 = 1: режим самоочистки, должен запускаться только при бит 5 = 0 - // бит 0 и бит 1: активация режима ионизатора воздуха (не проверен, у меня его нет) - uint8_t zero3; // всегда 0x00 - uint8_t display_and_mildew; // бит4 = 1, чтобы погасить дисплей на внутреннем блоке сплита - // бит3 = 1, чтобы включить функцию "антиплесень" (после отключения как-то прогревает или просушивает теплообменник, чтобы на нем не росла плесень) - uint8_t zero4; // всегда 0x00 - uint8_t target_temp_frac2; // дробная часть целевой температуры, может быть только 0x00 и 0x05 - // при установке температуры тут 0x00, а заданная температура передается в target_temp_int_and_v_louver и target_temp_frac - // после установки сплит в информационных пакетах тут начинает показывать дробную часть - // не очень понятно, зачем так сделано -}; - -//**************************************************************************************************************************************************** -//*************************************************** ПАРАМЕТРЫ РАБОТЫ КОНДИЦИОНЕРА ****************************************************************** -//**************************************************************************************************************************************************** - -// для показаний о реальной скорости фена из большого пакета -enum ac_realFan : uint8_t { AC_REAL_FAN_OFF = 0x00, AC_REAL_FAN_MUTE = 0x01, AC_REAL_FAN_LOW = 0x02, AC_REAL_FAN_MID = 0x04, - AC_REAL_FAN_HIGH = 0x06, AC_REAL_FAN_TURBO = 0x07, AC_REAL_FAN_UNTOUCHED = 0xFF }; - -// для всех параметров ниже вариант X_UNTOUCHED = 0xFF означает, что этот параметр команды должен остаться тот, который уже установлен -// питание кондиционера -#define AC_POWER_MASK 0b00100000 -enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTOUCHED = 0xFF }; - -// режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF -#define AC_CLEAN_MASK 0b00000100 -enum ac_clean : uint8_t { AC_CLEAN_OFF = 0x00, AC_CLEAN_ON = 0x04, AC_CLEAN_UNTOUCHED = 0xFF }; - -// для включения ионизатора нужно установить второй бит в байте -// по результату этот бит останется установленным, но кондиционер еще и установит первый бит -#define AC_HEALTH_MASK 0b00000010 -enum ac_health : uint8_t { AC_HEALTH_OFF = 0x00, AC_HEALTH_ON = 0x02, AC_HEALTH_UNTOUCHED = 0xFF }; - -// Статус ионизатора. Если бит поднят, то обнаружена ошибка ключения ионизатора -#define AC_HEALTH_STATUS_MASK 0b00000001 -enum ac_health_status : uint8_t { AC_HEALTH_STATUS_OFF = 0x00, AC_HEALTH_STATUS_ON = 0x01, AC_HEALTH_STATUS_UNTOUCHED = 0xFF }; - -// целевая температура -#define AC_TEMP_TARGET_INT_PART_MASK 0b11111000 -#define AC_TEMP_TARGET_FRAC_PART_MASK 0b10000000 - -// задержка отключения кондиционера -#define AC_TIMER_MINUTES_MASK 0b00111111 -#define AC_TIMER_HOURS_MASK 0b00011111 - -// включение таймера сна -#define AC_TIMER_MASK 0b01000000 -enum ac_timer : uint8_t {AC_TIMER_OFF = 0x00, AC_TIMER_ON = 0x40, AC_TIMER_UNTOUCHED = 0xFF}; - -// основные режимы работы кондиционера -#define AC_MODE_MASK 0b11100000 -enum ac_mode : uint8_t { AC_MODE_AUTO = 0x00, AC_MODE_COOL = 0x20, AC_MODE_DRY = 0x40, AC_MODE_HEAT = 0x80, AC_MODE_FAN = 0xC0, AC_MODE_UNTOUCHED = 0xFF }; - -// Ночной режим (SLEEP). Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. -// COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. -// HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. -// Восстанавливается ли температура через 7 часов при отключении режима - не понятно. -#define AC_SLEEP_MASK 0b00000100 -enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTOUCHED = 0xFF }; - -// функция iFeel - поддерживате температуру по датчику в пульте ДУ, а не во внутреннем блоке кондиционера -#define AC_IFEEL_MASK 0b00001000 -enum ac_ifeel : uint8_t { AC_IFEEL_OFF = 0x00, AC_IFEEL_ON = 0x08, AC_IFEEL_UNTOUCHED = 0xFF }; - -// Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V -#define AC_LOUVERV_MASK 0b00000111 -enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_UPDOWN = 0x00, AC_LOUVERV_OFF = 0x07, AC_LOUVERV_UNTOUCHED = 0xFF }; - -// Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H -#define AC_LOUVERH_MASK 0b11100000 -enum ac_louver_H : uint8_t { AC_LOUVERH_SWING_LEFTRIGHT = 0x00, AC_LOUVERH_OFF = 0xE0, AC_LOUVERH_UNTOUCHED = 0xFF }; - -struct ac_louver { - ac_louver_H louver_h; - ac_louver_V louver_v; -}; - -// скорость вентилятора -#define AC_FANSPEED_MASK 0b11100000 -enum ac_fanspeed : uint8_t { AC_FANSPEED_HIGH = 0x20, AC_FANSPEED_MEDIUM = 0x40, AC_FANSPEED_LOW = 0x60, AC_FANSPEED_AUTO = 0xA0, AC_FANSPEED_UNTOUCHED = 0xFF }; - -// TURBO работает только в режимах COOL и HEAT -#define AC_FANTURBO_MASK 0b01000000 -enum ac_fanturbo : uint8_t { AC_FANTURBO_OFF = 0x00, AC_FANTURBO_ON = 0x40, AC_FANTURBO_UNTOUCHED = 0xFF }; - -// MUTE работает только в режиме FAN. В режиме COOL кондей команду принимает, но MUTE не устанавливается -#define AC_FANMUTE_MASK 0b10000000 -enum ac_fanmute : uint8_t { AC_FANMUTE_OFF = 0x00, AC_FANMUTE_ON = 0x80, AC_FANMUTE_UNTOUCHED = 0xFF }; - -// включение-выключение дисплея на корпусе внутреннего блока -#define AC_DISPLAY_MASK 0b00010000 -enum ac_display : uint8_t { AC_DISPLAY_OFF = 0x00, AC_DISPLAY_ON = 0x10, AC_DISPLAY_UNTOUCHED = 0xFF }; - -// включение-выключение функции "Антиплесень". -// По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. Уличный блок при этом гудит и тарахтит. -// Возможно, прогревается теплообменник для высыхания. Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. -#define AC_MILDEW_MASK 0b00001000 -enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_UNTOUCHED = 0xFF }; - -// маска счетчика минут прошедших с последней команды -#define AC_MIN_COUTER 0b00111111 // - -// настройка усреднения фильтра температуры. Это значение - взнос нового измерения -// в усредненные показания в процентах -#define OUTDOOR_FILTER_PESCENT 0.2 - -/** команда для кондиционера - * - * ВАЖНО! В коде используется копирование команд простым присваиванием. - * Если в структуру будут введены указатели, то копирование надо будет изменить! -*/ - -// данные структур содержат настройку, специально вынес в макрос -#define AC_COMMAND_BASE float temp_target;\ - ac_power power;\ - ac_clean clean;\ - ac_health health;\ - ac_mode mode;\ - ac_sleep sleep;\ - ac_louver louver;\ - ac_fanspeed fanSpeed;\ - ac_fanturbo fanTurbo;\ - ac_fanmute fanMute;\ - ac_display display;\ - ac_mildew mildew;\ - ac_timer timer;\ - uint8_t timer_hours;\ - uint8_t timer_minutes;\ - bool temp_target_matter - -// чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше -// из-за такого приема нужно контролировать размер копируемых данных руками -#define AC_COMMAND_BASE_SIZE 20 - -struct ac_command_t { -/* - ac_power power; - ac_clean clean; - ac_health health; // включение ионизатора - ac_mode mode; - ac_sleep sleep; - ac_louver louver; - ac_fanspeed fanSpeed; - ac_fanturbo fanTurbo; - ac_fanmute fanMute; - ac_display display; - ac_mildew mildew; - ac_timer timer; - uint8_t timer_hours; - uint8_t timer_minutes; - float temp_target; - bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную -*/ - AC_COMMAND_BASE; - ac_health_status health_status; - float temp_ambient; // внутренняя температура - int8_t temp_outdoor; // внешняя температура - int8_t temp_inbound; // температура входящая - int8_t temp_outbound; // температура исходящая - int8_t temp_strange; // непонятная температура, понаблюдаем - ac_realFan realFanSpeed; // текущая скорость вентилятора - uint8_t invertor_power; // мощность инвертора - uint8_t pressure; // предположительно давление - bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) - ac_ifeel iFeel; -}; - -// структура для сохранения данных -struct ac_save_command_t { - AC_COMMAND_BASE; -}; - -// номера сохранений пресетов -enum store_pos : uint8_t { - POS_MODE_AUTO = 0, - POS_MODE_COOL, - POS_MODE_DRY, - POS_MODE_HEAT, - POS_MODE_FAN, - POS_MODE_OFF}; - -typedef ac_command_t ac_state_t; // текущее состояние параметров кондея можно хранить в таком же формате, как и комманды - -//**************************************************************************************************************************************************** -//************************************************ КОНЕЦ ПАРАМЕТРОВ РАБОТЫ КОНДИЦИОНЕРА ************************************************************** -//**************************************************************************************************************************************************** - - -/***************************************************************************************************************************************************** - * структуры и типы для последовательности команд - ***************************************************************************************************************************************************** - * - * Последовательность команд позволяет выполнить несколько последовательных команд с контролем получаемых в ответ пакетов. - * Если требуется, в получаемых в ответ пакетах можно контролировать значение любых байт. - * Для входящего пакета байт, значение которого не проверяется, должен быть установлен в AC_SEQUENCE_ANY_BYTE. - * Контроль возможен только для входящих пакетов, исходящие отправляются "как есть". - * - * Для исходящих пакетов значения CRC могут не рассчитываться, контрольная сумма будет рассчитана автоматически. - * Для входящих пакетов значение CRC также можно не рассчитывать, установив байты CRC в AC_SEQUENCE_ANY_BYTE, - * так как контроль CRC для получаемых пакетов выполняется автоматически при получении. - * - * Для входящих пакетов в последовательности можно указать таймаут. Если таймаут равен 0, то используется значение AC_SEQUENCE_DEFAULT_TIMEOUT. - * Если в течение указанного времени подходящий пакет не будет получен, то последовательность прерывается с ошибкой. - * Пинг-пакеты в последовательности игнорируются. - * - * Пауза в последовательности задается значением timeout элемента AC_DELAY. Никакие другие параметры такого элемента можно не заполнять. - * - **/ -// максимальная длина последовательности; больше вроде бы не требовалось -#define AC_SEQUENCE_MAX_LEN 0x0F - -// в пакетах никогда не встречалось значение 0xFF (только в CRC), поэтому решено его использовать как признак не важного значение байта -//#define AC_SEQUENCE_ANY_BYTE 0xFF - -// дефолтный таймаут входящего пакета в миллисекундах -// если для входящего пакета в последовательности указан таймаут 0, то используется значение по-умолчанию -// если нужный пакет не поступил в течение указанного времени, то последовательность прерывается с ошибкой -// Brokly: пришлось увеличить -#define AC_SEQUENCE_DEFAULT_TIMEOUT 580 - -enum sequence_item_type_t : uint8_t { - AC_SIT_NONE = 0x00, // пустой элемент последовательности - AC_SIT_DELAY = 0x01, // пауза в последовательности на нужное количество миллисекунд - AC_SIT_FUNC = 0x02 // рабочий элемент последовательности -}; - -// тип пакета в массиве последовательности -// информирует о том, что за пакет лежит в поле packet элемента последовательности -enum sequence_packet_type_t : uint8_t { - AC_SPT_CLEAR = 0x00, // пустой пакет - AC_SPT_RECEIVED_PACKET = 0x01, // полученный пакет - AC_SPT_SENT_PACKET = 0x02 // отправленный пакет -}; - -/** элемент последовательности - * Поля item_type, func, timeout и cmd устанавливаются ручками и задают параметры выполнения шага последовательности. - * Поля msec, packet_type и packet заполняются движком при обработке последовательности. - **/ -struct sequence_item_t { - sequence_item_type_t item_type; // тип элемента последовательности - bool (AirCon::*func)(); // указатель на функцию, отрабатывающую шаг последовательности - uint16_t timeout; // допустимый таймаут в ожидании пакета (применим только для входящих пакетов) - ac_command_t cmd; // новое состояние сплита, нужно для передачи кондиционеру команд - //******* поля ниже заполняются функциями обработки последовательности *********** - uint32_t msec; // время старта текущего шага последовательности (для входящего пакета и паузы) - sequence_packet_type_t packet_type; // тип пакета (входящий, исходящий или вовсе не пакет) - packet_t packet; // данные пакета -}; -/*****************************************************************************************************************************************************/ - - -class AirCon : public esphome::Component, public esphome::climate::Climate { - private: - - // массив для сохранения данных глобальных персетов - ac_save_command_t global_presets[POS_MODE_OFF+1]; - #if defined(ESP32) - // тут будем хранить данные глобальных пресетов во флеше - // ВНИМАНИЕ на данный момент 22.05.22 ESPHOME 20022.5.0 имеет ошибку - // траблтикет: https://github.com/esphome/issues/issues/3298 - // из-за этого сохранение в энергонезависимую память не работает !!! - ESPPreferenceObject storage = global_preferences->make_preference(this->get_object_id_hash(), true); - #endif - // настройка-ключ, для включения сохранения - восстановления настроек каждого - // режима работы в отдельности, то есть каждый режим работы имеет свои настройки - // температуры, шторок, скорости вентилятора, пресетов - bool _store_settings = false; - // флаги для сохранения пресетов - bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено - - // время последнего запроса статуса у кондея - uint32_t _dataMillis; - // периодичность обновления статуса кондея, по дефолту AC_STATES_REQUEST_INTERVAL - uint32_t _update_period = Constants::AC_STATES_REQUEST_INTERVAL; - - // надо ли отображать текущий режим работы внешнего блока - // в режиме нагрева, например, кондиционер может как греть воздух, так и работать в режиме вентилятора, если целевая темпреатура достигнута - // по дефолту показываем - bool _show_action = true; - - // как отрабатывается включание-выключение дисплея. - // если тут false, то 1 в соответствующем бите включает дисплей, а 0 выключает. - // если тут true, то 1 потушит дисплей, а 0 включит. - bool _display_inverted = false; - - // флаг типа кондиционера инвертор - true, ON/OFF - false, начальная установка false - // в таком режиме точность и скорость определения реального состояния системы для инвертора, - // будет работать, но будет ниже, переменная устанавливается при первом получении большого пакета; - // если эта переменная установлена, то режим работы не инверторного кондиционера будет распознаваться - // как "в простое" (IDLE) - bool _is_invertor = false; - - // поддерживаемые кондиционером опции - std::set _supported_modes{}; - std::set _supported_swing_modes{}; - std::set _supported_presets{}; - std::set _supported_custom_presets{}; - std::set _supported_custom_fan_modes{}; - - // состояние конечного автомата - acsm_state _ac_state = ACSM_IDLE; - - // текущее состояние задаваемых пользователем параметров системы - ac_state_t _current_ac_state; - - // флаг подключения к UART - bool _hw_initialized = false; - // указатель на UART, по которому общаемся с кондиционером - esphome::uart::UARTComponent *_ac_serial; - - // UART wrappers: peek - int peek() { - uint8_t data; - if (!_ac_serial->peek_byte(&data)) return -1; - return data; - } - - // UART wrappers: read - int read() { - uint8_t data; - if (!_ac_serial->read_byte(&data)) return -1; - return data; - } - - // флаг обмена пакетами с кондиционером (если проходят пинги, значит есть коннект) - bool _has_connection = false; - - // входящий и исходящий пакеты - packet_t _inPacket; - packet_t _outPacket; - - // пакет для тестирования всякой фигни - packet_t _outTestPacket; - - // последовательность пакетов текущий шаг в последовательности - sequence_item_t _sequence[AC_SEQUENCE_MAX_LEN]; - uint8_t _sequence_current_step; - - // флаг успешного выполнения стартовой последовательности команд - bool _startupSequenceComlete = false; - - // очистка последовательности команд - void _clearSequence(){ - for (uint8_t i = 0; i < AC_SEQUENCE_MAX_LEN; i++) { - _sequence[i].item_type = AC_SIT_NONE; - _sequence[i].func = nullptr; - _sequence[i].timeout = 0; - _sequence[i].msec = 0; - _sequence[i].packet_type = AC_SPT_CLEAR; - _clearPacket(&_sequence[i].packet); - _clearCommand(&_sequence[i].cmd); - } - _sequence_current_step = 0; - } - - // проверяет, есть ли свободные шаги в последовательности команд - bool _hasFreeSequenceStep(){ - return (_getNextFreeSequenceStep() < AC_SEQUENCE_MAX_LEN); - } - - // возвращает индекс первого пустого шага последовательности команд - uint8_t _getNextFreeSequenceStep(){ - for (size_t i = 0; i < AC_SEQUENCE_MAX_LEN; i++) { - if (_sequence[i].item_type == AC_SIT_NONE){ - return i; - } - } - // если свободных слотов нет, то возвращаем значение за пределом диапазона - return AC_SEQUENCE_MAX_LEN; - } - - // возвращает количество свободных шагов в последовательности - uint8_t _getFreeSequenceSpace() { - return (AC_SEQUENCE_MAX_LEN - _getNextFreeSequenceStep()); - } - - // добавляет шаг в последовательность команд - // возвращает false, если не нашлось места для шага - bool _addSequenceStep(const sequence_item_type_t item_type, bool (AirCon::*func)() = nullptr, ac_command_t *cmd = nullptr, uint16_t timeout = AC_SEQUENCE_DEFAULT_TIMEOUT){ - if (!_hasFreeSequenceStep()) return false; // если места нет, то уходим - if (item_type == AC_SIT_NONE) return false; // глупость какая-то, уходим - if ((item_type == AC_SIT_FUNC) && (func == nullptr)) return false; // должна быть передана функция для такого типа шага - if ((item_type != AC_SIT_DELAY) && (item_type != AC_SIT_FUNC)){ - // какой-то неизвестный тип - _debugMsg(F("_addSequenceStep: unknown sequence item type = %u"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__, item_type); - return false; - } - - uint8_t step = _getNextFreeSequenceStep(); - - _sequence[step].item_type = item_type; - - // если задержка нулевая, то присваиваем дефолтную задержку - if (timeout == 0) timeout = AC_SEQUENCE_DEFAULT_TIMEOUT; - _sequence[step].timeout = timeout; - - _sequence[step].func = func; - if (cmd != nullptr) _sequence[step].cmd = *cmd; // так как в структуре команды только простые типы, то можно вот так присваивать - - return true; - } - - // добавляет в последовательность шаг с задержкой - bool _addSequenceDelayStep(uint16_t timeout){ - return this->_addSequenceStep(AC_SIT_DELAY, nullptr, nullptr, timeout); - } - - // добавляет в последовательность функциональный шаг - bool _addSequenceFuncStep(bool (AirCon::*func)(), ac_command_t *cmd = nullptr, uint16_t timeout = AC_SEQUENCE_DEFAULT_TIMEOUT){ - return this->_addSequenceStep(AC_SIT_FUNC, func, cmd, timeout); - } - - // выполняет всю логику очередного шага последовательности команд - void _doSequence(){ - if (!hasSequence()) return; - - // если шаг уже максимальный из возможных - if (_sequence_current_step >= AC_SEQUENCE_MAX_LEN) { - // значит последовательность закончилась, надо её очистить - // при очистке последовательности будет и _sequence_current_step обнулён - _debugMsg(F("Sequence [step %u]: maximum step reached"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _clearSequence(); - return; - } - - // смотрим тип текущего элемента в последовательности - switch (_sequence[_sequence_current_step].item_type) { - case AC_SIT_FUNC: { - // если указатель на функцию пустой, то прерываем последовательность - if (_sequence[_sequence_current_step].func == nullptr) { - _debugMsg(F("Sequence [step %u]: function pointer is NULL, sequence broken"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _clearSequence(); - return; - } - - // сохраняем время начала паузы - if (_sequence[_sequence_current_step].msec == 0) { - _sequence[_sequence_current_step].msec = millis(); - _debugMsg(F("Sequence [step %u]: step started"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - } - - // если таймаут не указан, берем значение по-умолчанию - if (_sequence[_sequence_current_step].timeout == 0 ) _sequence[_sequence_current_step].timeout = AC_SEQUENCE_DEFAULT_TIMEOUT; - - // если время вышло, то отчитываемся в лог и очищаем последовательность - if (millis() - _sequence[_sequence_current_step].msec >= _sequence[_sequence_current_step].timeout) { - _debugMsg(F("Sequence [step %u]: step timed out (it took %u ms instead of %u ms)"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step, millis() - _sequence[_sequence_current_step].msec, _sequence[_sequence_current_step].timeout); - _clearSequence(); - return; - } - - // можно вызывать функцию - // она самомтоятельно загружает отправляемые/полученные пакеты в packet последовательности - // а также самостоятельно увеличивает счетчик шагов последовательности _sequence_current_step - // единственное исключение - таймауты - if (!(this->*_sequence[_sequence_current_step].func)()) { - _debugMsg(F("Sequence [step %u]: error was occur in step function"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step, millis() - _sequence[_sequence_current_step].msec); - _clearSequence(); - return; - } - break; - } - - case AC_SIT_DELAY: { // это пауза в последовательности - // пауза задается параметром timeout элемента последовательности - // начало паузы сохраняется в параметре msec - - // сохраняем время начала паузы - if (_sequence[_sequence_current_step].msec == 0) { - _sequence[_sequence_current_step].msec = millis(); - _debugMsg(F("Sequence [step %u]: begin delay (%u ms)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step, _sequence[_sequence_current_step].timeout); - } - - // если время вышло, то переходим на следующий шаг - if (millis() - _sequence[_sequence_current_step].msec >= _sequence[_sequence_current_step].timeout) { - _debugMsg(F("Sequence [step %u]: delay culminated (plan = %u ms, fact = %u ms)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step, _sequence[_sequence_current_step].timeout, millis() - _sequence[_sequence_current_step].msec); - _sequence_current_step++; - } - break; - } - - case AC_SIT_NONE: // шаги закончились - default: // или какой-то мусор в последовательности - // надо очистить последовательность и уходить - _debugMsg(F("Sequence [step %u]: sequence complete"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _clearSequence(); - break; - } - } - - // заполняет структуру команды нейтральными значениями - void _clearCommand(ac_command_t * cmd){ - cmd->clean = AC_CLEAN_UNTOUCHED; - cmd->display = AC_DISPLAY_UNTOUCHED; - cmd->fanMute = AC_FANMUTE_UNTOUCHED; - cmd->fanSpeed = AC_FANSPEED_UNTOUCHED; - cmd->fanTurbo = AC_FANTURBO_UNTOUCHED; - cmd->health = AC_HEALTH_UNTOUCHED; - cmd->health_status = AC_HEALTH_STATUS_UNTOUCHED; - cmd->iFeel = AC_IFEEL_UNTOUCHED; - cmd->louver.louver_h = AC_LOUVERH_UNTOUCHED; - cmd->louver.louver_v = AC_LOUVERV_UNTOUCHED; - cmd->mildew = AC_MILDEW_UNTOUCHED; - cmd->mode = AC_MODE_UNTOUCHED; - cmd->power = AC_POWER_UNTOUCHED; - cmd->sleep = AC_SLEEP_UNTOUCHED; - cmd->timer = AC_TIMER_UNTOUCHED; - cmd->timer_hours = 0; - cmd->timer_minutes = 0; - cmd->temp_target = 0; - cmd->temp_target_matter = false; - cmd->temp_ambient = 0; - cmd->temp_outdoor = 0; - cmd->temp_inbound = 0; - cmd->temp_outbound = 0; - cmd->temp_strange = 0; - cmd->realFanSpeed = AC_REAL_FAN_UNTOUCHED; - }; - - // очистка буфера размером AC_BUFFER_SIZE - void _clearBuffer(uint8_t * buf){ - memset(buf, 0, AC_BUFFER_SIZE); - } - - // очистка структуры пакета по указателю - void _clearPacket(packet_t * pckt){ - if (pckt == nullptr) { - _debugMsg(F("Clear packet error: pointer is NULL!"), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return; - } - pckt->crc = nullptr; - pckt->header = (packet_header_t *)(pckt->data); // заголовок же всегда стартует с начала пакета - pckt->msec = 0; - pckt->bytesLoaded = 0; - pckt->body = nullptr; - _clearBuffer(pckt->data); - } - - // очистка входящего пакета - void _clearInPacket(){ - _clearPacket(&_inPacket); - } - - // очистка исходящего пакета - void _clearOutPacket(){ - _clearPacket(&_outPacket); - _outPacket.header->start_byte = AC_PACKET_START_BYTE; // для исходящего сразу ставим стартовый байт - _outPacket.header->wifi = AC_PACKET_ANSWER; // для исходящего пакета сразу ставим признак ответа - } - - // копирует пакет из одной структуры в другую с корректным переносом указателей на заголовки и т.п. - bool _copyPacket(packet_t *dest, packet_t *src){ - if (dest == nullptr) return false; - if (src == nullptr) return false; - - dest->msec = src->msec; - dest->bytesLoaded = src->bytesLoaded; - memcpy(dest->data, src->data, AC_BUFFER_SIZE); - dest->header = (packet_header_t *)&dest->data; - if (dest->header->body_length > 0) dest->body = &dest->data[AC_HEADER_SIZE]; - dest->crc = (packet_crc_t *)&dest->data[AC_HEADER_SIZE + dest->header->body_length]; - - return true; - } - - // устанавливает состояние конечного автомата - // можно и напрямую устанавливать переменную, но для целей отладки лучше так - void _setStateMachineState(acsm_state state = ACSM_IDLE){ - if (_ac_state == state) return; // состояние не меняется - - _ac_state = state; - - switch (state) { - case ACSM_IDLE: - _debugMsg(F("State changed to ACSM_IDLE."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - case ACSM_RECEIVING_PACKET: - _debugMsg(F("State changed to ACSM_RECEIVING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - case ACSM_PARSING_PACKET: - _debugMsg(F("State changed to ACSM_PARSING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - case ACSM_SENDING_PACKET: - _debugMsg(F("State changed to ACSM_SENDING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - default: - _debugMsg(F("State changed to ACSM_IDLE by default. Given state is %02X."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, state); - _ac_state = ACSM_IDLE; - break; - } - } - - // состояние конечного автомата: IDLE - void _doIdleState(){ - // вначале нужно выполнить очередной шаг последовательности команд - _doSequence(); - - // Если нет входящих данных, значит можно отправить исходящий пакет, если он есть - if (_ac_serial->available() == 0) { - // если есть пакет на отправку, то надо отправлять - // вначале думал, что сейчас отправка пакетов тут не нужна, т.к. состояние ACSM_SENDING_PACKET устанавливается сразу в парсере пакетов - // но потом понял, что у нас пакеты уходят не только когда надо отвечать, но и мы можем быть инициаторами - // поэтому вызов отправки тут пригодится - if (_outPacket.msec > 0) _setStateMachineState(ACSM_SENDING_PACKET); - // иначе просто выходим - return; - }; - - if (this->peek() == AC_PACKET_START_BYTE) { - // если во входящий пакет что-то уже загружено, значит это какие-то ошибочные данные или неизвестные пакеты - // надо эту инфу вывалить в лог - if (_inPacket.bytesLoaded > 0){ - _debugMsg(F("Start byte received but there are some unparsed bytes in the buffer:"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - } - _clearInPacket(); - _inPacket.msec = millis(); - _setStateMachineState(ACSM_RECEIVING_PACKET); - //******************************************** экспериментальная секция ************************************************************* - // пробуем сократить время ответа с помощью прямых вызовов обработчиков, а не через состояние IDLE - //_doReceivingPacketState(); - // получилось всё те же 123 мсек. Только изредка падает до 109 мсек. Странно. - // логический анализатор показал примерно то же время от начала запроса до окончания ответа. - // запрос имеет длительность 18 мсек (лог.анализатор говорит 22,5 мсек). - // ответ имеет длительность 41 мсек по лог.анализатору. - // длительность паузы между запросом и ответом порядка 60 мсек. - // Скорее всего за один вызов _doReceivingPacketState не удается загрузить весь пакет (на момент вызова не все байы поступили в буфер UART) - // и поэтому программа отдает управление ESPHome для выполнения своих задач - // Стоит ли переделать код наоборот для непрерывного выполнения всё время, пока ожидается посылка - не знаю. Может быть такой риалтайм и не нужен. - //*********************************************************************************************************************************** - - } else { - while (_ac_serial->available() > 0) - { - // если наткнулись на старт пакета, то выходим из while - // если какие-то данные были загружены в буфер, то они будут выгружены в лог при загрузке нового пакета - if (this->peek() == AC_PACKET_START_BYTE) break; - - // читаем байт в буфер входящего пакета - _inPacket.data[_inPacket.bytesLoaded] = this->read(); - _inPacket.bytesLoaded++; - - // если буфер уже полон, надо его вывалить в лог и очистить - if (_inPacket.bytesLoaded >= AC_BUFFER_SIZE){ - _debugMsg(F("Some unparsed data on the bus:"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _clearInPacket(); - } - } - } - }; - - // состояние конечного автомата: ACSM_RECEIVING_PACKET - void _doReceivingPacketState(){ - while (_ac_serial-> available() > 0) { - // если в буфере пакета данных уже под завязку, то надо сообщить о проблеме и выйти - if (_inPacket.bytesLoaded >= AC_BUFFER_SIZE) { - _debugMsg(F("Receiver: packet buffer overflow!"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - return; - } - - _inPacket.data[_inPacket.bytesLoaded] = this->read(); - _inPacket.bytesLoaded++; - - // данных достаточно для заголовка - if (_inPacket.bytesLoaded == AC_HEADER_SIZE) { - // указатель заголовка установлен еще при обнулении пакета, его можно не трогать - //_inPacket.header = (packet_header_t *)(_inPacket.data); - - // уже знаем размер пакета и можем установить указатели на тело пакета и CRC - _inPacket.crc = (packet_crc_t *)&(_inPacket.data[AC_HEADER_SIZE + _inPacket.header->body_length]); - if (_inPacket.header->body_length > 0) _inPacket.body = &(_inPacket.data[AC_HEADER_SIZE]); - - _debugMsg(F("Header loaded: timestamp = %010u, start byte = %02X, packet type = %02X, body size = %02X"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.msec, _inPacket.header->start_byte, _inPacket.header->packet_type, _inPacket.header->body_length); - } - - // если все байты пакета загружены, надо его распарсить - // максимальный по размеру пакет будет упираться в размер буфера. если такой пакет здесь не уйдет на парсинг, - // то на следующей итерации будет ошибка о переполнении буфера, которая в начале цикла while - if (_inPacket.bytesLoaded == AC_HEADER_SIZE + _inPacket.header->body_length + 2) { - _debugMsg(F("Packet loaded: timestamp = %010u, start byte = %02X, packet type = %02X, body size = %02X, crc = [%02X, %02X]."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.msec, _inPacket.header->start_byte, _inPacket.header->packet_type, _inPacket.header->body_length, _inPacket.crc->crc[0], _inPacket.crc->crc[1]); - _debugMsg(F("Loaded %02u bytes for a %u ms."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.bytesLoaded, (millis() - _inPacket.msec)); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - _setStateMachineState(ACSM_PARSING_PACKET); - return; - } - } - - // если пакет не загружен, а время вышло, то надо вернуться в IDLE - if (millis() - _inPacket.msec >= AC_PACKET_TIMEOUT) { - _debugMsg(F("Receiver: packet timed out!"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - return; - } - }; - - // состояние конечного автомата: ACSM_PARSING_PACKET - void _doParsingPacket(){ - if (!_checkCRC(&_inPacket)) { - _debugMsg(F("Parser: packet CRC fail!"), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_ERROR, __LINE__); - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - return; - } - - bool stateChangedFlag = false; // флаг, показывающий, изменилось ли состояние кондиционера - uint8_t stateByte = 0; // переменная для временного сохранения текущих параметров сплита для проверки их изменения - float stateFloat = 0.0; // переменная для временного сохранения текущих параметров сплита для проверки их изменения - - // вначале выводим полученный пакет в лог, чтобы он шел до информации об ответах и т.п. - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - - // разбираем тип пакета - switch (_inPacket.header->packet_type) { - case AC_PTYPE_PING: { // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает - - if (_inPacket.header->body_length != 0 ) { // у входящего ping-пакета тело должно отсутствовать - // если тело есть, то жалуемся в лог - _debugMsg(F("Parser: ping packet should not to have body. Received one has body length %02X."), ESPHOME_LOG_LEVEL_WARN, __LINE__, _inPacket.header->body_length); - // очищаем пакет - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - break; - } - - _debugMsg(F("Parser: ping packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - // поднимаем флаг, что есть коннект с кондиционером - _has_connection = true; - - // надо отправлять ответ на пинг - _clearOutPacket(); - _outPacket.msec = millis(); - _outPacket.header->packet_type = AC_PTYPE_PING; - _outPacket.header->ping_answer_01 = 0x01; // только в ответе на пинг этот байт равен 0x01; что означает не ясно - _outPacket.header->body_length = 8; // в ответе на пинг у нас тело 8 байт - _outPacket.body = &(_outPacket.data[AC_HEADER_SIZE]); - - // заполняем тело пакета - packet_ping_answer_body_t * ping_body; - ping_body = (packet_ping_answer_body_t *) (_outPacket.body); - ping_body->byte_1C = 0x1C; - ping_body->byte_27 = 0x27; - - // расчет контрольной суммы и прописывание её в пакет - _outPacket.crc = (packet_crc_t *)&(_outPacket.data[AC_HEADER_SIZE + _outPacket.header->body_length]); - _setCRC16(&_outPacket); - _outPacket.bytesLoaded = AC_HEADER_SIZE + _outPacket.header->body_length + 2; - - _debugMsg(F("Parser: generated ping answer. Waiting for sending."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // до отправки пинг-ответа проверяем, не выполнялась ли стартовая последовательность команд - // по задумке она выполняется после подключения к кондиционеру после ответа на первый пинг - // нужна для максимально быстрого определния текущих параметров кондиционера - if (!_startupSequenceComlete){ - _startupSequenceComlete = startupSequence(); - } - - // изначально предполагал, что передачу пакета на отправку выполнит обработчик IDLE, но показалось, что слишком долго - // логика отправки через IDLE в том, что получение запросов может быть важнее отправки ответов и IDLE позволяет реализовать такой приоритет - // но потом решил всё же напрямую отправлять в отправку - // в этом случае пинг-ответ заканчивает отправку спустя 144 мсек после стартового байта пинг-запроса - //_setStateMachineState(ACSM_IDLE); - _setStateMachineState(ACSM_SENDING_PACKET); - // решил провести эксперимент - // получилось от начала запроса до отправки ответа порядка 165 мсек., если отправка идет не сразу, а через состояние IDLE - // Если сразу отсюда отправляться в обработчик отправки, то время сокращается до 131 мсек. Основные потери идут до входа в парсер пакетов - break; - } - - case AC_PTYPE_CMD: { // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита - // сплит такие команды отправлять не должен, поэтому жалуемся в лог - _debugMsg(F("Parser: packet type=0x06 received from HVAC. This isn't expected."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - // очищаем пакет - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - break; - } - - case AC_PTYPE_INFO: { // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля - // смотрим тип поступившего пакета по второму байту тела - _debugMsg(F("Parser: status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - switch (_inPacket.body[1]) { - case AC_CMD_STATUS_SMALL: { // маленький пакет статуса кондиционера - _debugMsg(F("Parser: status packet type = small"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - stateChangedFlag = false; - - // будем обращаться к телу пакета через указатель на структуру - packet_small_info_body_t * small_info_body; - small_info_body = (packet_small_info_body_t *) (_inPacket.body); - - // в малом пакете передается большое количество установленных пользователем параметров работы - stateFloat = 8 + (small_info_body->target_temp_int_and_v_louver >> 3) + 0.5*(float)(small_info_body->target_temp_frac >> 7); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_target != stateFloat); - _current_ac_state.temp_target = stateFloat; - _current_ac_state.temp_target_matter = true; - - stateByte = small_info_body->target_temp_int_and_v_louver & AC_LOUVERV_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.louver.louver_v != (ac_louver_V)stateByte); - _current_ac_state.louver.louver_v = (ac_louver_V)stateByte; - - stateByte = small_info_body->h_louver & AC_LOUVERH_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.louver.louver_h != (ac_louver_H)stateByte); - _current_ac_state.louver.louver_h = (ac_louver_H)stateByte; - - stateByte = small_info_body->fan_speed & AC_FANSPEED_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.fanSpeed != (ac_fanspeed)stateByte); - _current_ac_state.fanSpeed = (ac_fanspeed)stateByte; - - stateByte = small_info_body->fan_turbo_and_mute & AC_FANTURBO_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.fanTurbo != (ac_fanturbo)stateByte); - _current_ac_state.fanTurbo = (ac_fanturbo)stateByte; - - stateByte = small_info_body->fan_turbo_and_mute & AC_FANMUTE_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.fanMute != (ac_fanmute)stateByte); - _current_ac_state.fanMute = (ac_fanmute)stateByte; - - stateByte = small_info_body->mode & AC_MODE_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.mode != (ac_mode)stateByte); - _current_ac_state.mode = (ac_mode)stateByte; - - stateByte = small_info_body->mode & AC_SLEEP_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.sleep != (ac_sleep)stateByte); - _current_ac_state.sleep = (ac_sleep)stateByte; - - stateByte = small_info_body->mode & AC_IFEEL_MASK; - _current_ac_state.iFeel = (ac_ifeel)stateByte; - - stateByte = small_info_body->status & AC_POWER_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.power != (ac_power)stateByte); - _current_ac_state.power = (ac_power)stateByte; - - stateByte = small_info_body->status & AC_HEALTH_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.health != (ac_health)stateByte); - _current_ac_state.health = (ac_health)stateByte; - - stateByte = small_info_body->status & AC_HEALTH_STATUS_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.health_status != (ac_health_status)stateByte); - _current_ac_state.health_status = (ac_health_status)stateByte; - - stateByte = small_info_body->status & AC_CLEAN_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.clean != (ac_clean)stateByte); - _current_ac_state.clean = (ac_clean)stateByte; - - stateByte = small_info_body->display_and_mildew & AC_DISPLAY_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.display != (ac_display)stateByte); - _current_ac_state.display = (ac_display)stateByte; - - stateByte = small_info_body->display_and_mildew & AC_MILDEW_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.mildew != (ac_mildew)stateByte); - _current_ac_state.mildew = (ac_mildew)stateByte; - - // уведомляем об изменении статуса сплита - if (stateChangedFlag) stateChanged(); - - break; - } - - case AC_CMD_STATUS_BIG: // большой пакет статуса кондиционера - case AC_CMD_STATUS_PERIODIC: { // раз в 10 минут рассылается сплитом, структура аналогична большому пакету статуса - // TODO: вроде как AC_CMD_STATUS_PERIODIC могут быть и с другими кодами; пока что другие будут игнорироваться; если это будет критично, надо будет поправить - _debugMsg(F("Parser: status packet type = big or periodic"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - stateChangedFlag = false; - - // будем обращаться к телу пакета через указатель на структуру - packet_big_info_body_t * big_info_body; - big_info_body = (packet_big_info_body_t *) (_inPacket.body); - - // тип кондея (инвертор или старт стоп) - _is_invertor = big_info_body->is_invertor; - - // температура воздуха в помещении по версии сплит-системы - stateFloat = big_info_body->ambient_temperature_int - 0x20 + (float)(big_info_body->ambient_temperature_frac & 0x0f) / 10.0; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_ambient != stateFloat); - _current_ac_state.temp_ambient = stateFloat; - - // некая температура из наружного блока, скорее всего температура испарителя - // temp = big_info_body->outdoor_temperature - 0x20; - // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах - { - const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; - const float antkoef = 1.0 - koef; - static float temp = _current_ac_state.temp_outdoor; - temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); - _current_ac_state.temp_outdoor = temp; - } - - // температура входящей магистрали - stateFloat = big_info_body->in_temperature_int - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_inbound != stateFloat); - _current_ac_state.temp_inbound = stateFloat; - - // температура исходящей магистрали - stateFloat = big_info_body->out_temperature_int - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outbound != stateFloat); - _current_ac_state.temp_outbound = stateFloat; - - // температура непонятная температура - stateFloat = big_info_body->strange_temperature_int - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_strange != stateFloat); - _current_ac_state.temp_strange = stateFloat; - - // реальная скорость проперлера - stateFloat = big_info_body->realFanSpeed; - stateChangedFlag = stateChangedFlag || (_current_ac_state.realFanSpeed != (ac_realFan)stateFloat); - _current_ac_state.realFanSpeed = (ac_realFan)stateFloat; - - // мощность инвертора - stateFloat = big_info_body->invertor_power; - stateChangedFlag = stateChangedFlag || (_current_ac_state.invertor_power != stateFloat); - _current_ac_state.invertor_power = stateFloat; - - // режим разморозки - bool temp = (big_info_body->needDefrost && big_info_body->defrostMode); - stateChangedFlag = stateChangedFlag || (_current_ac_state.defrost != temp); - _current_ac_state.defrost = temp; - - // уведомляем об изменении статуса сплита - if (stateChangedFlag) { - stateChanged(); - } - break; - } - - case AC_CMD_SET_PARAMS: { // такой статусный пакет присылается кондиционером в ответ на команду установки параметров - // в теле пакета нет ничего примечательного - // в байтах 2 и 3 тела похоже передается CRC пакета поступившей команды, на которую сплит отвечает - // но я решил этот момент тут не проверять и не контролировать. - // корректную установку параметров можно определить, запросив статус кондиционера сразу после получения этой команды кондея - // в настоящий момент проверка сделана в механизме sequences - // TODO: если доводить до идеала, то проверку байтов 2 и 3 можно сделать и тут - break; - } - - default: - _debugMsg(F("Parser: status packet type = unknown (%02X)"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _inPacket.body[1]); - break; - } - _setStateMachineState(ACSM_IDLE); - break; - } - - case AC_PTYPE_INIT: // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. - case AC_PTYPE_UNKN: // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует - default: - // игнорируем. Для нашего случая эти пакеты не важны - _setStateMachineState(ACSM_IDLE); - break; - } - - // если есть последовательность команд, то надо отработать проверку последовательности - if (hasSequence()) _doSequence(); - - // после разбора входящего пакета его надо очистить - _clearInPacket(); - } - - // состояние конечного автомата: ACSM_SENDING_PACKET - void _doSendingPacketState(){ - // если нет исходящего пакета, то выходим - if ((_outPacket.msec == 0) || (_outPacket.crc == nullptr) || (_outPacket.bytesLoaded == 0)) { - _debugMsg(F("Sender: no packet to send."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - _setStateMachineState(ACSM_IDLE); - return; - } - - _debugMsg(F("Sender: sending packet."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - _ac_serial->write_array(_outPacket.data, _outPacket.bytesLoaded); - _ac_serial->flush(); - - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _debugMsg(F("Sender: %u bytes sent (%u ms)."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _outPacket.bytesLoaded, millis()-_outPacket.msec); - _clearOutPacket(); - - _setStateMachineState(ACSM_IDLE); - }; - - /** вывод отладочной информации в лог - * - * dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе. - * msg - сообщение, выводимое в лог - * line - строка, на которой произошел вызов (удобно при отладке) - */ - void _debugMsg(const String &msg, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0, ... ){ - if (dbgLevel < ESPHOME_LOG_LEVEL_NONE) dbgLevel = ESPHOME_LOG_LEVEL_NONE; - if (dbgLevel > ESPHOME_LOG_LEVEL_VERY_VERBOSE) dbgLevel = ESPHOME_LOG_LEVEL_VERY_VERBOSE; - - if (line == 0) line = __LINE__; // если строка не передана, берем текущую строку - - va_list vl; - va_start(vl, line); - esp_log_vprintf_(dbgLevel, Constants::TAG, line, msg.c_str(), vl); // так тоже вроде через Ж*, только ее не видно :) - va_end(vl); - } - - /** выводим данные пакета в лог для отладки - * - * dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе. - * packet - указатель на пакет для вывода; - * если указатель на crc равен nullptr или первый байт в буфере не AC_PACKET_START_BYTE, то считаем, что передан битый пакет - * или не пакет вовсе. Для такого выводим только массив байт. - * Для нормального пакета данные выводятся с форматированием. - * line - строка, на которой произошел вызов (удобно при отладке) - **/ - void _debugPrintPacket(packet_t * packet, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0){ - // определяем, полноценный ли пакет нам передан - bool notAPacket = false; - // указатель заголовка всегда установден на начало буфера - notAPacket = notAPacket || (packet->crc == nullptr); - notAPacket = notAPacket || (packet->data[0] != AC_PACKET_START_BYTE); - - String st = ""; - char textBuf[11]; - - // заполняем время получения пакета - memset(textBuf, 0, 11); - sprintf(textBuf, "%010u", packet->msec); - st = st + textBuf + ": "; - - // формируем преамбулы - if (packet == &_inPacket) { - st += "[<=] "; // преамбула входящего пакета - } else if (packet == &_outPacket) { - st += "[=>] "; // преамбула исходящего пакета - } else { - st += "[--] "; // преамбула для "непакета" - } - - // формируем данные - #ifdef HOLMS - dbgLevel = ESPHOME_LOG_LEVEL_ERROR; - if(packet->header->body_length > HOLMS){ - for (int i=0; ibytesLoaded; i++){ - sprintf(textBuf, "%03d;", packet->data[i]); - st += textBuf; - } - if (line == 0) line = __LINE__; - _debugMsg(st, dbgLevel, line); - } - #else - for (int i=0; ibytesLoaded; i++){ - // для нормальных пакетов надо заключить заголовок в [] - if ((!notAPacket) && (i == 0)) st += "["; - // для нормальных пакетов надо заключить CRC в [] - if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE)) st += "["; - - //memset(textBuf, 0, 11); - sprintf(textBuf, "%02X", packet->data[i]); - st += textBuf; - - // для нормальных пакетов надо заключить заголовок в [] - if ((!notAPacket) && (i == AC_HEADER_SIZE-1)) st += "]"; - // для нормальных пакетов надо заключить CRC в [] - if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE+2-1)) st += "]"; - st += " "; - } - if (line == 0) line = __LINE__; - _debugMsg(st, dbgLevel, line); - #endif - - } - - /** расчет CRC16 для блока данных data длиной len - * data - данные для расчета CRC16, указатель на массив байт - * len - длина блока данных для расчета, в байтах - * - * возвращаем uint16_t CRC16 - **/ - uint16_t _CRC16(uint8_t *data, uint8_t len){ - uint32_t crc = 0; - - // выделяем буфер для расчета CRC и копируем в него переданные данные - // это нужно для того, чтобы в случае нечетной длины данных можно было дополнить тело пакета - // одним нулевым байтом и не попортить загруженный пакет (ведь в загруженном сразу за телом идёт CRC) - uint8_t _crcBuffer[AC_BUFFER_SIZE]; - memset(_crcBuffer, 0, AC_BUFFER_SIZE); - memcpy(_crcBuffer, data, len); - - // если длина данных нечетная, то надо сделать четной, дополнив данные в конце нулевым байтом - // но так как выше буфер заполняли нулями, то отдельно тут присваивать 0x00 нет смысла - if ((len%2) == 1) len++; - - // рассчитываем CRC16 - uint32_t word = 0; - for (uint8_t i=0; i < len; i+=2){ - word = (_crcBuffer[i] << 8) + _crcBuffer[i+1]; - crc += word; - } - crc = (crc >> 16) + (crc & 0xFFFF); - crc = ~ crc; - - return crc & 0xFFFF; - } - - // расчитываем CRC16 и заполняем эти данные в структуре пакета - void _setCRC16(packet_t* pack = nullptr){ - // если пакет не указан, то устанавливаем CRC для исходящего пакета - if (pack == nullptr) pack = &_outPacket; - - packet_crc_t crc; - crc.crc16 = _CRC16(pack->data, AC_HEADER_SIZE + pack->header->body_length); - - // если забыли указатель на crc установить, то устанавливаем - if (pack->crc == nullptr) pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - - pack->crc->crc[0] = crc.crc[1]; - pack->crc->crc[1] = crc.crc[0]; - return; - } - - // проверяет CRC пакета по указателю - bool _checkCRC(packet_t* pack = nullptr){ - // если пакет не указан, то проверяем входящий - if (pack == nullptr) pack = &_inPacket; - if (pack->bytesLoaded < AC_HEADER_SIZE) { - _debugMsg(F("CRC check: incoming packet size error."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - // если забыли указатель на crc установить, то устанавливаем - if (pack->crc == nullptr) pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - - packet_crc_t crc; - crc.crc16 = _CRC16(pack->data, AC_HEADER_SIZE + pack->header->body_length); - - return ((pack->crc->crc[0] == crc.crc[1]) && (pack->crc->crc[1] == crc.crc[0])); - } - - // заполняет пакет по ссылке командой запроса маленького пакета статуса - void _fillStatusSmall(packet_t * pack = nullptr){ - // по умолчанию заполняем исходящий пакет - if (pack == nullptr) pack = &_outPacket; - - // присваиваем параметры пакета - pack->msec = millis(); - pack->header->start_byte = AC_PACKET_START_BYTE; - pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа - pack->header->packet_type = AC_PTYPE_CMD; - pack->header->body_length = 2; // тело команды 2 байта - pack->body = &(pack->data[AC_HEADER_SIZE]); - pack->body[0] = AC_CMD_STATUS_SMALL; - pack->body[1] = 0x01; // он всегда 0x01 - pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; - - // рассчитываем и записываем в пакет CRC - pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - _setCRC16(pack); - } - - // заполняет пакет по ссылке командой запроса большого пакета статуса - void _fillStatusBig(packet_t * pack = nullptr){ - // по умолчанию заполняем исходящий пакет - if (pack == nullptr) pack = &_outPacket; - - // присваиваем параметры пакета - pack->msec = millis(); - pack->header->start_byte = AC_PACKET_START_BYTE; - pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа - pack->header->packet_type = AC_PTYPE_CMD; - pack->header->body_length = 2; // тело команды 2 байта - pack->body = &(pack->data[AC_HEADER_SIZE]); - pack->body[0] = AC_CMD_STATUS_BIG; - pack->body[1] = 0x01; // он всегда 0x01 - pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; - - // рассчитываем и записываем в пакет CRC - pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - _setCRC16(pack); - } - - /** заполняет пакет по ссылке командой установки параметров - * - * указатель на пакет может отсутствовать, тогда заполняется _outPacket - * указатель на команду также может отсутствовать, тогда используется текущее состояние из _current_ac_state - * все *__UNTOUCHED параметры заполняются из _current_ac_state - **/ - void _fillSetCommand(bool clrPacket = false, packet_t * pack = nullptr, ac_state_t *cmd = nullptr){ - // по умолчанию заполняем исходящий пакет - if (pack == nullptr) pack = &_outPacket; - - // очищаем пакет, если это указано - if (clrPacket) _clearPacket(pack); - - // заполняем его параметрами из _current_ac_state - if (cmd != &_current_ac_state) { - _fillSetCommand(false, pack, &_current_ac_state); - } - - // если команда не указана, значит выходим - if (cmd == nullptr) return; - - // команда указана, дополнительно внесем в пакет те параметры, которые установлены в команде - - // присваиваем параметры пакета - pack->msec = millis(); - pack->header->start_byte = AC_PACKET_START_BYTE; - pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа - pack->header->packet_type = AC_PTYPE_CMD; - pack->header->body_length = 15; // тело команды 15 байт, как у Small status - pack->body = &(pack->data[AC_HEADER_SIZE]); - pack->body[0] = AC_CMD_SET_PARAMS; // устанавливаем параметры - pack->body[1] = 0x01; // он всегда 0x01 - pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; - - // целевая температура кондиционера - if (cmd->temp_target_matter){ - // устраняем выход за границы диапазона (это ограничение самого кондиционера) - if (cmd->temp_target < Constants::AC_MIN_TEMPERATURE) cmd->temp_target = Constants::AC_MIN_TEMPERATURE; - if (cmd->temp_target > Constants::AC_MAX_TEMPERATURE) cmd->temp_target = Constants::AC_MAX_TEMPERATURE; - - // целая часть температуры - pack->body[2] = (pack->body[2] & ~AC_TEMP_TARGET_INT_PART_MASK) | (((uint8_t)(cmd->temp_target) - 8) << 3); - - // дробная часть температуры - if (cmd->temp_target - (uint8_t)(cmd->temp_target) > 0) { - pack->body[4] = (pack->body[4] & ~AC_TEMP_TARGET_FRAC_PART_MASK) | 1; - } else { - pack->body[4] = (pack->body[4] & ~AC_TEMP_TARGET_FRAC_PART_MASK) | 0; - } - } - - // обнулить счетчик минут с последней команды - pack->body[4] &= ~ AC_MIN_COUTER ; - - // вертикальные жалюзи - if (cmd->louver.louver_v != AC_LOUVERV_UNTOUCHED){ - pack->body[2] = (pack->body[2] & ~AC_LOUVERV_MASK) | cmd->louver.louver_v; - } - - // горизонтальные жалюзи - if (cmd->louver.louver_h != AC_LOUVERH_UNTOUCHED){ - pack->body[3] = (pack->body[3] & ~AC_LOUVERH_MASK) | cmd->louver.louver_h; - } - - // скорость вентилятора - if (cmd->fanSpeed != AC_FANSPEED_UNTOUCHED){ - pack->body[5] = (pack->body[5] & ~AC_FANSPEED_MASK) | cmd->fanSpeed; - } - - // спец.режимы вентилятора: TURBO - if (cmd->fanTurbo != AC_FANTURBO_UNTOUCHED){ - pack->body[6] = (pack->body[6] & ~AC_FANTURBO_MASK) | cmd->fanTurbo; - } - - // спец.режимы вентилятора: MUTE - if (cmd->fanMute != AC_FANMUTE_UNTOUCHED){ - pack->body[6] = (pack->body[6] & ~AC_FANMUTE_MASK) | cmd->fanMute; - } - - // режим кондея - if (cmd->mode != AC_MODE_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_MODE_MASK) | cmd->mode; - } - if (cmd->sleep != AC_SLEEP_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_SLEEP_MASK) | cmd->sleep; - } - if (cmd->iFeel != AC_IFEEL_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_IFEEL_MASK) | cmd->iFeel; - } - - // питание вкл/выкл - if (cmd->power != AC_POWER_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_POWER_MASK) | cmd->power; - } - if (cmd->clean != AC_CLEAN_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_CLEAN_MASK) | cmd->clean; - } - // ионизатор - if (cmd->health != AC_HEALTH_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_HEALTH_MASK) | cmd->health; - } - - // какой то флаг ионизатора - if (cmd->health_status != AC_HEALTH_STATUS_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_HEALTH_STATUS_MASK) | cmd->health_status; - } - - // дисплей - if (cmd->display != AC_DISPLAY_UNTOUCHED){ - pack->body[12] = (pack->body[12] & ~AC_DISPLAY_MASK) | cmd->display; - } - - // антиплесень - if (cmd->mildew != AC_MILDEW_UNTOUCHED){ - pack->body[12] = (pack->body[12] & ~AC_MILDEW_MASK) | cmd->mildew; - } - - - - // рассчитываем и записываем в пакет CRC - pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - _setCRC16(pack); - } - - // отправка запроса на маленький статусный пакет - bool sq_requestSmallStatus(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _fillStatusSmall(&_outPacket); - _fillStatusSmall(&_sequence[_sequence_current_step].packet); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: small status request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // проверка ответа на запрос маленького статусного пакета - bool sq_controlSmallStatus(){ - // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим - if (_inPacket.bytesLoaded == 0) return true; - - // Пинги игнорируем - if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; - - // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать - _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; - - // проверяем ответ - bool relevant = true; - relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x0F)); - relevant = (relevant && (_inPacket.body[0] == 0x01)); - relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_SMALL)); - - // если пакет подходит, значит можно переходить к следующему шагу - if (relevant) { - _debugMsg(F("Sequence [step %u]: correct small status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _sequence_current_step++; - } else { - // если пакет не подходящий, то отчитываемся в лог... - _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_SMALL); - // ...и прерываем последовательность, так как вернем false - } - return relevant; - } - - // отправка запроса на большой статусный пакет - bool sq_requestBigStatus(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _fillStatusBig(&_outPacket); - _fillStatusBig(&_sequence[_sequence_current_step].packet); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: big status request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // проверка ответа на запрос большого статусного пакета - bool sq_controlBigStatus(){ - // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим - if (_inPacket.bytesLoaded == 0) return true; - - // Пинги игнорируем - if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; - - // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать - _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; - - // проверяем ответ - bool relevant = true; - relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x18 || _inPacket.header->body_length == 0x19)); // канальник Royal Clima отвечает пакетом длиной 0x19 - relevant = (relevant && (_inPacket.body[0] == 0x01)); - relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_BIG)); - - // если пакет подходит, значит можно переходить к следующему шагу - if (relevant) { - _debugMsg(F("Sequence [step %u]: correct big status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _sequence_current_step++; - } else { - // если пакет не подходящий, то отчитываемся в лог... - _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_BIG); - // ...и прерываем последовательность - } - return relevant; - } - - // отправка запроса на выполнение команды - bool sq_requestDoCommand(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _fillSetCommand(true, &_outPacket, &_sequence[_sequence_current_step].cmd); - _fillSetCommand(true, &_sequence[_sequence_current_step].packet, &_sequence[_sequence_current_step].cmd); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: doCommand request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // проверка ответа на выполнение команды - bool sq_controlDoCommand(){ - // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим - if (_inPacket.bytesLoaded == 0) return true; - - // Пинги игнорируем - if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; - - // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать - _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; - - // проверяем ответ - bool relevant = true; - relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x04)); - relevant = (relevant && (_inPacket.body[0] == 0x01)); - relevant = (relevant && (_inPacket.body[1] == AC_CMD_SET_PARAMS)); - // байты 2 и 3 обычно равны CRC отправленного пакета с командой - relevant = (relevant && (_inPacket.body[2] == _sequence[_sequence_current_step-1].packet.crc->crc[0])); - relevant = (relevant && (_inPacket.body[3] == _sequence[_sequence_current_step-1].packet.crc->crc[1])); - - // если пакет подходит, значит можно переходить к следующему шагу - if (relevant) { - _debugMsg(F("Sequence [step %u]: correct doCommand packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _sequence_current_step++; - } else { - // если пакет не подходящий, то отчитываемся в лог... - _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_BIG); - // ...и прерываем последовательность - } - return relevant; - } - - // отправка запроса с тестовым пакетом - bool sq_requestTestPacket(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _copyPacket(&_outPacket, &_outTestPacket); - _copyPacket(&_sequence[_sequence_current_step].packet, &_outTestPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: Test Packet request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // сенсоры, отображающие параметры сплита - esphome::sensor::Sensor *sensor_indoor_temperature_ = nullptr; - esphome::sensor::Sensor *sensor_outdoor_temperature_ = nullptr; - esphome::sensor::Sensor *sensor_inbound_temperature_ =nullptr; - esphome::sensor::Sensor *sensor_outbound_temperature_ =nullptr; - esphome::sensor::Sensor *sensor_strange_temperature_ =nullptr; - // текущая мощность компрессора - esphome::sensor::Sensor *sensor_invertor_power_ = nullptr; - // бинарный сенсор, отображающий состояние дисплея - esphome::binary_sensor::BinarySensor *sensor_display_ = nullptr; - // бинарный сенсор состония разморозки - esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; - - // загружает на выполнение последовательность команд на включение/выключение табло с температурой - bool _displaySequence(ac_display dsp = AC_DISPLAY_ON){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("displaySequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - if (dsp == AC_DISPLAY_UNTOUCHED) return false; // выходим, чтобы не тратить время - - // формируем команду - ac_command_t cmd; - _clearCommand(&cmd); // не забываем очищать, а то будет мусор - cmd.display = dsp; - // добавляем команду в последовательность - if (!commandSequence(&cmd)) return false; - - _debugMsg(F("displaySequence: loaded (display = %02X)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, dsp); - return true; - } - - // номер глобального пресета от режима работы - uint8_t get_num_preset(ac_command_t* cmd){ - if(cmd->power == AC_POWER_OFF){ - return POS_MODE_OFF; - } else if(cmd->mode == AC_MODE_AUTO){ - return POS_MODE_AUTO; - } else if(cmd->mode == AC_MODE_COOL){ - return POS_MODE_COOL; - } else if(cmd->mode == AC_MODE_DRY){ - return POS_MODE_DRY; - } else if(cmd->mode == AC_MODE_FAN){ - return POS_MODE_FAN; - } else if(cmd->mode == AC_MODE_HEAT){ - return POS_MODE_HEAT; - } - cmd->power = AC_POWER_OFF; - return POS_MODE_OFF; - } - - // восстановление данных из пресета - void load_preset(ac_command_t* cmd, uint8_t num_preset){ - if(num_preset < sizeof(global_presets)/sizeof(global_presets[0])){ // проверка выхода за пределы массива - if(cmd->power == global_presets[num_preset].power && cmd->mode == global_presets[num_preset].mode){ //контроль инициализации - memcpy(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE); // просто копируем из массива - _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } else { - _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } - } - } - - // запись данных в массив персетов - void save_preset(ac_command_t* cmd){ - uint8_t num_preset = get_num_preset(cmd); - if(memcmp(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE) != 0){ // содержимое пресетов разное - memcpy(&(global_presets[num_preset]), cmd, AC_COMMAND_BASE_SIZE); // копируем пресет в массив - #if defined(ESP32) - _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - if(storage.save(global_presets)){ - if(!global_preferences->sync()) // сохраняем все пресеты - _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } else { - _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } - #endif - } else { - _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } - } - - public: - // инициализация объекта - void initAC(esphome::uart::UARTComponent *parent = nullptr){ - _dataMillis = millis(); - _clearInPacket(); - _clearOutPacket(); - - _clearPacket(&_outTestPacket); - _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - _outTestPacket.header->wifi = AC_PACKET_ANSWER; - - _setStateMachineState(ACSM_IDLE); - _ac_serial = parent; - _hw_initialized = (_ac_serial != nullptr); - _has_connection = false; - - // заполняем структуру состояния начальными значениями - _clearCommand((ac_command_t *)&_current_ac_state); - - // очищаем последовательность пакетов - _clearSequence(); - - // выполнена ли уже стартовая последовательность команд (сбор информации о статусе кондея) - _startupSequenceComlete = false; - }; - - float get_setup_priority() const override { return esphome::setup_priority::DATA; } - - void set_indoor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_indoor_temperature_ = temperature_sensor; } - void set_outdoor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_outdoor_temperature_ = temperature_sensor; } - void set_inbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_inbound_temperature_ = temperature_sensor; } - void set_outbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_outbound_temperature_ = temperature_sensor; } - void set_strange_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_strange_temperature_ = temperature_sensor; } - void set_defrost_state(binary_sensor::BinarySensor *defrost_state) { sensor_defrost_ = defrost_state; } - void set_display_sensor(binary_sensor::BinarySensor *display_sensor) { sensor_display_ = display_sensor; } - void set_invertor_power_sensor(sensor::Sensor *invertor_power_sensor) { sensor_invertor_power_ = invertor_power_sensor; } - bool get_hw_initialized(){ return _hw_initialized; }; - bool get_has_connection(){ return _has_connection; }; - - // возвращает, есть ли елементы в последовательности команд - bool hasSequence(){ - return (_sequence[0].item_type != AC_SIT_NONE); - } - - // вызывается для обновления отображения состояния кондиционера, ДЛЯ ПУБЛИКАЦИИ - void stateChanged(){ - _debugMsg(F("State changed, let's publish it."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - if(_is_invertor){ // анализ режима для инвертора, точнее потому что использует показания мощности инвертора - static uint32_t timerInv = 0; - if(_current_ac_state.invertor_power == 0){ // инвертор выключен - timerInv = millis(); - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && - _current_ac_state.power == AC_POWER_OFF ){ // внутренний кулер остановлен, кондей выключен - this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает - } else { - int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; - if (delta_temp > 0 && delta_temp < 2 && - (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else if (_current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_OFF ){ // кулер чуть вертится - this->action = climate::CLIMATE_ACTION_IDLE; // кондей в простое - } else { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция - } - } - } else if(millis()-timerInv > 2000){ // инвертор включен, но нужно дождаться реакции на его включение - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE ){ //медленное вращение - if(_current_ac_state.temp_ambient - _current_ac_state.temp_inbound > 0){ //холодный радиатор - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else { // теплый радиатор, видимо переходный режим - this->action = climate::CLIMATE_ACTION_IDLE; - } - } else { - int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; - if(delta_temp < -2){ // входящая температура выше комнатной, быстрый фен - ОБОГРЕВ - this->action = climate::CLIMATE_ACTION_HEATING; - } else if(delta_temp > 2){ // ниже, быстрый фен - ОХЛАЖДЕНИЕ - this->action = climate::CLIMATE_ACTION_COOLING; - } else { // просто вентиляция - this->action = climate::CLIMATE_ACTION_IDLE; - } - } - } else { - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE){ - this->action = climate::CLIMATE_ACTION_IDLE; - } else { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция - } - } - } else { - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && - _current_ac_state.power == AC_POWER_OFF){ - this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает - } else { - int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; // разность температуры между комнатной и входящей - if (delta_temp > 0 && delta_temp < 2 && - (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else if(_current_ac_state.realFanSpeed != AC_REAL_FAN_OFF && - _current_ac_state.realFanSpeed != AC_REAL_FAN_MUTE){ - if(delta_temp > 2){ - this->action = climate::CLIMATE_ACTION_COOLING; - } else if(delta_temp < -2){ - this->action = climate::CLIMATE_ACTION_HEATING; - } else { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция - } - } else { - this->action = climate::CLIMATE_ACTION_IDLE; - } - } - } - _debugMsg(F("Action mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->action); - /*************************** POWER & MODE ***************************/ - if (_current_ac_state.power == AC_POWER_ON){ - switch (_current_ac_state.mode) { - case AC_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_HEAT_COOL; // по факту режим, названный в AUX как AUTO, является режимом HEAT_COOL - break; - - case AC_MODE_COOL: - this->mode = climate::CLIMATE_MODE_COOL; - break; - - case AC_MODE_DRY: - this->mode = climate::CLIMATE_MODE_DRY; - break; - - case AC_MODE_HEAT: - this->mode = climate::CLIMATE_MODE_HEAT; - break; - - case AC_MODE_FAN: - this->mode = climate::CLIMATE_MODE_FAN_ONLY; - break; - - default: - _debugMsg(F("Warning: unknown air conditioner mode."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - break; - } - } else { - this->mode = climate::CLIMATE_MODE_OFF; - } - - _debugMsg(F("Climate mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->mode); - - /*************************** FAN SPEED ***************************/ - if(_current_ac_state.power == AC_POWER_ON){ - this->fan_mode = climate::CLIMATE_FAN_OFF; - switch (_current_ac_state.fanSpeed) { - case AC_FANSPEED_HIGH: - this->fan_mode = climate::CLIMATE_FAN_HIGH; - this->custom_fan_mode = (std::string)""; - break; - - case AC_FANSPEED_MEDIUM: - this->fan_mode = climate::CLIMATE_FAN_MEDIUM; - this->custom_fan_mode = (std::string)""; - break; - - case AC_FANSPEED_LOW: - this->fan_mode = climate::CLIMATE_FAN_LOW; - break; - - case AC_FANSPEED_AUTO: - this->fan_mode = climate::CLIMATE_FAN_AUTO; - break; - - default: - break; - } - /*************************** TURBO FAN MODE ***************************/ - switch (_current_ac_state.fanTurbo) { - case AC_FANTURBO_ON: - this->custom_fan_mode = Constants::TURBO; - break; - case AC_FANTURBO_OFF: - default: - if (this->custom_fan_mode == Constants::TURBO) this->custom_fan_mode = (std::string)""; - break; - } - /*************************** MUTE FAN MODE ***************************/ - switch (_current_ac_state.fanMute) { - case AC_FANMUTE_ON: - this->custom_fan_mode = Constants::MUTE; - break; - case AC_FANMUTE_OFF: - default: - if (this->custom_fan_mode == Constants::MUTE) this->custom_fan_mode = (std::string)""; - break; - } - } else { // при выключеном питании публикуем фальшивый статус - //this->fan_mode = climate::CLIMATE_FAN_LOW ; - this->custom_fan_mode = Constants::MUTE; - } - - _debugMsg(F("Climate fan mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->fan_mode); - _debugMsg(F("Climate fan TURBO mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanTurbo); - _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); - - //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ - - /*************************** SLEEP PRESET ***************************/ - // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - if(_current_ac_state.sleep == AC_SLEEP_ON && - _current_ac_state.power == AC_POWER_ON ) { - this->preset = climate::CLIMATE_PRESET_SLEEP; - } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { - this->preset = climate::CLIMATE_PRESET_NONE; - } - - _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); - - /*************************** HEALTH CUSTOM PRESET ***************************/ - // режим работы ионизатора - if(_current_ac_state.health == AC_HEALTH_ON && - _current_ac_state.power == AC_POWER_ON ) { - this->custom_preset = Constants::HEALTH; - } else if (this->custom_preset == Constants::HEALTH) { - this->custom_preset = (std::string)""; - } - - _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); - - /*************************** CLEAN CUSTOM PRESET ***************************/ - // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF - if(_current_ac_state.clean == AC_CLEAN_ON && - _current_ac_state.power == AC_POWER_OFF ) { - this->custom_preset = Constants::CLEAN; - } else if (this->custom_preset == Constants::CLEAN) { - this->custom_preset = (std::string)""; - } - - _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); - - /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ - // пресет просушки кондиционера после выключения - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - // - // Brokly: - // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет - // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии - // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки - // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало - // и затыкается. - if(_current_ac_state.mildew == AC_MILDEW_ON && - _current_ac_state.power == AC_POWER_OFF ) { - this->custom_preset = Constants::ANTIFUNGUS; - } else if (this->custom_preset == Constants::ANTIFUNGUS) { - this->custom_preset = (std::string)""; - } - - _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); - - /*************************** LOUVERs ***************************/ - - if( _current_ac_state.power == AC_POWER_OFF){ //ЕСЛИ КОНДЕЙ ВЫКЛЮЧЕН - this->swing_mode = climate::CLIMATE_SWING_OFF; - } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN && - _current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ - this->swing_mode = climate::CLIMATE_SWING_BOTH; - } else if (_current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN){ - this->swing_mode = climate::CLIMATE_SWING_VERTICAL; - } else { - this->swing_mode = climate::CLIMATE_SWING_OFF; - } - - _debugMsg(F("Climate swing mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->swing_mode); - - /*************************** TEMPERATURE ***************************/ - - if(_current_ac_state.mode == AC_MODE_FAN || _current_ac_state.power == AC_POWER_OFF){ - this->target_temperature = _current_ac_state.temp_ambient; - } else if (_current_ac_state.mode == AC_MODE_AUTO ){ - this->target_temperature = 25; - } else { - this->target_temperature = _current_ac_state.temp_target; - } - _debugMsg(F("Target temperature: %f"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->target_temperature); - this->current_temperature = _current_ac_state.temp_ambient; - _debugMsg(F("Room temperature: %f"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->current_temperature); - - /*********************************************************************/ - /*************************** PUBLISH STATE ***************************/ - /*********************************************************************/ - this->publish_state(); - // температура в комнате - if (sensor_indoor_temperature_ != nullptr) - sensor_indoor_temperature_->publish_state(_current_ac_state.temp_ambient); - // температура уличного блока - if (sensor_outdoor_temperature_ != nullptr) - sensor_outdoor_temperature_->publish_state(_current_ac_state.temp_outdoor); - // температура подводящей магистрали - if (sensor_inbound_temperature_ != nullptr) - sensor_inbound_temperature_->publish_state(_current_ac_state.temp_inbound); - // температура отводящей магистрали - if (sensor_outbound_temperature_ != nullptr) - sensor_outbound_temperature_->publish_state(_current_ac_state.temp_outbound); - // температура странного датчика - if (sensor_strange_temperature_ != nullptr) - sensor_strange_temperature_->publish_state(_current_ac_state.temp_strange); - // мощность инвертора - if (sensor_invertor_power_ != nullptr) - sensor_invertor_power_->publish_state(_current_ac_state.invertor_power); - // флаг режима разморозки - if (sensor_defrost_ != nullptr) - sensor_defrost_->publish_state(_current_ac_state.defrost); - // состояние дисплея - if (sensor_display_ != nullptr) { - switch (_current_ac_state.display) { - case AC_DISPLAY_ON: - if (this->get_display_inverted()) { - sensor_display_->publish_state(false); - } else { - sensor_display_->publish_state(true); - } - break; - - case AC_DISPLAY_OFF: - if (this->get_display_inverted()) { - sensor_display_->publish_state(true); - } else { - sensor_display_->publish_state(false); - } - break; - - default: - // могут быть и другие состояния, поэтому так - break; - } - } - } - - // вывод в дебаг текущей конфигурации компонента - void dump_config() { - ESP_LOGCONFIG(Constants::TAG, "AUX HVAC:"); - ESP_LOGCONFIG(Constants::TAG, " [x] Firmware version: %s", Constants::AC_FIRMWARE_VERSION.c_str()); - ESP_LOGCONFIG(Constants::TAG, " [x] Period: %dms", this->get_period()); - ESP_LOGCONFIG(Constants::TAG, " [x] Show action: %s", TRUEFALSE(this->get_show_action())); - ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", TRUEFALSE(this->get_display_inverted())); - ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); - ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); - if ((this->sensor_indoor_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); - if (!(this->sensor_indoor_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_indoor_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_indoor_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_indoor_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_indoor_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_indoor_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_indoor_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_indoor_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_indoor_temperature_)->unique_id().c_str()); - } - if ((this->sensor_indoor_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_outdoor_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Outdoor Temperature"), (this->sensor_outdoor_temperature_)->get_name().c_str()); - if (!(this->sensor_outdoor_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_outdoor_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_outdoor_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_outdoor_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_outdoor_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_outdoor_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_outdoor_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_outdoor_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_outdoor_temperature_)->unique_id().c_str()); - } - if ((this->sensor_outdoor_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_inbound_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Inbound Temperature"), (this->sensor_inbound_temperature_)->get_name().c_str()); - if (!(this->sensor_inbound_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_inbound_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_inbound_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_inbound_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_inbound_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_inbound_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_inbound_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_inbound_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_inbound_temperature_)->unique_id().c_str()); - } - if ((this->sensor_inbound_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_outbound_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Outbound Temperature"), (this->sensor_outbound_temperature_)->get_name().c_str()); - if (!(this->sensor_outbound_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_outbound_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_outbound_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_outbound_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_outbound_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_outbound_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_outbound_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_outbound_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_outbound_temperature_)->unique_id().c_str()); - } - if ((this->sensor_outbound_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_strange_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Strange Temperature"), (this->sensor_strange_temperature_)->get_name().c_str()); - if (!(this->sensor_strange_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_strange_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_strange_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_strange_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_strange_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_strange_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_strange_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_strange_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_strange_temperature_)->unique_id().c_str()); - } - if ((this->sensor_strange_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_invertor_power_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Inverter Power"), (this->sensor_invertor_power_)->get_name().c_str()); - if (!(this->sensor_invertor_power_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_invertor_power_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_invertor_power_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_invertor_power_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_invertor_power_)->get_accuracy_decimals()); - if (!(this->sensor_invertor_power_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_invertor_power_)->get_icon().c_str()); - } - if (!(this->sensor_invertor_power_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_invertor_power_)->unique_id().c_str()); - } - if ((this->sensor_invertor_power_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_defrost_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Defrost status"), (this->sensor_defrost_)->get_name().c_str()); - if (!(this->sensor_defrost_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_defrost_)->get_device_class().c_str()); - } - if (!(this->sensor_defrost_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_defrost_)->get_icon().c_str()); - } - if (!(this->sensor_defrost_)->get_object_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Object ID: '%s'", " ", (this->sensor_defrost_)->get_object_id().c_str()); - } - } - - if ((this->sensor_display_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Display"), (this->sensor_display_)->get_name().c_str()); - if (!(this->sensor_display_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_display_)->get_device_class().c_str()); - } - if (!(this->sensor_display_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_display_)->get_icon().c_str()); - } - if (!(this->sensor_display_)->get_object_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Object ID: '%s'", " ", (this->sensor_display_)->get_object_id().c_str()); - } - } - this->dump_traits_(Constants::TAG); - } - - // вызывается пользователем из интерфейса ESPHome или Home Assistant - void control(const esphome::climate::ClimateCall &call) override { - bool hasCommand = false; - ac_command_t cmd; - - _clearCommand(&cmd); // не забываем очищать, а то будет мусор - - // User requested mode change - if (call.get_mode().has_value()) { - ClimateMode mode = *call.get_mode(); - // Send mode to hardware - switch (mode) { - case climate::CLIMATE_MODE_OFF: - hasCommand = true; - cmd.power = AC_POWER_OFF; - load_preset(&cmd, POS_MODE_OFF); - cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать - this->mode = mode; - break; - - case climate::CLIMATE_MODE_COOL: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_COOL; - load_preset(&cmd, POS_MODE_COOL); - this->mode = mode; - break; - - case climate::CLIMATE_MODE_HEAT: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_HEAT; - load_preset(&cmd, POS_MODE_HEAT); - this->mode = mode; - break; - - case climate::CLIMATE_MODE_HEAT_COOL: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_AUTO; - load_preset(&cmd, POS_MODE_AUTO); - cmd.temp_target = 25; // зависимость от режима HEAT_COOL - cmd.temp_target_matter = true; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL - this->mode = mode; - break; - - case climate::CLIMATE_MODE_FAN_ONLY: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_FAN; - load_preset(&cmd, POS_MODE_FAN); - cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN - cmd.temp_target_matter = true; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN - cmd.sleep = AC_SLEEP_OFF; - if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ - cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN - } - this->mode = mode; - break; - - case climate::CLIMATE_MODE_DRY: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_DRY; - load_preset(&cmd, POS_MODE_DRY); - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY - cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY - this->mode = mode; - break; - - case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) - default: - break; - } - } - - // User requested fan_mode change - if(_current_ac_state.power == AC_POWER_ON || cmd.power == AC_POWER_ON) { // пресеты для включенного кондея - // User requested swing_mode change - if (call.get_swing_mode().has_value()) { - ClimateSwingMode swingmode = *call.get_swing_mode(); - // Send fan mode to hardware - switch (swingmode) { - // The protocol allows other combinations for SWING. - // For example "turn the louvers to the desired position or "spread to the sides" / "concentrate in the center". - // But the ROVEX IR-remote does not provide this features. Therefore this features haven't been tested. - // May be suitable for other models of AUX-based ACs. - case climate::CLIMATE_SWING_OFF: - cmd.louver.louver_h = AC_LOUVERH_OFF; - cmd.louver.louver_v = AC_LOUVERV_OFF; - hasCommand = true; - this->swing_mode = swingmode; - break; - - case climate::CLIMATE_SWING_BOTH: - cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; - cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; - hasCommand = true; - this->swing_mode = swingmode; - break; - - case climate::CLIMATE_SWING_VERTICAL: - cmd.louver.louver_h = AC_LOUVERH_OFF; - cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; - hasCommand = true; - this->swing_mode = swingmode; - break; - - case climate::CLIMATE_SWING_HORIZONTAL: - cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; - cmd.louver.louver_v = AC_LOUVERV_OFF; - hasCommand = true; - this->swing_mode = swingmode; - break; - } - } - - if (call.get_target_temperature().has_value()) { - if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_FAN) || - (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_FAN)){ // блокировка ввода температуры в режиме FAN - this->target_temperature = _current_ac_state.temp_ambient; - this->publish_state(); - } else if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_AUTO) || - (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_AUTO)){ // блокировка ввода температуры в режиме АВТО - this->target_temperature = 25; - this->publish_state(); - } else { - // User requested target temperature change - float temp = *call.get_target_temperature(); - // Send target temp to climate - if (temp > Constants::AC_MAX_TEMPERATURE) temp = Constants::AC_MAX_TEMPERATURE; - if (temp < Constants::AC_MIN_TEMPERATURE) temp = Constants::AC_MIN_TEMPERATURE; - if(cmd.temp_target != temp){ - cmd.temp_target = temp; - hasCommand = true; - cmd.temp_target_matter = true; - } - } - } - - if (call.get_fan_mode().has_value()) { - ClimateFanMode fanmode = *call.get_fan_mode(); - // Send fan mode to hardware - switch (fanmode) { - case climate::CLIMATE_FAN_AUTO: - if(cmd.mode != AC_MODE_FAN){ // при вентиляции нет такого режима - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_AUTO; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - } - break; - - case climate::CLIMATE_FAN_LOW: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_LOW; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_MEDIUM: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_MEDIUM; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_HIGH: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_HIGH; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_ON: - case climate::CLIMATE_FAN_OFF: - case climate::CLIMATE_FAN_MIDDLE: - case climate::CLIMATE_FAN_FOCUS: - case climate::CLIMATE_FAN_DIFFUSE: - default: - break; - } - - } else if (call.get_custom_fan_mode().has_value()) { - std::string customfanmode = *call.get_custom_fan_mode(); - // Send fan mode to hardware - if (customfanmode == Constants::TURBO) { - // TURBO fan mode is suitable in COOL and HEAT modes for Rovex air conditioners. - // Other modes don't accept TURBO fan mode. - // May be other AUX-based air conditioners do the same. - if ( cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT) { - - hasCommand = true; - cmd.fanTurbo = AC_FANTURBO_ON; - cmd.fanMute = AC_FANMUTE_OFF; // зависимость от fanturbo - this->custom_fan_mode = customfanmode; - } else { - _debugMsg(F("TURBO fan mode is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - } - } else if (customfanmode == Constants::MUTE) { - // MUTE fan mode is suitable in FAN mode only for Rovex air conditioner. - // In COOL mode AC receives command without any changes. - // May be other AUX-based air conditioners do the same. - //if ( cmd.mode == AC_MODE_FAN || - // _current_ac_state.mode == AC_MODE_FAN) { - - hasCommand = true; - cmd.fanMute = AC_FANMUTE_ON; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от fanmute - this->custom_fan_mode = customfanmode; - //} else { - // _debugMsg(F("MUTE fan mode is suitable in FAN mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - //} - } - } - - // Пользователь выбрал пресет - if (call.get_preset().has_value()) { - ClimatePreset preset = *call.get_preset(); - switch (preset) { - case climate::CLIMATE_PRESET_SLEEP: - // Ночной режим (SLEEP). Комбинируется только с режимами COOL, HEAT AUTO и DRY. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - if ( cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || cmd.mode == AC_MODE_DRY || cmd.mode == AC_MODE_AUTO || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_DRY || _current_ac_state.mode == AC_MODE_AUTO){ - - hasCommand = true; - cmd.sleep = AC_SLEEP_ON; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - this->preset = preset; - } else { - _debugMsg(F("SLEEP preset is suitable in COOL,HEAT and AUTO modes only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - break; - case climate::CLIMATE_PRESET_NONE: - // выбран пустой пресет, сбрасываем все настройки - hasCommand = true; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - this->preset = preset; - _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - default: - // никакие другие встроенные пресеты не поддерживаются - break; - } - } else if (call.get_custom_preset().has_value()) { - std::string custom_preset = *call.get_custom_preset(); - if (custom_preset == Constants::HEALTH) { - hasCommand = true; - cmd.health = AC_HEALTH_ON; - cmd.health_status = AC_HEALTH_STATUS_ON; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health - cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - if(cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || cmd.mode == AC_MODE_AUTO || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_AUTO ){ - - cmd.fanSpeed = AC_FANSPEED_AUTO; // зависимость от health - } else if(cmd.mode == AC_MODE_FAN || _current_ac_state.mode == AC_MODE_FAN){ - cmd.fanSpeed = AC_FANSPEED_MEDIUM; // зависимость от health - } - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::CLEAN) { - _debugMsg(F("CLEAN work only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } else if (custom_preset == Constants::ANTIFUNGUS) { - _debugMsg(F("Anti-FUNGUS works only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - } - } else if(_current_ac_state.power == AC_POWER_OFF || cmd.power == AC_POWER_OFF){ // функции при выключеном питании - - if (call.get_target_temperature().has_value()) { // блокировка изменения температуры в выключеном состоянии - this->target_temperature = _current_ac_state.temp_ambient; - this->publish_state(); - } - - if (call.get_preset().has_value()) { // пользователь выбрал пустой пресет - ClimatePreset preset = *call.get_preset(); - switch (preset) { - case climate::CLIMATE_PRESET_SLEEP: - _debugMsg(F("SLEEP preset is suitable in POWER ON mode only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - case climate::CLIMATE_PRESET_NONE: - // выбран пустой пресет, сбрасываем все настройки - hasCommand = true; - cmd.clean = AC_CLEAN_OFF; // для логики пресетов - cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->preset = preset; - _debugMsg(F("Clear all 'Power OFF' presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - default: - // никакие другие встроенные пресеты не поддерживаются - break; - } - } else { - std::string custom_preset = *call.get_custom_preset(); - if (call.get_custom_preset().has_value()) { - if (custom_preset == Constants::CLEAN) { - // режим очистки кондиционера при AC_POWER_OFF - hasCommand = true; - cmd.clean = AC_CLEAN_ON; - cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::ANTIFUNGUS) { - // Brokly: - // включение-выключение функции "Антиплесень". - // у меня пульт отправляет 5 посылок и на включение и на выключение, но реагирует на эту кнопку - // только в режиме POWER_OFF - - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - cmd.mildew = AC_MILDEW_ON; - cmd.clean = AC_CLEAN_OFF; // для логики пресетов - hasCommand = true; - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::HEALTH) { - _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - } - } - } - - if (hasCommand) { - commandSequence(&cmd); - this->publish_state(); // Publish updated state - _new_command_set = _store_settings; // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка - } - } - - esphome::climate::ClimateTraits traits() override { - // The capabilities of the climate device - auto traits = climate::ClimateTraits(); - - traits.set_supports_current_temperature(true); - traits.set_supports_two_point_target_temperature(false); // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature - - // tells the frontend what range of temperatures the climate device should display (gauge min/max values) - traits.set_visual_min_temperature(Constants::AC_MIN_TEMPERATURE); - traits.set_visual_max_temperature(Constants::AC_MAX_TEMPERATURE); - // the step with which to increase/decrease target temperature. This also affects with how many decimal places the temperature is shown. - traits.set_visual_temperature_step(Constants::AC_TEMPERATURE_STEP); - - traits.set_supported_modes(this->_supported_modes); - traits.set_supported_swing_modes(this->_supported_swing_modes); - traits.set_supported_presets(this->_supported_presets); - traits.set_supported_custom_presets(this->_supported_custom_presets); - traits.set_supported_custom_fan_modes(this->_supported_custom_fan_modes); - - /* + MINIMAL SET */ - traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF); - traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH); - traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF); - //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL); - //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_BOTH); - traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); - //traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); - - /* *************** TODO: надо сделать информирование о текущем режиме, сплит поддерживает *************** - * смотри climate::ClimateAction - */ - // if the climate device supports reporting the active current action of the device with the action property. - traits.set_supports_action(this->_show_action); - - return traits; - } - - // запрос маленького пакета статуса кондиционера - bool getStatusSmall(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("getStatusSmall: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 2) { - _debugMsg(F("getStatusSmall: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** getSmallInfo request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestSmallStatus)) { - _debugMsg(F("getStatusSmall: getSmallInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /*************************************** getSmallInfo control ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_controlSmallStatus)) { - _debugMsg(F("getStatusSmall: getSmallInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - _debugMsg(F("getStatusSmall: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - // запрос большого пакета статуса кондиционера - bool getStatusBig(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("getStatusBig: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 2) { - _debugMsg(F("getStatusBig: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** getBigInfo request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestBigStatus)) { - _debugMsg(F("getStatusBig: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /*************************************** getBigInfo control ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_controlBigStatus)) { - _debugMsg(F("getStatusBig: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - _debugMsg(F("getStatusBig: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - // запрос большого и малого пакетов статуса последовательно - bool getStatusBigAndSmall(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("getStatusBigAndSmall: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - - if (!getStatusSmall()) { - _debugMsg(F("getStatusBigAndSmall: error with small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - if (!getStatusBig()) { - _debugMsg(F("getStatusBigAndSmall: error with big status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - _debugMsg(F("getStatusBigAndSmall: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - /** стартовая последовательность пакетов - * - * нужна, чтобы не ждать долго обновления статуса кондиционера - * запускаем сразу, как только удалось подключиться к кондиционеру и прошел первый пинг-пакет - * возвращаемое значение будет присвоено флагу выполнения последовательности - * то есть при возврате false последовательность считается не запущенной и будет вызоваться до тех пор, пока не вернет true - **/ - bool startupSequence(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("startupSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - - // по сути на старте надо получить от кондиционера два статуса - if (!getStatusBigAndSmall()){ - _debugMsg(F("startupSequence: error with big&small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - }; - - _debugMsg(F("startupSequence: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - /** загружает на выполнение команду - * - * стандартная последовательность - это запрос маленького статусного пакета, выполнение команды и повторный запрос - * такого же статуса для проверки, что всё включилось, ну и для обновления интерфейсов всяких связанных компонентов - **/ - bool commandSequence(ac_command_t * cmd){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("commandSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - - // добавление начального запроса маленького статусного пакета в последовательность команд - if (!getStatusSmall()) { - _debugMsg(F("commandSequence: error with first small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 2) { - _debugMsg(F("commandSequence: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** set params request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestDoCommand, cmd)) { - _debugMsg(F("commandSequence: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /*************************************** set params control ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_controlDoCommand)) { - _debugMsg(F("commandSequence: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - // добавление финального запроса маленького статусного пакета в последовательность команд - if (!getStatusSmall()) { - _debugMsg(F("commandSequence: error with last small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - _debugMsg(F("commandSequence: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - // загружает на выполнение последовательность команд на включение/выключение - bool powerSequence(ac_power pwr = AC_POWER_ON){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("powerSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - if (pwr == AC_POWER_UNTOUCHED) return false; // выходим, чтобы не тратить время - - // формируем команду - ac_command_t cmd; - _clearCommand(&cmd); // не забываем очищать, а то будет мусор - cmd.power = pwr; - // добавляем команду в последовательность - if (!commandSequence(&cmd)) return false; - - _debugMsg(F("powerSequence: loaded (power = %02X)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, pwr); - return true; - } - - // выключает экран - bool displayOffSequence(){ - ac_display dsp = AC_DISPLAY_OFF; - if (this->get_display_inverted()) dsp = AC_DISPLAY_ON; - return _displaySequence(dsp); - } - - // включает экран - bool displayOnSequence(){ - ac_display dsp = AC_DISPLAY_ON; - if (this->get_display_inverted()) dsp = AC_DISPLAY_OFF; - return _displaySequence(dsp); - } - - // отправляет сплиту заданный набор байт - // Перед отправкой проверяет пакет на корректность структуры. CRC16 рассчитывает самостоятельно и перезаписывает. - bool sendTestPacket(const std::vector &data){ - //bool sendTestPacket(uint8_t *data = nullptr, uitn8_t data_length = 0){ - //if (data == nullptr) return false; - //if (data_length == 0) return false; - if (data.size() == 0) return false; - //if (data_length > AC_BUFFER_SIZE) return false; - if (data.size() > AC_BUFFER_SIZE) return false; - - // нет смысла в отправке, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("sendTestPacket: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - // очищаем пакет - _clearPacket(&_outTestPacket); - - // копируем данные в пакет - //memcpy(_outTestPacket.data, data, data_length); - uint8_t i = 0; - for (uint8_t n : data) { - _outTestPacket.data[i] = n; - i++; - } - - // на всякий случай указываем правильные некоторые байты - _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - //_outTestPacket.header->wifi = AC_PACKET_ANSWER; - - _outTestPacket.msec = millis(); - _outTestPacket.body = &(_outTestPacket.data[AC_HEADER_SIZE]); - _outTestPacket.bytesLoaded = AC_HEADER_SIZE + _outTestPacket.header->body_length + 2; - - // рассчитываем и записываем в пакет CRC - _outTestPacket.crc = (packet_crc_t *) &(_outTestPacket.data[AC_HEADER_SIZE + _outTestPacket.header->body_length]); - _setCRC16(&_outTestPacket); - - _debugMsg(F("sendTestPacket: test packet loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_outTestPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - - // ниже блок добавления отправки пакета в последовательность команд - //***************************************************************** - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 1) { - _debugMsg(F("sendTestPacket: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** sendTestPacket request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestTestPacket)) { - _debugMsg(F("sendTestPacket: sendTestPacket request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - _debugMsg(F("sendTestPacket: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - return true; - } - - void set_period(uint32_t ms) { this->_update_period = ms; } - uint32_t get_period() { return this->_update_period; } - - void set_show_action(bool show_action) { this->_show_action = show_action; } - bool get_show_action() { return this->_show_action; } - - void set_display_inverted(bool display_inverted) { this->_display_inverted = display_inverted; } - bool get_display_inverted() { return this->_display_inverted; } - - void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } - bool get_store_settings() { return this->_store_settings; } - - void set_supported_modes(const std::set &modes) { this->_supported_modes = modes; } - void set_supported_swing_modes(const std::set &modes) { this->_supported_swing_modes = modes; } - void set_supported_presets(const std::set &presets) { this->_supported_presets = presets; } - void set_custom_presets(const std::set &presets) { this->_supported_custom_presets = presets; } - void set_custom_fan_modes(const std::set &modes) { this->_supported_custom_fan_modes = modes; } - - uint8_t load_presets_result = 0xFF; - void setup() override { - #if defined(ESP32) - load_presets_result = storage.load(global_presets); // читаем все пресеты из флеша - _debugMsg(F("Preset base read from NVRAM, result %02d."), ESPHOME_LOG_LEVEL_WARN, __LINE__, load_presets_result); - #endif - }; - - void loop() override { - if (!get_hw_initialized()) return; - - // контролируем сохранение пресета - if(_new_command_set){ //нужно сохранить пресет - _new_command_set = false; - save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов - } - - // отрабатываем состояния конечного автомата - switch (_ac_state) { - case ACSM_RECEIVING_PACKET: - // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны - _doReceivingPacketState(); - break; - - case ACSM_PARSING_PACKET: - // разбираем полученный пакет - _doParsingPacket(); - break; - - case ACSM_SENDING_PACKET: - // отправляем пакет сплиту - _doSendingPacketState(); - break; - - case ACSM_IDLE: // ничего не делаем, ждем, на что бы среагировать - default: // если состояние какое-то посторонее, то считаем, что IDLE - _doIdleState(); - break; - } - - // раз в заданное количество миллисекунд запрашиваем обновление статуса кондиционера - if ((millis()-_dataMillis) > _update_period){ - _dataMillis = millis(); - - // обычный wifi-модуль запрашивает маленький пакет статуса - // но нам никто не мешает запрашивать и большой и маленький, чтобы чаще обновлять комнатную температуру - // делаем этот запросо только в случае, если есть коннект с кондиционером - if (get_has_connection()) getStatusBigAndSmall(); - } - - }; - }; -} // namespace aux_ac -} // namespace esphome \ No newline at end of file From 947bd4c2d9da72f2d9b75d3d99455c0d77d1314a Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:16:21 +0300 Subject: [PATCH 6/9] Delete climate.py --- climate.py | 341 ----------------------------------------------------- 1 file changed, 341 deletions(-) delete mode 100644 climate.py diff --git a/climate.py b/climate.py deleted file mode 100644 index 58a9ab2..0000000 --- a/climate.py +++ /dev/null @@ -1,341 +0,0 @@ -import logging -import esphome.config_validation as cv -import esphome.codegen as cg -from esphome.components import climate, uart, sensor, binary_sensor -from esphome import automation -from esphome.automation import maybe_simple_id -from esphome.const import ( - CONF_ID, - CONF_UART_ID, - CONF_PERIOD, - CONF_CUSTOM_FAN_MODES, - CONF_CUSTOM_PRESETS, - CONF_INTERNAL, - CONF_DATA, - CONF_SUPPORTED_MODES, - CONF_SUPPORTED_SWING_MODES, - CONF_SUPPORTED_PRESETS, - CONF_PRESSURE, - UNIT_CELSIUS, - UNIT_PERCENT, - UNIT_PASCAL, - ICON_POWER, - ICON_THERMOMETER, - ICON_GAS_CYLINDER, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_POWER_FACTOR, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, -) -from esphome.components.climate import ( - ClimateMode, - ClimatePreset, - ClimateSwingMode, -) - -_LOGGER = logging.getLogger(__name__) - -CODEOWNERS = ["@GrKoR"] -DEPENDENCIES = ["climate", "uart"] -AUTO_LOAD = ["sensor", "binary_sensor"] - -CONF_SUPPORTED_MODES = 'supported_modes' -CONF_SUPPORTED_SWING_MODES = 'supported_swing_modes' -CONF_SUPPORTED_PRESETS = 'supported_presets' -CONF_SHOW_ACTION = 'show_action' -CONF_INDOOR_TEMPERATURE = 'indoor_temperature' -CONF_OUTDOOR_TEMPERATURE = 'outdoor_temperature' -ICON_OUTDOOR_TEMPERATURE = 'mdi:home-thermometer-outline' -CONF_INBOUND_TEMPERATURE = 'inbound_temperature' -ICON_INBOUND_TEMPERATURE = 'mdi:thermometer-plus' -CONF_OUTBOUND_TEMPERATURE = 'outbound_temperature' -ICON_OUTBOUND_TEMPERATURE = 'mdi:thermometer-minus' -CONF_STRANGE_TEMPERATURE = 'strange_temperature' -ICON_STRANGE_TEMPERATURE = 'mdi:thermometer-lines' -CONF_DISPLAY_STATE = 'display_state' -CONF_INVERTOR_POWER = 'invertor_power' -CONF_DEFROST_STATE = 'defrost_state' -ICON_DEFROST = "mdi:snowflake-melt" -CONF_DISPLAY_INVERTED = 'display_inverted' -ICON_DISPLAY = "mdi:clock-digital" -CONF_STORE_SETTINGS = 'store_settings' - - -aux_ac_ns = cg.esphome_ns.namespace("aux_ac") -AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) -Capabilities = aux_ac_ns.namespace("Constants") - -AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action) -AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action) -AirConSendTestPacketAction = aux_ac_ns.class_("AirConSendTestPacketAction", automation.Action) - -ALLOWED_CLIMATE_MODES = { - "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, - "COOL": ClimateMode.CLIMATE_MODE_COOL, - "HEAT": ClimateMode.CLIMATE_MODE_HEAT, - "DRY": ClimateMode.CLIMATE_MODE_DRY, - "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, -} -validate_modes = cv.enum(ALLOWED_CLIMATE_MODES, upper=True) - -ALLOWED_CLIMATE_PRESETS = { - "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, -} -validate_presets = cv.enum(ALLOWED_CLIMATE_PRESETS, upper=True) - -ALLOWED_CLIMATE_SWING_MODES = { - "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, - "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, - "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, -} -validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True) - -CUSTOM_FAN_MODES = { - "MUTE": Capabilities.MUTE, - "TURBO": Capabilities.TURBO, -} -validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) - -CUSTOM_PRESETS = { - "CLEAN": Capabilities.CLEAN, - "HEALTH": Capabilities.HEALTH, - "ANTIFUNGUS": Capabilities.ANTIFUNGUS, -} -validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True) - - -def validate_raw_data(value): - if isinstance(value, list): - return cv.Schema([cv.hex_uint8_t])(value) - raise cv.Invalid( - "data must be a list of bytes" - ) - - -def output_info(config): - """_LOGGER.info(config)""" - return config - -CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(AirCon), - cv.Optional(CONF_PERIOD, default="7s"): cv.time_period, - cv.Optional(CONF_SHOW_ACTION, default="true"): cv.boolean, - cv.Optional(CONF_DISPLAY_INVERTED, default="false"): cv.boolean, - cv.Optional(CONF_STORE_SETTINGS, default="false"): cv.boolean, - cv.Optional(CONF_DEFROST_STATE, default="false"): cv.boolean, - cv.Optional(CONF_INVERTOR_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_PERCENT, - icon=ICON_POWER, - accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER_FACTOR, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_INDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_THERMOMETER, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_OUTDOOR_TEMPERATURE, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_INBOUND_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_INBOUND_TEMPERATURE, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_OUTBOUND_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_OUTBOUND_TEMPERATURE, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_STRANGE_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_STRANGE_TEMPERATURE, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - - cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema( - icon=ICON_DISPLAY - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean - } - ), - - cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema( - icon=ICON_DEFROST - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean - } - ), - - - cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes), - cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(validate_swing_modes), - cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(validate_presets), - cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(validate_custom_presets), - cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list(validate_custom_fan_modes), - } - ) - .extend(uart.UART_DEVICE_SCHEMA) - .extend(cv.COMPONENT_SCHEMA), - output_info -) - -async def to_code(config): - """_LOGGER.info("--------------")""" - """_LOGGER.info(config)""" - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await climate.register_climate(var, config) - - parent = await cg.get_variable(config[CONF_UART_ID]) - cg.add(var.initAC(parent)) - - if CONF_INDOOR_TEMPERATURE in config: - conf = config[CONF_INDOOR_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_indoor_temperature_sensor(sens)) - - if CONF_OUTDOOR_TEMPERATURE in config: - conf = config[CONF_OUTDOOR_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_outdoor_temperature_sensor(sens)) - - if CONF_OUTBOUND_TEMPERATURE in config: - conf = config[CONF_OUTBOUND_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_outbound_temperature_sensor(sens)) - - if CONF_INBOUND_TEMPERATURE in config: - conf = config[CONF_INBOUND_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_inbound_temperature_sensor(sens)) - - if CONF_STRANGE_TEMPERATURE in config: - conf = config[CONF_STRANGE_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_strange_temperature_sensor(sens)) - - if CONF_DISPLAY_STATE in config: - conf = config[CONF_DISPLAY_STATE] - sens = await binary_sensor.new_binary_sensor(conf) - cg.add(var.set_display_sensor(sens)) - - if CONF_DEFROST_STATE in config: - conf = config[CONF_DEFROST_STATE] - sens = await binary_sensor.new_binary_sensor(conf) - cg.add(var.set_defrost_state(sens)) - - if CONF_INVERTOR_POWER in config: - conf = config[CONF_INVERTOR_POWER] - sens = await sensor.new_sensor(conf) - cg.add(var.set_invertor_power_sensor(sens)) - - cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) - cg.add(var.set_show_action(config[CONF_SHOW_ACTION])) - cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED])) - cg.add(var.set_store_settings(config[CONF_STORE_SETTINGS])) - if CONF_SUPPORTED_MODES in config: - cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) - if CONF_SUPPORTED_SWING_MODES in config: - cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) - if CONF_SUPPORTED_PRESETS in config: - cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) - if CONF_CUSTOM_PRESETS in config: - cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS])) - if CONF_CUSTOM_FAN_MODES in config: - cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) - - - -DISPLAY_ACTION_SCHEMA = maybe_simple_id( - { - cv.Required(CONF_ID): cv.use_id(AirCon), - } -) - -@automation.register_action("aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA) -async def display_off_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - return cg.new_Pvariable(action_id, template_arg, paren) - -@automation.register_action("aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA) -async def display_on_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - return cg.new_Pvariable(action_id, template_arg, paren) - - -SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id( - { - cv.Required(CONF_ID): cv.use_id(AirCon), - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), - } -) - - -# ********************************************************************************************************* -# ВАЖНО! Только для инженеров! -# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт -# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает. -# Вы действуете на свой страх и риск. -# ********************************************************************************************************* -@automation.register_action( - "aux_ac.send_packet", - AirConSendTestPacketAction, - SEND_TEST_PACKET_ACTION_SCHEMA -) -async def send_packet_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, paren) - - data = config[CONF_DATA] - if isinstance(data, bytes): - data = list(data) - - if cg.is_template(data): - templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) - cg.add(var.set_data_template(templ)) - else: - cg.add(var.set_data_static(data)) - - return var \ No newline at end of file From f67bc7df33e4f3fc0c54f4e7c35c4747200e65e9 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:16:53 +0300 Subject: [PATCH 7/9] Add files via upload --- components/aux_ac/aux_ac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 9c6a569..c91e397 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -1316,7 +1316,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { { const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; const float antkoef = 1.0 - koef; - static float temp = _current_ac_state.temp_outdoor; + static float temp = big_info_body->outdoor_temperature - 0x20; temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); _current_ac_state.temp_outdoor = temp; From 022596d555f23916a53187f06ac2e902f98a01a5 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:18:52 +0300 Subject: [PATCH 8/9] Add files via upload From b5c591d99f6045e7306ccc2470a582bb08b4ab56 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 11:42:58 +0300 Subject: [PATCH 9/9] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=8B=D1=82=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=BE=D1=84=D0=B8=D0=BA=D1=81=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BF=D1=83=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D1=8E=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B5=D1=81=D0=B5=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/aux_ac.h | 69 ++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index c91e397..9600541 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -410,7 +410,7 @@ struct packet_small_info_body_t { uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны - // БФЙТ 7 + // БАЙТ 7 uint8_t mode; // режим работы сплита: // AUTO : bits[7, 6, 5] = [0, 0, 0] // COOL : bits[7, 6, 5] = [0, 0, 1] @@ -2144,16 +2144,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** FAN SPEED ***************************/ if(_current_ac_state.power == AC_POWER_ON){ - this->fan_mode = climate::CLIMATE_FAN_OFF; switch (_current_ac_state.fanSpeed) { case AC_FANSPEED_HIGH: this->fan_mode = climate::CLIMATE_FAN_HIGH; - this->custom_fan_mode = (std::string)""; break; case AC_FANSPEED_MEDIUM: this->fan_mode = climate::CLIMATE_FAN_MEDIUM; - this->custom_fan_mode = (std::string)""; break; case AC_FANSPEED_LOW: @@ -2165,6 +2162,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; default: + this->custom_fan_mode = Constants::MUTE; break; } /*************************** TURBO FAN MODE ***************************/ @@ -2188,7 +2186,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; } } else { // при выключеном питании публикуем фальшивый статус - //this->fan_mode = climate::CLIMATE_FAN_LOW ; this->custom_fan_mode = Constants::MUTE; } @@ -2490,7 +2487,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // вызывается пользователем из интерфейса ESPHome или Home Assistant void control(const esphome::climate::ClimateCall &call) override { bool hasCommand = false; - ac_command_t cmd; + static ac_command_t cmd; _clearCommand(&cmd); // не забываем очищать, а то будет мусор @@ -2504,7 +2501,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.power = AC_POWER_OFF; load_preset(&cmd, POS_MODE_OFF); cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_COOL: @@ -2512,7 +2509,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_COOL; load_preset(&cmd, POS_MODE_COOL); - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_HEAT: @@ -2520,7 +2517,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_HEAT; load_preset(&cmd, POS_MODE_HEAT); - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_HEAT_COOL: @@ -2531,7 +2528,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.temp_target = 25; // зависимость от режима HEAT_COOL cmd.temp_target_matter = true; cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -2546,7 +2543,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN } - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_DRY: @@ -2556,13 +2553,14 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { load_preset(&cmd, POS_MODE_DRY); cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) default: break; } + //this->mode = mode; } // User requested fan_mode change @@ -2580,30 +2578,31 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.louver.louver_h = AC_LOUVERH_OFF; cmd.louver.louver_v = AC_LOUVERV_OFF; hasCommand = true; - this->swing_mode = swingmode; + //this->swing_mode = swingmode; break; case climate::CLIMATE_SWING_BOTH: cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; hasCommand = true; - this->swing_mode = swingmode; + //this->swing_mode = swingmode; break; case climate::CLIMATE_SWING_VERTICAL: cmd.louver.louver_h = AC_LOUVERH_OFF; cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; hasCommand = true; - this->swing_mode = swingmode; + //this->swing_mode = swingmode; break; case climate::CLIMATE_SWING_HORIZONTAL: cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; cmd.louver.louver_v = AC_LOUVERV_OFF; hasCommand = true; - this->swing_mode = swingmode; + //this->swing_mode = swingmode; break; } + //this->swing_mode = swingmode; } if (call.get_target_temperature().has_value()) { @@ -2640,7 +2639,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners cmd.fanTurbo = AC_FANTURBO_OFF; cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; + //this->fan_mode = fanmode; } break; @@ -2650,7 +2649,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners cmd.fanTurbo = AC_FANTURBO_OFF; cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; + //this->fan_mode = fanmode; break; case climate::CLIMATE_FAN_MEDIUM: @@ -2659,7 +2658,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners cmd.fanTurbo = AC_FANTURBO_OFF; cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; + //this->fan_mode = fanmode; break; case climate::CLIMATE_FAN_HIGH: @@ -2668,7 +2667,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners cmd.fanTurbo = AC_FANTURBO_OFF; cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; + //this->fan_mode = fanmode; break; case climate::CLIMATE_FAN_ON: @@ -2679,6 +2678,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { default: break; } + //this->fan_mode = fanmode; } else if (call.get_custom_fan_mode().has_value()) { std::string customfanmode = *call.get_custom_fan_mode(); @@ -2693,7 +2693,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.fanTurbo = AC_FANTURBO_ON; cmd.fanMute = AC_FANMUTE_OFF; // зависимость от fanturbo - this->custom_fan_mode = customfanmode; + //this->custom_fan_mode = customfanmode; } else { _debugMsg(F("TURBO fan mode is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } @@ -2707,11 +2707,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.fanMute = AC_FANMUTE_ON; cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от fanmute - this->custom_fan_mode = customfanmode; + //this->custom_fan_mode = customfanmode; //} else { // _debugMsg(F("MUTE fan mode is suitable in FAN mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); //} } + //this->custom_fan_mode = customfanmode; } // Пользователь выбрал пресет @@ -2731,7 +2732,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.sleep = AC_SLEEP_ON; cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; - this->preset = preset; + //this->preset = preset; } else { _debugMsg(F("SLEEP preset is suitable in COOL,HEAT and AUTO modes only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } @@ -2742,13 +2743,14 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - this->preset = preset; + //this->preset = preset; _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); break; default: // никакие другие встроенные пресеты не поддерживаются break; } + //this->preset = preset; } else if (call.get_custom_preset().has_value()) { std::string custom_preset = *call.get_custom_preset(); if (custom_preset == Constants::HEALTH) { @@ -2766,12 +2768,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } else if(cmd.mode == AC_MODE_FAN || _current_ac_state.mode == AC_MODE_FAN){ cmd.fanSpeed = AC_FANSPEED_MEDIUM; // зависимость от health } - this->custom_preset = custom_preset; + //this->custom_preset = custom_preset; } else if (custom_preset == Constants::CLEAN) { _debugMsg(F("CLEAN work only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } else if (custom_preset == Constants::ANTIFUNGUS) { _debugMsg(F("Anti-FUNGUS works only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } + } + //this->custom_preset = custom_preset; } } else if(_current_ac_state.power == AC_POWER_OFF || cmd.power == AC_POWER_OFF){ // функции при выключеном питании @@ -2791,13 +2794,14 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.clean = AC_CLEAN_OFF; // для логики пресетов cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->preset = preset; + //this->preset = preset; _debugMsg(F("Clear all 'Power OFF' presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); break; default: // никакие другие встроенные пресеты не поддерживаются break; } + //this->preset = preset; } else { std::string custom_preset = *call.get_custom_preset(); if (call.get_custom_preset().has_value()) { @@ -2806,7 +2810,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.clean = AC_CLEAN_ON; cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->custom_preset = custom_preset; + //this->custom_preset = custom_preset; } else if (custom_preset == Constants::ANTIFUNGUS) { // Brokly: // включение-выключение функции "Антиплесень". @@ -2819,11 +2823,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.mildew = AC_MILDEW_ON; cmd.clean = AC_CLEAN_OFF; // для логики пресетов hasCommand = true; - this->custom_preset = custom_preset; + //this->custom_preset = custom_preset; } else if (custom_preset == Constants::HEALTH) { _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } } + //this->custom_preset = custom_preset; } } @@ -3144,6 +3149,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { load_presets_result = storage.load(global_presets); // читаем все пресеты из флеша _debugMsg(F("Preset base read from NVRAM, result %02d."), ESPHOME_LOG_LEVEL_WARN, __LINE__, load_presets_result); #endif + this->preset = climate::CLIMATE_PRESET_NONE; + this->custom_preset = (std::string)""; + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->fan_mode = climate::CLIMATE_FAN_LOW; + this->custom_fan_mode = (std::string)""; }; void loop() override {