From d74c8c59abceb7d91939dc3d447690bd981ac924 Mon Sep 17 00:00:00 2001 From: Brokly Date: Tue, 24 May 2022 22:08:39 +0300 Subject: [PATCH 1/8] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=B8=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BD=D0=B4?= =?UTF-8?q?=D0=B8=D1=86=D0=B8=D0=BE=D0=BD=D0=B5=D1=80=D0=BE=D0=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Реализовал зависимости, экшены, сделал сохранение пресетов для каждого режима, сохранение в NVRAM работает только для ESP32. Отлаживался на Energolux Bern, инвертер. --- components/aux_ac/automation.h | 34 +- components/aux_ac/aux_ac.h | 1646 +++++++++++++++++++++----------- components/aux_ac/climate.py | 182 ++-- 3 files changed, 1212 insertions(+), 650 deletions(-) diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index a62fb27..d8d9565 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -17,7 +17,7 @@ namespace aux_ac { protected: AirCon *ac_; - }; + }; template class AirConDisplayOnAction : public Action @@ -29,37 +29,7 @@ namespace aux_ac { 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 diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index fcb3fd2..71b69f6 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -13,6 +13,13 @@ #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 19 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны namespace esphome { namespace aux_ac { @@ -49,20 +56,19 @@ public: static const uint32_t AC_STATES_REQUEST_INTERVAL; }; -const std::string Constants::AC_FIRMWARE_VERSION = "0.2.2"; +const std::string Constants::AC_FIRMWARE_VERSION = "0.2.3"; 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::FEEL = "Feel"; +const std::string Constants::HEALTH = "Health"; +const std::string Constants::ANTIFUNGUS = "Antifugnus"; 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; // состояния конечного автомата компонента @@ -158,14 +164,14 @@ struct packet_t { // тело ответа на пинг struct packet_ping_answer_body_t { - uint8_t byte_1C; // первый байт всегда 0x1C - uint8_t byte_27; // второй байт тела пинг-ответа всегда 0x27 - uint8_t zero1; // всегда 0x00 - uint8_t zero2; // всегда 0x00 - uint8_t zero3; // всегда 0x00 - uint8_t zero4; // всегда 0x00 - uint8_t zero5; // всегда 0x00 - uint8_t zero6; // всегда 0x00 + 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 }; // тело большого информационного пакета @@ -174,63 +180,192 @@ struct packet_big_info_body_t { uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x21); // пакет может рассылаться и в дежурном режиме (без запроса со стороны wifi-модуля) // в этом случае тут могут быть значения, отличные от 0x21 - uint8_t byte_C0; // не расшифрован, всегда 0xC0 - // для RoyalClima18HNI: всегда 0xE0 - uint8_t unknown1; // не расшифрован, как-то связан с режимом работы сплита; как вариант, отражает режим работы - // компрессора во внешнем блоке или что-то такое, потому что иногда включение сплита не сразу приводит к изменениям в этом байте + uint8_t byte_C0; // не расшифрован, всегда 0xC0 11000000 + // для RoyalClima18HNI: всегда 0xE0 11100000 + // Brokly: для Energolux Bern: 0xE0; иногда, с равными промежутками во времени проскакивает )xE4 + // предполагаю, что это байт конфига кондиционера (5 бит - инвертер), (2 бит - периодический мпульсный сигнал,период пимерно 500 сек) + // БАЙТ 3 + bool power:1; + bool sleep:1; + bool v_louver:1; + bool h_louver:1; + bool louvers_on:1; + 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 - сплит выключен, до этого работал (статус держится 1 час после выкл.) - // 0x05 - режим AUTO - // 0x24 - режим OFF - // 0x25 - режим COOL - // 0x39 - ?? - // 0x45 - режим DRY - // 0x85 - режим HEAT - // 0xC4 - режим OFF, выключен давно, зима - // 0xC5 - режим FAN - uint8_t zero1; // всегда 0x00 - // для RoyalClima18HNI: режим разморозки внешнего блока - 0x20, в других случаях 0x00 - uint8_t fanSpeed; // в ответах на команды wifi-модуля в этом байте скорость работы вентилятора + // 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 + // 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 LouvON LouH LouV 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_POWER b00000001 + // #define AC_BIG_MASK_LOUVERS_ON b00010000 + // #define AC_BIG_MASK_LOUVERS_H b00000100 + + // #define AC_BIG_MASK_LOUVERS_L b00001000 + // #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 + // БАЙТ5 + uint8_t realFanSpeed:3; // Brokly: Energolux Bern - подтверждаю, ВАЖНО !!!! Это реальная скорость фена + uint8_t reserv30:5; // та которая в данный момент. Например может быть установлен нагрев со скоростью + // вентилятора HI, но кондей еще не произвел достаточно тепла, и крутит на LOW, + // Тут будет отображаться LOW // fanSpeed: OFF=0x00, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07; режим CLEAN=0x01 // в дежурных пакетах тут похоже что-то другое - uint8_t zero2; // всегда 0x00 - uint8_t ambient_temperature_int; // целая часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы + // БАЙТ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 - десятичная часть температуры - uint8_t zero3; // всегда 0x00 - uint8_t outdoor_temperature; // этот байт как-то связан с температурой во внешнем блоке. Требуются дополнительные исследования. - // При выключенном сплите характер изменения значения примерно соответствует изменению температуры на улице. - // При включенном сплите значение может очень сильно скакать. - // По схеме wiring diagram сплит-системы, во внешнем блоке есть термодатчик, отслеживающий температуру испарителя. - // Возможно, этот байт как раз и отражает изменение температуры на испарителе. - // Но я не смог разобраться, как именно перевести эти значения в градусы. - // Кроме того, зимой даже в минусовую температуру этот байт не уходит ниже 0x33 по крайней мере - // для температур в диапазоне -5..-10 градусов Цельсия. - uint8_t zero4; // всегда 0x00 - uint8_t zero5; // всегда 0x00 - uint8_t zero6; // всегда 0x00 - // для RoyalClima18HNI: похоже на какую-то температуру, точно неизвестно - // температура внешнего теплообменника влияет на это значение (при работе на обогрев - понижает, при охлаждении или при разморозке - повышает) - uint8_t zero7; // всегда 0x00 - // для RoyalClima18HNI: 0x20 - uint8_t zero8; // всегда 0x00 - // для RoyalClima18HNI: 0x20 - uint8_t zero9; // всегда 0x00 - uint8_t zero10; // всегда 0x00 + + // В ВЫКЛЮЧЕНОМ СОСТОНИИ занчение 7 8 9 10 равны !!!!!! + // А значит это термодатчики внутри внутреннего блока !!!!!! + + // БАЙТ8 + 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 градусов Цельсия. + // БАЙТ9 + uint8_t zero3; // Brokly: полностью повторяет значение 8 байта + + // БАЙТ10 + uint8_t zero4; // Brokly: полностью повторяет значение 8 байта + + // БАЙТ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% - uint8_t zero11; // всегда 0x00 - uint8_t zero12; // всегда 0x00 - uint8_t zero13; // всегда 0x00 - uint8_t zero14; // всегда 0x00 - uint8_t zero15; // всегда 0x00 - uint8_t zero16; // всегда 0x00 - uint8_t ambient_temperature_frac; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы + // БАЙТ17 + uint8_t zero11; // всегда 0x00 + // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 22, когда инвертор отключен и тут 0 + // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика достаточно плавна + + // БАЙТ18 + uint8_t zero13; // + // Brokly: Energolux Bern : наложение на показания инвертора (от 144 до 174, когда инвертор отключен + // показания немного скачут в районе 149...154, при включении инвертора быстро растет, при выключении + // моментально падает до 149...154, бывают опускания ниже этих значений до 144, чаще в момент первоначального + // включения инвертора, а потом вверх, не всегда. При включении уходит в 0 на одну посылку + + // БАЙТ19 + uint8_t zero12; // 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; }; // тело малого информационного пакета @@ -258,7 +393,7 @@ struct packet_small_info_body_t { // биты 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; остальные не известны + uint8_t fan_turbo_and_mute; // бит 7 = режим TURBO, бит 6 - режим MUTE; остальные не известны uint8_t mode; // режим работы сплита: // AUTO : bits[7, 6, 5] = [0, 0, 0] // COOL : bits[7, 6, 5] = [0, 0, 1] @@ -282,11 +417,14 @@ struct packet_small_info_body_t { // не очень понятно, зачем так сделано }; - - //**************************************************************************************************************************************************** //*************************************************** ПАРАМЕТРЫ РАБОТЫ КОНДИЦИОНЕРА ****************************************************************** //**************************************************************************************************************************************************** + +// для показаний о реальной скорости фена из большого пакета +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 @@ -296,19 +434,27 @@ enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTO #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_HEALTH_ERROR_MASK 0b00000001 +enum ac_health_error : uint8_t { AC_HEALTH_ERROR_NO = 0x00, AC_HEALTH_ERROR_ACT = 0x01, AC_HEALTH_ERROR_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 }; @@ -361,20 +507,46 @@ enum ac_display : uint8_t { AC_DISPLAY_OFF = 0x00, AC_DISPLAY_ON = 0x10, AC_DISP #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 5 + /** команда для кондиционера * * ВАЖНО! В коде используется копирование команд простым присваиванием. * Если в структуру будут введены указатели, то копирование надо будет изменить! */ + +// данные структур содержат настройку, специально вынес в макрос +#define AC_COMMAND_BASE float temp_target;\ + ac_power power;\ + ac_clean clean;\ + ac_health health;\ + ac_mode mode;\ + ac_sleep sleep;\ + ac_ifeel iFeel;\ + 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; + +// чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше +// из-за такого приема нужно контролировать размер копируемых данных руками +#define AC_COMMAND_BASE_SIZE 20 + struct ac_command_t { +/* ac_power power; - float temp_target; - bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную - float temp_ambient; - float temp_outdoor; ac_clean clean; - ac_health health; - ac_health_status health_status; + ac_health health; // включение ионизатора ac_mode mode; ac_sleep sleep; ac_ifeel iFeel; @@ -384,8 +556,40 @@ struct ac_command_t { ac_fanmute fanMute; ac_display display; ac_mildew mildew; + ac_timer timer; + uint8_t timer_hours; + uint8_t timer_minutes; + ac_health_error health_error; // ошибка ионизатора + float temp_target; +*/ + AC_COMMAND_BASE; + ac_health_error health_error; + bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную + 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; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) }; +// структура для сохранения данных +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; // текущее состояние параметров кондея можно хранить в таком же формате, как и комманды //**************************************************************************************************************************************************** @@ -393,8 +597,6 @@ typedef ac_command_t ac_state_t; // текущее состояние пара //**************************************************************************************************************************************************** - - /***************************************************************************************************************************************************** * структуры и типы для последовательности команд ***************************************************************************************************************************************************** @@ -424,7 +626,8 @@ typedef ac_command_t ac_state_t; // текущее состояние пара // дефолтный таймаут входящего пакета в миллисекундах // если для входящего пакета в последовательности указан таймаут 0, то используется значение по-умолчанию // если нужный пакет не поступил в течение указанного времени, то последовательность прерывается с ошибкой -#define AC_SEQUENCE_DEFAULT_TIMEOUT 500 +// Brokly: пришлось увеличить +#define AC_SEQUENCE_DEFAULT_TIMEOUT 580 enum sequence_item_type_t : uint8_t { AC_SIT_NONE = 0x00, // пустой элемент последовательности @@ -459,6 +662,21 @@ struct sequence_item_t { 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 new_command_set = false; // флаг отправки новой команды + bool new_command_reply = false; // флаг получения ответа на новую команду + // время последнего запроса статуса у кондея uint32_t _dataMillis; // периодичность обновления статуса кондея, по дефолту AC_STATES_REQUEST_INTERVAL @@ -513,9 +731,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { packet_t _inPacket; packet_t _outPacket; - // пакет для тестирования всякой фигни - packet_t _outTestPacket; - // последовательность пакетов текущий шаг в последовательности sequence_item_t _sequence[AC_SEQUENCE_MAX_LEN]; uint8_t _sequence_current_step; @@ -671,7 +886,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; } } - + // заполняет структуру команды нейтральными значениями void _clearCommand(ac_command_t * cmd){ cmd->clean = AC_CLEAN_UNTOUCHED; @@ -680,7 +895,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd->fanSpeed = AC_FANSPEED_UNTOUCHED; cmd->fanTurbo = AC_FANTURBO_UNTOUCHED; cmd->health = AC_HEALTH_UNTOUCHED; - cmd->health_status = AC_HEALTH_STATUS_UNTOUCHED; + cmd->health_error = AC_HEALTH_ERROR_UNTOUCHED; cmd->iFeel = AC_IFEEL_UNTOUCHED; cmd->louver.louver_h = AC_LOUVERH_UNTOUCHED; cmd->louver.louver_v = AC_LOUVERV_UNTOUCHED; @@ -688,12 +903,19 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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); @@ -1021,9 +1243,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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_HEALTH_ERROR_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.health_error != (ac_health_error)stateByte); + _current_ac_state.health_error = (ac_health_error)stateByte; stateByte = small_info_body->status & AC_CLEAN_MASK; stateChangedFlag = stateChangedFlag || (_current_ac_state.clean != (ac_clean)stateByte); @@ -1036,9 +1258,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; } @@ -1052,20 +1275,61 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { packet_big_info_body_t * big_info_body; big_info_body = (packet_big_info_body_t *) (_inPacket.body); - // температура воздуха в помещении по версии сплит-систему + // температура воздуха в помещении по версии сплит-системы 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; // некая температура из наружного блока, скорее всего температура испарителя - // TODO: формула расчета неправильная! Нужно исследовать на опыте, какая температура при каких условиях - //stateFloat = big_info_body->outdoor_temperature - 0x20; - stateFloat = big_info_body->outdoor_temperature; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != stateFloat); - _current_ac_state.temp_outdoor = 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(); + if (stateChangedFlag) { + stateChanged(); + if(new_command_set){ // это ответ на исходящий пакет + new_command_set = false; + new_command_reply = true; //флаг о получении ответа, нужно сохранить пресет, после полуучения ответа от кондея + } + } break; } @@ -1133,16 +1397,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (dbgLevel < ESPHOME_LOG_LEVEL_NONE) dbgLevel = ESPHOME_LOG_LEVEL_NONE; if (dbgLevel > ESPHOME_LOG_LEVEL_VERY_VERBOSE) dbgLevel = ESPHOME_LOG_LEVEL_VERY_VERBOSE; - // TODO: Пока сделано через Ж* - сообщение копируется в массив и потом выводится.... - // это костыль, чтобы передать неизвестное количество аргументов - char _msg[128]; - msg.toCharArray(_msg, 128); - if (line == 0) line = __LINE__; // если строка не передана, берем текущую строку va_list vl; va_start(vl, line); - esp_log_vprintf_(dbgLevel, Constants::TAG, line, _msg, vl); + esp_log_vprintf_(dbgLevel, Constants::TAG, line, msg.c_str(), vl); // так тоже вроде через Ж*, только ее не видно :) va_end(vl); } @@ -1180,26 +1439,37 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } // формируем данные - for (int i=0; ibytesLoaded; i++){ - // для нормальных пакетов надо заключить заголовок в [] - if ((!notAPacket) && (i == 0)) st += "["; - // для нормальных пакетов надо заключить CRC в [] - if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE)) 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; + //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); + // для нормальных пакетов надо заключить заголовок в [] + 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 @@ -1323,7 +1593,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (clrPacket) _clearPacket(pack); // заполняем его параметрами из _current_ac_state - if (cmd != &_current_ac_state) _fillSetCommand(false, pack, &_current_ac_state); + if (cmd != &_current_ac_state) { + _fillSetCommand(false, pack, &_current_ac_state); + } // если команда не указана, значит выходим if (cmd == nullptr) return; @@ -1358,6 +1630,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } + // обнулить счетчик минут с последней команды + 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; @@ -1401,10 +1676,16 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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_error != AC_HEALTH_ERROR_UNTOUCHED){ + pack->body[10] = (pack->body[10] & ~AC_HEALTH_ERROR_MASK) | cmd->health_error; + } + // дисплей if (cmd->display != AC_DISPLAY_UNTOUCHED){ pack->body[12] = (pack->body[12] & ~AC_DISPLAY_MASK) | cmd->display; @@ -1414,6 +1695,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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]); @@ -1579,33 +1862,18 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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 = new esphome::sensor::Sensor(); esphome::sensor::Sensor *sensor_indoor_temperature_ = nullptr; - // TODO: если расшифруем формулу для уличной температуры, то можно будет вернуть - //esphome::sensor::Sensor *sensor_outdoor_temperature = new esphome::sensor::Sensor(); - + 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){ @@ -1626,7 +1894,56 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _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); // просто копируем из массива + cmd->temp_target_matter = true; // флаг изменения температуры + _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("Try 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 + //} + } + public: // инициализация объекта void initAC(esphome::uart::UARTComponent *parent = nullptr){ @@ -1634,10 +1951,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _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); @@ -1656,8 +1969,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; }; @@ -1666,235 +1984,253 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return (_sequence[0].item_type != AC_SIT_NONE); } - // вызывается, если параметры кондиционера изменились + // вызывается, если параметры кондиционера изменились, ДЛЯ ПУБЛИКАЦИИ void stateChanged(){ _debugMsg(F("State changed, let's publish it."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - + 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 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){ // инвертор включен, но нужно дождаться реакции на его включение + int16_t delta=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE ){ //медленное вращение + if(delta > 0){ //холодный радиатор + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else { // теплый радиатор, видимо переходный режим + this->action = climate::CLIMATE_ACTION_IDLE; + } + } else if(delta < -2){ // входящая температура выше комнатной, быстрый фен - ОБОГРЕВ + this->action = climate::CLIMATE_ACTION_HEATING; + } else if(delta > 2){ // ниже, быстрый фен - ОХЛАЖДЕНИЕ + this->action = climate::CLIMATE_ACTION_COOLING; + } else { // просто вентиляция + this->action = climate::CLIMATE_ACTION_IDLE; + } + } + + _debugMsg(F("Action mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->action); /*************************** POWER & MODE ***************************/ - this->mode = climate::CLIMATE_MODE_OFF; - this->action = climate::CLIMATE_ACTION_OFF; 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 - // TODO: надо реализовать отображение action - this->action = climate::CLIMATE_ACTION_IDLE; - break; + break; case AC_MODE_COOL: this->mode = climate::CLIMATE_MODE_COOL; - // TODO: надо реализовать отображение action - this->action = climate::CLIMATE_ACTION_COOLING; - break; + break; case AC_MODE_DRY: this->mode = climate::CLIMATE_MODE_DRY; - // TODO: надо реализовать отображение action - this->action = climate::CLIMATE_ACTION_DRYING; - break; + break; case AC_MODE_HEAT: this->mode = climate::CLIMATE_MODE_HEAT; - // TODO: надо реализовать отображение action - this->action = climate::CLIMATE_ACTION_HEATING; - break; + break; case AC_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; - // TODO: надо реализовать отображение action - this->action = climate::CLIMATE_ACTION_FAN; - break; + break; default: _debugMsg(F("Warning: unknown air conditioner mode."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - break; + break; } } else { this->mode = climate::CLIMATE_MODE_OFF; - // TODO: надо реализовать отображение action - // TODO: возможно, тут некорректно. Сплит может быть выключен, но продолжать крутить вентилятор для просушки (MILDEW preset) или очистки (CLEAN preset) - this->action = climate::CLIMATE_ACTION_OFF; } _debugMsg(F("Climate mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->mode); /*************************** FAN SPEED ***************************/ - this->fan_mode = climate::CLIMATE_FAN_OFF; - switch (_current_ac_state.fanSpeed) { - case AC_FANSPEED_HIGH: - this->fan_mode = climate::CLIMATE_FAN_HIGH; + 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_MEDIUM: - this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + case AC_FANSPEED_LOW: + this->fan_mode = climate::CLIMATE_FAN_LOW; break; - case AC_FANSPEED_LOW: - this->fan_mode = climate::CLIMATE_FAN_LOW; + case AC_FANSPEED_AUTO: + this->fan_mode = climate::CLIMATE_FAN_AUTO; break; - case AC_FANSPEED_AUTO: - this->fan_mode = climate::CLIMATE_FAN_AUTO; + default: break; - - default: - _debugMsg(F("Warning: unknown fan speed."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + } + /*************************** 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); - - /*************************** TURBO FAN MODE ***************************/ - // TURBO работает только в режимах COOL и HEAT - switch (_current_ac_state.fanTurbo) { - case AC_FANTURBO_ON: - if ((_current_ac_state.mode == AC_MODE_HEAT) || (_current_ac_state.mode == AC_MODE_COOL)) { - 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; - } - _debugMsg(F("Climate fan TURBO mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanTurbo); - - /*************************** MUTE FAN MODE ***************************/ - // MUTE работает только в режиме FAN. В режиме COOL кондей команду принимает, но MUTE не устанавливается - switch (_current_ac_state.fanMute) { - case AC_FANMUTE_ON: - if (_current_ac_state.mode == AC_MODE_FAN) { - 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; - } - _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 часов при отключении режима - не понятно. - switch (_current_ac_state.sleep) { - case AC_SLEEP_ON: - if ( _current_ac_state.mode == AC_MODE_COOL - or _current_ac_state.mode == AC_MODE_HEAT) { - - this->preset = climate::CLIMATE_PRESET_SLEEP; - - } + //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ + 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_SLEEP_OFF: - default: - if (this->preset == climate::CLIMATE_PRESET_SLEEP) this->preset = climate::CLIMATE_PRESET_NONE; + 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); - _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); - - /*************************** CLEAN CUSTOM PRESET ***************************/ - // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF - switch (_current_ac_state.clean) { - case AC_CLEAN_ON: - if (_current_ac_state.power == AC_POWER_OFF) { - - 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); - - /*************************** iFEEL CUSTOM PRESET ***************************/ - // режим поддержки температуры в районе пульта - // TODO: пока не реализован - switch (_current_ac_state.iFeel) { - case AC_IFEEL_ON: - this->custom_preset = Constants::FEEL; - 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 ***************************/ - // режим работы ионизатора - // TODO: не реализован, у меня отсутствует. Смотри комменты в секции define - switch (_current_ac_state.health) { - case AC_HEALTH_ON: + /*************************** 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){ // ЕСЛИ КОНДЕЙ ВЫКЛЮЧЕН - case AC_HEALTH_OFF: - default: - if (this->custom_preset == Constants::HEALTH) this->custom_preset = (std::string)""; + 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); } - _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); - - /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ - // пресет просушки кондиционера после выключения - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - // TODO: не реализован, у меня отсутствует - 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); - - /*************************** LOUVERs ***************************/ - this->swing_mode = climate::CLIMATE_SWING_OFF; - if (_current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ + + 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; - } - if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN){ - if (_current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ - this->swing_mode = climate::CLIMATE_SWING_BOTH; - } else { - this->swing_mode = climate::CLIMATE_SWING_VERTICAL; - } - } + } 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 ***************************/ - this->target_temperature = _current_ac_state.temp_target; + + 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 ***************************/ /*********************************************************************/ @@ -1903,11 +2239,25 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (sensor_indoor_temperature_ != nullptr) sensor_indoor_temperature_->publish_state(_current_ac_state.temp_ambient); // температура уличного блока - // TODO: если расшифруем формулу для уличной температуры, то можно будет вернуть - //sensor_outdoor_temperature->publish_state(_current_ac_state.temp_outdoor); - + 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) + if (sensor_display_ != nullptr) { switch (_current_ac_state.display) { case AC_DISPLAY_ON: if (this->get_display_inverted()) { @@ -1915,7 +2265,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } else { sensor_display_->publish_state(true); } - break; + break; case AC_DISPLAY_OFF: if (this->get_display_inverted()) { @@ -1923,12 +2273,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } else { sensor_display_->publish_state(false); } - break; + break; default: // могут быть и другие состояния, поэтому так - break; + break; } + } } // вывод в дебаг текущей конфигурации компонента @@ -1938,6 +2289,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { ESP_LOGCONFIG(Constants::TAG, " [x] Period: %dms", this->get_period()); ESP_LOGCONFIG(Constants::TAG, " [x] Show action: %s", this->get_show_action() ? "true" : "false"); ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", this->get_display_inverted() ? "true" : "false"); + 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()) { @@ -1956,6 +2308,115 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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()) { @@ -1969,7 +2430,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } this->dump_traits_(Constants::TAG); - } // вызывается пользователем из интерфейса ESPHome или Home Assistant @@ -1986,6 +2446,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { case climate::CLIMATE_MODE_OFF: hasCommand = true; cmd.power = AC_POWER_OFF; + if(_current_ac_state.power != AC_POWER_OFF){ + load_preset(&cmd, POS_MODE_OFF); + cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать + cmd.temp_target_matter = true; + } this->mode = mode; break; @@ -1993,6 +2458,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_COOL; + if(_current_ac_state.mode != AC_MODE_COOL){ + load_preset(&cmd, POS_MODE_COOL); + } this->mode = mode; break; @@ -2000,6 +2468,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_HEAT; + if(_current_ac_state.mode != AC_MODE_HEAT){ + load_preset(&cmd, POS_MODE_HEAT); + } this->mode = mode; break; @@ -2007,6 +2478,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_AUTO; + if(_current_ac_state.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; @@ -2014,6 +2491,16 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_FAN; + if(_current_ac_state.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; @@ -2021,6 +2508,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_DRY; + if(_current_ac_state.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; @@ -2028,210 +2520,293 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { default: break; } - } // User requested fan_mode change - 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: - 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; + 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_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; + + 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_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; + + 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_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: + + 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->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 + 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; + } + } } - } 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) { + 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 - or cmd.mode == AC_MODE_HEAT - or _current_ac_state.mode == AC_MODE_COOL - or _current_ac_state.mode == AC_MODE_HEAT) { + 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.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) { + } 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 - or _current_ac_state.mode == AC_MODE_FAN) { + //if ( cmd.mode == AC_MODE_FAN || + // _current_ac_state.mode == AC_MODE_FAN) { hasCommand = true; - cmd.fanMute = AC_FANMUTE_ON; + 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. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - if ( cmd.mode == AC_MODE_COOL - or cmd.mode == AC_MODE_HEAT - or _current_ac_state.mode == AC_MODE_COOL - or _current_ac_state.mode == AC_MODE_HEAT) { - - hasCommand = true; - cmd.sleep = AC_SLEEP_ON; - this->preset = preset; - } else { - _debugMsg(F("SLEEP preset is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - } - break; - - default: - // никакие другие встроенные пресеты не поддерживаются - break; - } - } else if (call.get_custom_preset().has_value()) { - std::string custompreset = *call.get_custom_preset(); - if (custompreset == Constants::CLEAN) { - // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF - // TODO: надо отдебажить выключение этого режима - if ( cmd.power == AC_POWER_OFF - or _current_ac_state.power == AC_POWER_OFF) { - - hasCommand = true; - cmd.clean = AC_CLEAN_ON; - this->custom_preset = custompreset; - - } else { - _debugMsg(F("CLEAN preset is suitable in POWER_OFF mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + //} 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.iFeel = AC_IFEEL_OFF; // для логики пресетов + cmd.health = AC_HEALTH_OFF; // для логики пресетов + cmd.health_error = AC_HEALTH_ERROR_NO; + 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_error = AC_HEALTH_ERROR_NO; + 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; + default: + // никакие другие встроенные пресеты не поддерживаются + break; + } + } 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_error = AC_HEALTH_ERROR_NO; + cmd.sleep = AC_SLEEP_OFF; // для логики пресетов + this->custom_preset = custom_preset; + } else if (custom_preset == Constants::HEALTH) { + hasCommand = true; + cmd.health = AC_HEALTH_ON; + cmd.health_error = AC_HEALTH_ERROR_ACT; + cmd.iFeel = AC_IFEEL_ON; // зависимость от health + 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->mode = climate::CLIMATE_MODE_OFF; + 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::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__); + } } - } else if (custompreset == Constants::FEEL) { - _debugMsg(F("iFEEL preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); - // TODO: надо подумать, как заставить этот режим работать без пульта - //hasCommand = true; - //this->custom_preset = custompreset; - } else if (custompreset == Constants::HEALTH) { - _debugMsg(F("HEALTH preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); - // TODO: в моём кондиционере этот режим отсутствует, не понятно, как отлаживать - //hasCommand = true; - //this->custom_preset = custompreset; - } else if (custompreset == Constants::ANTIFUNGUS) { - // включение-выключение функции "Антиплесень". - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - _debugMsg(F("ANTIFUNGUS preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); - // TODO: надо уточнить, в каких режимах штатно включается этот режим у кондиционера - //cmd.mildew = AC_MILDEW_ON; - //hasCommand = true; - //this->custom_preset = custompreset; } } - // 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()) { - hasCommand = true; - // 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; - cmd.temp_target = temp; - cmd.temp_target_matter = true; - } if (hasCommand) { commandSequence(&cmd); this->publish_state(); // Publish updated state + new_command_set = true; // флаг отправки новой команды, для процедуры сохранения пресетов } } @@ -2460,67 +3035,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; } @@ -2536,13 +3050,24 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; } - void setup() override { + 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_reply){ //нужно сохранить пресет + new_command_reply = false; + save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов + } + + // отрабатываем состояния конечного автомата switch (_ac_state) { case ACSM_RECEIVING_PACKET: // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны @@ -2571,12 +3096,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // обычный wifi-модуль запрашивает маленький пакет статуса // но нам никто не мешает запрашивать и большой и маленький, чтобы чаще обновлять комнатную температуру - // делаем этот запрос только в случае, если есть коннект с кондиционером + // делаем этот запросом только в случае, если есть коннект с кондиционером if (get_has_connection()) getStatusBigAndSmall(); } }; -}; - + }; } // namespace aux_ac } // namespace esphome \ No newline at end of file diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index c6ba1d1..fe51809 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -11,10 +11,19 @@ from esphome.const import ( 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 ( @@ -29,15 +38,25 @@ 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_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:numeric" +ICON_DISPLAY = "mdi:clock-digital" aux_ac_ns = cg.esphome_ns.namespace("aux_ac") AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) @@ -45,7 +64,6 @@ 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, @@ -82,15 +100,6 @@ CUSTOM_PRESETS = { } 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 @@ -102,6 +111,18 @@ CONFIG_SCHEMA = cv.All( 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_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, @@ -113,6 +134,51 @@ CONFIG_SCHEMA = cv.All( 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( @@ -120,6 +186,16 @@ CONFIG_SCHEMA = cv.All( 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), @@ -146,11 +222,41 @@ async def to_code(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])) @@ -166,8 +272,6 @@ async def to_code(config): 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), @@ -175,47 +279,11 @@ DISPLAY_ACTION_SCHEMA = maybe_simple_id( ) @automation.register_action("aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA) -async def display_off_to_code(config, action_id, template_arg, args): +async def switch_toggle_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): +async def switch_toggle_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 + return cg.new_Pvariable(action_id, template_arg, paren) \ No newline at end of file From 37f0724a4152f8095357ff4d1f5da04a1307375c Mon Sep 17 00:00:00 2001 From: Brokly Date: Tue, 24 May 2022 22:13:39 +0300 Subject: [PATCH 2/8] =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D1=80=D0=B0=D0=B9=D0=BD=D0=B5?= =?UTF-8?q?=D0=B9=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлены дополнительные датчики --- examples/advanced/ac-energolux-bern.yaml | 143 +++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 examples/advanced/ac-energolux-bern.yaml diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml new file mode 100644 index 0000000..1db3349 --- /dev/null +++ b/examples/advanced/ac-energolux-bern.yaml @@ -0,0 +1,143 @@ +external_components: + - source: + #type: git + #url: https://github.com/GrKoR/esphome_aux_ac_component + type: local + path: my_components + components: [ aux_ac ] + refresh: 0s + +esphome: + name: $devicename + +esp32: + board: nodemcu-32s + framework: + type: arduino + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_pass + manual_ip: + static_ip: ${wifi_ip} + gateway: !secret gateway + subnet: !secret subnet + dns1: !secret dns1 + dns2: !secret dns2 + ap: + ssid: ${upper_devicename} Hotspot + password: !secret ap_wifi_pass + +captive_portal: + +debug: + +logger: + level: DEBUG + +api: +# password: !secret api_pass + +ota: + password: !secret ota_pass + +web_server: + port: 80 + auth: + username: !secret web_user + password: !secret web_pass + +uart: + id: ac_uart_bus + tx_pin: GPIO16 + rx_pin: GPIO17 + baud_rate: 4800 + data_bits: 8 + parity: EVEN + stop_bits: 1 + +climate: + - platform: aux_ac + name: ${upper_devicename} + id: aux_id + uart_id: ac_uart_bus + period: 7s + show_action: true + display_inverted: false + indoor_temperature: + name: ${upper_devicename} AC Indoor Temperature + id: ${low_devicename}_indoor_temp + internal: false + outdoor_temperature: + name: ${upper_devicename} AC Outdoor Temperature + id: ${low_devicename}_outdoor_temp + internal: false + outbound_temperature: + name: ${upper_devicename} AC Colant Outbound Temperature + id: ${low_devicename}_outbound_temp + internal: false + inbound_temperature: + name: ${upper_devicename} AC Colant Inbound Temperature + id: ${low_devicename}_inbound_temp + internal: false + strange_temperature: + name: ${upper_devicename} AC Strange Temperature + id: ${low_devicename}_strange_temp + internal: false + display_state: + name: $upper_devicename Display State + id: ${low_devicename}_display_state + internal: false + defrost_state: + name: $upper_devicename Defrost State + id: ${low_devicename}_defrost_state + internal: false + invertor_power: + name: $upper_devicename Invertor Power + id: ${low_devicename}_invertor_power + internal: false + visual: + min_temperature: 16 + max_temperature: 32 + temperature_step: 1 + supported_modes: + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + custom_fan_modes: + - MUTE + - TURBO + supported_presets: + - SLEEP + custom_presets: + - CLEAN + - FEEL + - HEALTH + - ANTIFUNGUS + supported_swing_modes: + - VERTICAL + - HORIZONTAL + - BOTH + +sensor: + - platform: wifi_signal + name: ${upper_devicename} WiFi Signal + update_interval: 30s + unit_of_measurement: "dBa" + accuracy_decimals: 0 + +switch: + - platform: template + name: $upper_devicename Display + lambda: |- + if (id(${low_devicename}_display_state).state) { + return true; + } else { + return false; + } + turn_on_action: + - aux_ac.display_on: aux_id + turn_off_action: + - aux_ac.display_off: aux_id From c30efa4d44f4c061cd4ce6cd2ec3f77d062b4da1 Mon Sep 17 00:00:00 2001 From: Brokly Date: Wed, 25 May 2022 22:11:11 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BB?= =?UTF-8?q?=D0=B8=D1=88=D0=BD=D0=B8=D0=B5=20"AC"=20=D0=B2=20=D1=8F=D0=BC?= =?UTF-8?q?=D0=BB=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/advanced/ac-energolux-bern.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml index 1db3349..2d2dfba 100644 --- a/examples/advanced/ac-energolux-bern.yaml +++ b/examples/advanced/ac-energolux-bern.yaml @@ -65,23 +65,23 @@ climate: show_action: true display_inverted: false indoor_temperature: - name: ${upper_devicename} AC Indoor Temperature + name: ${upper_devicename} Indoor Temperature id: ${low_devicename}_indoor_temp internal: false outdoor_temperature: - name: ${upper_devicename} AC Outdoor Temperature + name: ${upper_devicename} Outdoor Temperature id: ${low_devicename}_outdoor_temp internal: false outbound_temperature: - name: ${upper_devicename} AC Colant Outbound Temperature + name: ${upper_devicename} Colant Outbound Temperature id: ${low_devicename}_outbound_temp internal: false inbound_temperature: - name: ${upper_devicename} AC Colant Inbound Temperature + name: ${upper_devicename} Colant Inbound Temperature id: ${low_devicename}_inbound_temp internal: false strange_temperature: - name: ${upper_devicename} AC Strange Temperature + name: ${upper_devicename} Strange Temperature id: ${low_devicename}_strange_temp internal: false display_state: From 33afa263dbc88b577586434f8354fe01c8d9a76e Mon Sep 17 00:00:00 2001 From: GrKoR Date: Wed, 25 May 2022 23:31:24 +0300 Subject: [PATCH 4/8] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20acti?= =?UTF-8?q?on=20=D0=B4=D0=BB=D1=8F=20=D0=B8=D0=BD=D0=B6=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/automation.h | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index d8d9565..a62fb27 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -17,7 +17,7 @@ namespace aux_ac { protected: AirCon *ac_; - }; + }; template class AirConDisplayOnAction : public Action @@ -29,7 +29,37 @@ namespace aux_ac { 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 06e3d7e65e08f623f546c913e1e649f10d202e6b Mon Sep 17 00:00:00 2001 From: GrKoR Date: Wed, 25 May 2022 23:44:10 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20?= =?UTF-8?q?=D0=B2=20climate.py=20action=20=D0=B4=D0=BB=D1=8F=20=D0=B8?= =?UTF-8?q?=D0=BD=D0=B6=D0=B5=D0=BD=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/climate.py | 57 ++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index fe51809..a3c9676 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_CUSTOM_FAN_MODES, CONF_CUSTOM_PRESETS, CONF_INTERNAL, + CONF_DATA, CONF_SUPPORTED_MODES, CONF_SUPPORTED_SWING_MODES, CONF_SUPPORTED_PRESETS, @@ -38,9 +39,6 @@ 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' @@ -64,6 +62,7 @@ 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, @@ -100,6 +99,15 @@ CUSTOM_PRESETS = { } 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 @@ -279,11 +287,48 @@ DISPLAY_ACTION_SCHEMA = maybe_simple_id( ) @automation.register_action("aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA) -async def switch_toggle_to_code(config, action_id, template_arg, args): +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 switch_toggle_to_code(config, action_id, template_arg, args): +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) \ No newline at end of file + 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 7c9ee25010a20dad7c1596c3bbe481553abe1960 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 26 May 2022 02:45:33 +0300 Subject: [PATCH 6/8] =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=20=D0=B2?= =?UTF-8?q?=D1=8B=D1=87=D0=B8=D1=89=D0=B0=D1=82=D1=8C=20=D0=B8=D0=BD=D1=84?= =?UTF-8?q?=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BE=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=D0=B5,=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=83=D1=8E=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=88=D1=83=20=D0=B2=20GitHub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/aux_ac.h | 216 +++++++++++++------------------------ 1 file changed, 74 insertions(+), 142 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 71b69f6..d3849f1 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -112,6 +112,7 @@ enum acsm_state : uint8_t { #define AC_PACKET_TIMEOUT 150 // 150 мсек - отработка буфера UART за раз, 600 мсек - отработка буфера UART по 1 байту за вызов loop // типы пакетов +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_types #define AC_PTYPE_PING 0x01 // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает #define AC_PTYPE_CMD 0x06 // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита #define AC_PTYPE_INFO 0x07 // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля @@ -129,25 +130,20 @@ enum acsm_state : uint8_t { #define AC_PACKET_ANSWER 0x80 // признак ответа wifi-модуля // заголовок пакета +// описание тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_header 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; // не расшифрован + uint8_t start_byte = AC_PACKET_START_BYTE; + uint8_t _unknown1; + uint8_t packet_type; // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_header_2 + uint8_t wifi; + uint8_t ping_answer_01; + uint8_t _unknown2; + uint8_t body_length; + uint8_t _unknown3; }; // CRC пакета +// описание тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_crc union packet_crc_t { uint16_t crc16; uint8_t crc[2]; @@ -163,76 +159,37 @@ struct packet_t { }; // тело ответа на пинг +// описание тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_type_ping 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 + 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 }; // тело большого информационного пакета +// описание тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21 struct packet_big_info_body_t { - uint8_t byte_01; // всегда 0x01 - uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x21); - // пакет может рассылаться и в дежурном режиме (без запроса со стороны wifi-модуля) - // в этом случае тут могут быть значения, отличные от 0x21 - uint8_t byte_C0; // не расшифрован, всегда 0xC0 11000000 - // для RoyalClima18HNI: всегда 0xE0 11100000 - // Brokly: для Energolux Bern: 0xE0; иногда, с равными промежутками во времени проскакивает )xE4 - // предполагаю, что это байт конфига кондиционера (5 бит - инвертер), (2 бит - периодический мпульсный сигнал,период пимерно 500 сек) + uint8_t byte_01; + uint8_t cmd_answer; // особенности тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_2x + uint8_t config; // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_conf + // БАЙТ 3 + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_mode bool power:1; bool sleep:1; bool v_louver:1; bool h_louver:1; bool louvers_on:1; - 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 - // 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 LouvON LouH LouV 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} + uint8_t mode:3; // #define AC_BIG_MASK_MODE b11100000 + // enum { AC_BIG_MODE_DRY = 0x40, // 0100 0000 + // AC_BIG_MODE_COOL = 0x20, // 0010 0000 + // AC_BIG_MODE_HEAT = 0x80, // 1000 0000 + // AC_BIG_MODE_FAN = 0xC0} // 1100 0000 // #define AC_BIG_MASK_POWER b00000001 // #define AC_BIG_MASK_LOUVERS_ON b00010000 // #define AC_BIG_MASK_LOUVERS_H b00000100 @@ -240,21 +197,22 @@ struct packet_big_info_body_t { // #define AC_BIG_MASK_LOUVERS_L b00001000 // #define AC_BIG_MASK_SLEEP b00000010 // #define AC_BIG_MASK_COOL b00100000 - // + // БАЙТ 4 + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_frst uint8_t reserv40:4; // 8 bool needDefrost:1; // 5 бит начало разморозки(накопление тепла) bool defrostMode:1; // 6 бит режим разморозки внешнего блока (прогрев испарителя) bool reserv41:1; // для RoyalClima18HNI: режим разморозки внешнего блока - 0x20, в других случаях 0x00 bool cleen:1; // 8 бит CLEAN + // БАЙТ5 - uint8_t realFanSpeed:3; // Brokly: Energolux Bern - подтверждаю, ВАЖНО !!!! Это реальная скорость фена - uint8_t reserv30:5; // та которая в данный момент. Например может быть установлен нагрев со скоростью - // вентилятора HI, но кондей еще не произвел достаточно тепла, и крутит на LOW, - // Тут будет отображаться LOW - // fanSpeed: OFF=0x00, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07; режим CLEAN=0x01 - // в дежурных пакетах тут похоже что-то другое + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_fspd + uint8_t realFanSpeed:3; + uint8_t reserv30:5; + // БАЙТ6 + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_fpwm bool reserv60:1; uint8_t fanPWM:7; // скорость шима вентилятора // 126...128 - turbo @@ -264,6 +222,7 @@ struct packet_big_info_body_t { // 0 - off // // БАЙТ7 + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_tint uint8_t ambient_temperature_int; // Brokly: ПОДТВЕРЖДАЮ это точно показания датчика под крышкой внутреннего блока, // физически доступен для пользователя // целая часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы @@ -276,95 +235,68 @@ struct packet_big_info_body_t { // А значит это термодатчики внутри внутреннего блока !!!!!! // БАЙТ8 - 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 градусов Цельсия. + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_int + uint8_t in_temperature_int; + // БАЙТ9 - uint8_t zero3; // Brokly: полностью повторяет значение 8 байта + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_int2 + uint8_t zero3; // БАЙТ10 - uint8_t zero4; // Brokly: полностью повторяет значение 8 байта + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_int3 + uint8_t zero4; // БАЙТ11 - uint8_t zero5; // всегда = 100 (0x64) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b19 + uint8_t zero5; // БАЙТ12 - uint8_t outdoor_temperature; // Brokly Energolux Bern: Внешняя температура формула T=БАЙТ12 - 0x20 - // Датчик на радиаторе внешнего блока, доступен для пользователя, без разборки блока - // температура внешнего теплообменника влияет на это значение (при работе на обогрев - понижает, при охлаждении или при разморозке - повышает) - // для RoyalClima18HNI: похоже на какую-то температуру, точно неизвестно + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b20 + uint8_t outdoor_temperature; // БАЙТ13 - uint8_t out_temperature_int; // всегда 0x00 - // для RoyalClima18HNI: 0x20 - // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ13 - 0x20 - // При охлаждении растет, при нагреве падает, можно делать вывод о режиме COOL или HEAT - // ПОХОЖЕ НА ТЕМПЕРАТУРУ ОБРАТКИ !!! + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b21 + uint8_t out_temperature_int; // БАЙТ14 - uint8_t strange_temperature_int;// всегда 0x00 - // для RoyalClima18HNI: 0x20 - // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ14 - 0x20 - // показания РАСТЕТ ПРИ ВКЛЮЧЕНИИ ИНВЕРТОРА, при выключении падают до комнатной - // от режима охлаждения или нагрева не зависит !!! - + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b22 + uint8_t strange_temperature_int; // БАЙТ15 - uint8_t zero9; // всегда 0x00, Brokly: Energolux Bern, всегда 0x39 111001 + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b23 + uint8_t zero9; + // БАЙТ16 - uint8_t invertor_power; // МОщность инвертера (Brokly: подтверждаю) - // для RoyalClima18HNI: мощность инвертора (от 0 до 100) в % - // например, разморозка внешнего блока происходит при 80% + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b24 + uint8_t invertor_power; + // БАЙТ17 - uint8_t zero11; // всегда 0x00 - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 22, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика достаточно плавна + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b25 + uint8_t zero11; // БАЙТ18 - uint8_t zero13; // - // Brokly: Energolux Bern : наложение на показания инвертора (от 144 до 174, когда инвертор отключен - // показания немного скачут в районе 149...154, при включении инвертора быстро растет, при выключении - // моментально падает до 149...154, бывают опускания ниже этих значений до 144, чаще в момент первоначального - // включения инвертора, а потом вверх, не всегда. При включении уходит в 0 на одну посылку + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b26 + uint8_t zero13; // БАЙТ19 - uint8_t zero12; // Brokly: Energolux Bern : включение 144 -> 124 -> 110 далее все время держим 110 + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b27 + uint8_t zero12; // БАЙТ20 - uint8_t zero14; // - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 45, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика дрожащая нестабильная - // колебания в районе +-2...4 единицы + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b28 + uint8_t zero14; + // БАЙТ21 - uint8_t zero15; // всегда 0x00 Brokly: подтверждаю + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b29 + uint8_t zero15; // БАЙТ22 - uint8_t zero16; // всегда 0x00 Brokly: подтверждаю + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b30 + uint8_t zero16; // БАЙТ23 - uint8_t ambient_temperature_frac:4; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // подробнее смотреть ambient_temperature_int - // для RoyalClima18HNI: старшие 4 бита - 0x2 + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b31 + uint8_t ambient_temperature_frac:4; uint8_t reserv023:4; }; From 28378cecc443ce2b3bf334cac45c07b42220a89b Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 13:11:19 +0300 Subject: [PATCH 7/8] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B0=20=D1=82=D0=B5=D0=BC=D1=83=20?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=80=D1=82/=D1=81=D1=82=D0=BE=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2.=20=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B0=20=D1=81=D0=B2?= =?UTF-8?q?=D0=BE=D0=B8=D1=85=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA.=20?= =?UTF-8?q?=D0=9E=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Исправил ошибки при сохранении пресетов - Добавил опцию отключения сохранения пресетов - Исправил положение входящей температуры в большом пакете - Добавил флаг определения типа кондиционера Инвертор или Старт/Стоп - Для инверторов поправил процедуру визуализации состояний, для старт/стопов написал новую. Теперь старт/стопы должны корректно отображать текущий режим работы. Нужно тестировать, но я не могу. ЖДУ НАЙДЕНЫХ ОШИБОК ! --- components/aux_ac/automation.h | 34 +-- components/aux_ac/aux_ac.h | 422 +++++++++++++++++++++------------ 2 files changed, 277 insertions(+), 179 deletions(-) diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index a62fb27..d8d9565 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -17,7 +17,7 @@ namespace aux_ac { protected: AirCon *ac_; - }; + }; template class AirConDisplayOnAction : public Action @@ -29,37 +29,7 @@ namespace aux_ac { 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 diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index d3849f1..9cbd09c 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -56,7 +56,7 @@ public: static const uint32_t AC_STATES_REQUEST_INTERVAL; }; -const std::string Constants::AC_FIRMWARE_VERSION = "0.2.3"; +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"; @@ -112,7 +112,6 @@ enum acsm_state : uint8_t { #define AC_PACKET_TIMEOUT 150 // 150 мсек - отработка буфера UART за раз, 600 мсек - отработка буфера UART по 1 байту за вызов loop // типы пакетов -// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_types #define AC_PTYPE_PING 0x01 // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает #define AC_PTYPE_CMD 0x06 // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита #define AC_PTYPE_INFO 0x07 // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля @@ -130,20 +129,25 @@ enum acsm_state : uint8_t { #define AC_PACKET_ANSWER 0x80 // признак ответа wifi-модуля // заголовок пакета -// описание тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_header struct packet_header_t { - uint8_t start_byte = AC_PACKET_START_BYTE; - uint8_t _unknown1; - uint8_t packet_type; // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_header_2 - uint8_t wifi; - uint8_t ping_answer_01; - uint8_t _unknown2; - uint8_t body_length; - uint8_t _unknown3; + 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 пакета -// описание тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_crc union packet_crc_t { uint16_t crc16; uint8_t crc[2]; @@ -159,37 +163,78 @@ struct packet_t { }; // тело ответа на пинг -// описание тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_type_ping 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 + 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 }; // тело большого информационного пакета -// описание тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21 struct packet_big_info_body_t { - uint8_t byte_01; - uint8_t cmd_answer; // особенности тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_2x - uint8_t config; // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_conf - + 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 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_mode bool power:1; bool sleep:1; bool v_louver:1; bool h_louver:1; bool louvers_on:1; - uint8_t mode:3; // #define AC_BIG_MASK_MODE b11100000 - // enum { AC_BIG_MODE_DRY = 0x40, // 0100 0000 - // AC_BIG_MODE_COOL = 0x20, // 0010 0000 - // AC_BIG_MODE_HEAT = 0x80, // 1000 0000 - // AC_BIG_MODE_FAN = 0xC0} // 1100 0000 + 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 + // 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 LouvON LouH LouV 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_POWER b00000001 // #define AC_BIG_MASK_LOUVERS_ON b00010000 // #define AC_BIG_MASK_LOUVERS_H b00000100 @@ -197,22 +242,36 @@ struct packet_big_info_body_t { // #define AC_BIG_MASK_LOUVERS_L b00001000 // #define AC_BIG_MASK_SLEEP b00000010 // #define AC_BIG_MASK_COOL b00100000 - + // // БАЙТ 4 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_frst 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 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_fspd - uint8_t realFanSpeed:3; - uint8_t reserv30: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 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_fpwm bool reserv60:1; uint8_t fanPWM:7; // скорость шима вентилятора // 126...128 - turbo @@ -222,7 +281,6 @@ struct packet_big_info_body_t { // 0 - off // // БАЙТ7 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_tint uint8_t ambient_temperature_int; // Brokly: ПОДТВЕРЖДАЮ это точно показания датчика под крышкой внутреннего блока, // физически доступен для пользователя // целая часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы @@ -235,68 +293,95 @@ struct packet_big_info_body_t { // А значит это термодатчики внутри внутреннего блока !!!!!! // БАЙТ8 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_int - uint8_t in_temperature_int; + uint8_t zero3; // Brokly: полностью повторяет значение 9 байта // БАЙТ9 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_int2 - uint8_t zero3; - + 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 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_int3 - uint8_t zero4; + uint8_t zero4; // Brokly: полностью повторяет значение 9 байта // БАЙТ11 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b19 - uint8_t zero5; + uint8_t zero5; // всегда = 100 (0x64) // БАЙТ12 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b20 - uint8_t outdoor_temperature; + uint8_t outdoor_temperature; // Brokly Energolux Bern: Внешняя температура формула T=БАЙТ12 - 0x20 + // Датчик на радиаторе внешнего блока, доступен для пользователя, без разборки блока + // температура внешнего теплообменника влияет на это значение (при работе на обогрев - понижает, при охлаждении или при разморозке - повышает) + // для RoyalClima18HNI: похоже на какую-то температуру, точно неизвестно // БАЙТ13 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b21 - uint8_t out_temperature_int; + uint8_t out_temperature_int; // всегда 0x00 + // для RoyalClima18HNI: 0x20 + // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ13 - 0x20 + // При охлаждении растет, при нагреве падает, можно делать вывод о режиме COOL или HEAT + // ПОХОЖЕ НА ТЕМПЕРАТУРУ ОБРАТКИ !!! // БАЙТ14 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b22 - uint8_t strange_temperature_int; + uint8_t strange_temperature_int;// всегда 0x00 + // для RoyalClima18HNI: 0x20 + // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ14 - 0x20 + // показания РАСТЕТ ПРИ ВКЛЮЧЕНИИ ИНВЕРТОРА, при выключении падают до комнатной + // от режима охлаждения или нагрева не зависит !!! + // БАЙТ15 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b23 - uint8_t zero9; - + uint8_t zero9; // всегда 0x00, Brokly: Energolux Bern, всегда 0x39 111001 // БАЙТ16 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b24 - uint8_t invertor_power; - + uint8_t invertor_power; // МОщность инвертера (Brokly: подтверждаю) + // для RoyalClima18HNI: мощность инвертора (от 0 до 100) в % + // например, разморозка внешнего блока происходит при 80% // БАЙТ17 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b25 - uint8_t zero11; + uint8_t zero11; // всегда 0x00 + // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 22, когда инвертор отключен и тут 0 + // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика достаточно плавна // БАЙТ18 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b26 - uint8_t zero13; + uint8_t zero13; // + // Brokly: Energolux Bern : наложение на показания инвертора (от 144 до 174, когда инвертор отключен + // показания немного скачут в районе 149...154, при включении инвертора быстро растет, при выключении + // моментально падает до 149...154, бывают опускания ниже этих значений до 144, чаще в момент первоначального + // включения инвертора, а потом вверх, не всегда. При включении уходит в 0 на одну посылку // БАЙТ19 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b27 - uint8_t zero12; + uint8_t zero12; // Brokly: Energolux Bern : включение 144 -> 124 -> 110 далее все время держим 110 // БАЙТ20 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b28 - uint8_t zero14; - + uint8_t zero14; // + // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 45, когда инвертор отключен и тут 0 + // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика дрожащая нестабильная + // колебания в районе +-2...4 единицы // БАЙТ21 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b29 - uint8_t zero15; + uint8_t zero15; // всегда 0x00 Brokly: подтверждаю // БАЙТ22 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b30 - uint8_t zero16; + uint8_t zero16; // всегда 0x00 Brokly: подтверждаю // БАЙТ23 - // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b31 - uint8_t ambient_temperature_frac:4; + uint8_t ambient_temperature_frac:4; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы + // подробнее смотреть ambient_temperature_int + // для RoyalClima18HNI: старшие 4 бита - 0x2 uint8_t reserv023:4; }; @@ -444,7 +529,7 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // настройка усреднения фильтра температуры. Это значение - взнос нового измерения // в усредненные показания в процентах -#define OUTDOOR_FILTER_PESCENT 5 +#define OUTDOOR_FILTER_PESCENT 1 /** команда для кондиционера * @@ -468,11 +553,12 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_mildew mildew;\ ac_timer timer;\ uint8_t timer_hours;\ - uint8_t timer_minutes; + uint8_t timer_minutes;\ + bool temp_target_matter\ // чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше // из-за такого приема нужно контролировать размер копируемых данных руками -#define AC_COMMAND_BASE_SIZE 20 +#define AC_COMMAND_BASE_SIZE 21 struct ac_command_t { /* @@ -491,18 +577,17 @@ struct ac_command_t { ac_timer timer; uint8_t timer_hours; uint8_t timer_minutes; - ac_health_error health_error; // ошибка ионизатора float temp_target; + bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную */ AC_COMMAND_BASE; ac_health_error health_error; - bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную float temp_ambient; // внутренняя температура int8_t temp_outdoor; // внешняя температура int8_t temp_inbound; // температура входящая int8_t temp_outbound; // температура исходящая int8_t temp_strange; // непонятная температура, понаблюдаем - ac_realFan realFanSpeed; // текущая скорость вентилятора + ac_realFan realFanSpeed; // текущая скорость вентилятора uint8_t invertor_power; // мощность инвертора uint8_t pressure; // предположительно давление bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) @@ -597,7 +682,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // массив для сохранения данных глобальных персетов ac_save_command_t global_presets[POS_MODE_OFF+1]; - #if defined(ESP32) // тут будем хранить данные глобальных пресетов во флеше // ВНИМАНИЕ на данный момент 22.05.22 ESPHOME 20022.5.0 имеет ошибку @@ -605,9 +689,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // из-за этого сохранение в энергонезависимую память не работает !!! ESPPreferenceObject storage = global_preferences->make_preference(this->get_object_id_hash(), true); #endif + // настройка-ключ, для включения сохранения - восстановления настроек каждого + // режима работы в отдельности, то есть каждый режим работы имеет свои настройки + // температуры, шторок, скорости вентилятора, пресетов + bool _store_settings = false; // флаги для сохранения пресетов - bool new_command_set = false; // флаг отправки новой команды - bool new_command_reply = false; // флаг получения ответа на новую команду + bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено // время последнего запроса статуса у кондея uint32_t _dataMillis; @@ -623,6 +710,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // если тут false, то 1 в соответствующем бите включает дисплей, а 0 выключает. // если тут true, то 1 потушит дисплей, а 0 включит. bool _display_inverted = false; + + // флаг типа кондиционера инвертор - true, ON/OFF - false, начальная установка false + // в таком режиме точность и скорость определения реального состояния системы для инвертора, + // будет работать, но будет ниже, переменная устанавливается при первом получении большого пакета; + // если эта переменная установлена, то режим работы не инверторного кондиционера будет распознаваться + // как "в простое" (IDLE) + bool _is_invertor = false; // поддерживаемые кондиционером опции std::set _supported_modes{}; @@ -1207,6 +1301,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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); @@ -1257,10 +1354,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // уведомляем об изменении статуса сплита if (stateChangedFlag) { stateChanged(); - if(new_command_set){ // это ответ на исходящий пакет - new_command_set = false; - new_command_reply = true; //флаг о получении ответа, нужно сохранить пресет, после полуучения ответа от кондея - } } break; } @@ -1851,7 +1944,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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); // просто копируем из массива - cmd->temp_target_matter = true; // флаг изменения температуры _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); @@ -1862,10 +1954,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // запись данных в массив персетов 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){ // содержимое пресетов разное + 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("Try save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + _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); @@ -1873,7 +1965,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _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: @@ -1916,39 +2010,81 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return (_sequence[0].item_type != AC_SIT_NONE); } - // вызывается, если параметры кондиционера изменились, ДЛЯ ПУБЛИКАЦИИ + // вызывается для обновления отображения состояния кондиционера, ДЛЯ ПУБЛИКАЦИИ void stateChanged(){ _debugMsg(F("State changed, let's publish it."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - 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 if (_current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_OFF ){ // кулер чуть вертится - this->action = climate::CLIMATE_ACTION_IDLE; // кондей в простое + 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 { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + 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(millis()-timerInv > 2000){ // инвертор включен, но нужно дождаться реакции на его включение - int16_t delta=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE ){ //медленное вращение - if(delta > 0){ //холодный радиатор - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else { // теплый радиатор, видимо переходный режим + } 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; } - } else if(delta < -2){ // входящая температура выше комнатной, быстрый фен - ОБОГРЕВ - this->action = climate::CLIMATE_ACTION_HEATING; - } else if(delta > 2){ // ниже, быстрый фен - ОХЛАЖДЕНИЕ - this->action = climate::CLIMATE_ACTION_COOLING; - } 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){ @@ -2219,9 +2355,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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", this->get_show_action() ? "true" : "false"); - ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", this->get_display_inverted() ? "true" : "false"); - + 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"); 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()) { @@ -2368,6 +2505,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { void control(const esphome::climate::ClimateCall &call) override { bool hasCommand = false; ac_command_t cmd; + _clearCommand(&cmd); // не забываем очищать, а то будет мусор // User requested mode change @@ -2378,11 +2516,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { case climate::CLIMATE_MODE_OFF: hasCommand = true; cmd.power = AC_POWER_OFF; - if(_current_ac_state.power != AC_POWER_OFF){ - load_preset(&cmd, POS_MODE_OFF); - cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать - cmd.temp_target_matter = true; - } + load_preset(&cmd, POS_MODE_OFF); + cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать this->mode = mode; break; @@ -2390,9 +2525,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_COOL; - if(_current_ac_state.mode != AC_MODE_COOL){ - load_preset(&cmd, POS_MODE_COOL); - } + load_preset(&cmd, POS_MODE_COOL); this->mode = mode; break; @@ -2400,9 +2533,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_HEAT; - if(_current_ac_state.mode != AC_MODE_HEAT){ - load_preset(&cmd, POS_MODE_HEAT); - } + load_preset(&cmd, POS_MODE_HEAT); this->mode = mode; break; @@ -2410,11 +2541,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_AUTO; - if(_current_ac_state.mode != AC_MODE_AUTO){ - load_preset(&cmd, POS_MODE_AUTO); - cmd.temp_target = 25; // зависимость от режима HEAT_COOL - cmd.temp_target_matter = true; - } + 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; @@ -2423,11 +2552,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_FAN; - if(_current_ac_state.mode != AC_MODE_FAN){ - load_preset(&cmd, POS_MODE_FAN); - cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN - cmd.temp_target_matter = true; - } + 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){ @@ -2440,9 +2567,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_DRY; - if(_current_ac_state.mode != AC_MODE_DRY){ - load_preset(&cmd, POS_MODE_DRY); - } + load_preset(&cmd, POS_MODE_DRY); cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY this->mode = mode; @@ -2738,7 +2863,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (hasCommand) { commandSequence(&cmd); this->publish_state(); // Publish updated state - new_command_set = true; // флаг отправки новой команды, для процедуры сохранения пресетов + _new_command_set = _store_settings; // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка } } @@ -2976,6 +3101,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; } @@ -2994,8 +3122,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (!get_hw_initialized()) return; // контролируем сохранение пресета - if(new_command_reply){ //нужно сохранить пресет - new_command_reply = false; + if(_new_command_set){ //нужно сохранить пресет + _new_command_set = false; save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов } From b2dadff6b7c99223be142737d7e0e6cf7cbd007f Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 13:13:08 +0300 Subject: [PATCH 8/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D1=83=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D1=8E=D1=89=D1=83?= =?UTF-8?q?=D1=8E=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=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 --- examples/advanced/ac-energolux-bern.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml index 2d2dfba..69035c3 100644 --- a/examples/advanced/ac-energolux-bern.yaml +++ b/examples/advanced/ac-energolux-bern.yaml @@ -64,6 +64,7 @@ climate: period: 7s show_action: true display_inverted: false + store_settings: true indoor_temperature: name: ${upper_devicename} Indoor Temperature id: ${low_devicename}_indoor_temp