From d74c8c59abceb7d91939dc3d447690bd981ac924 Mon Sep 17 00:00:00 2001 From: Brokly Date: Tue, 24 May 2022 22:08:39 +0300 Subject: [PATCH 01/74] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=B8=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D0=B4=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 02/74] =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=88=D0=B8=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8?= =?UTF-8?q?=D0=B3=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D1=80=D0=B0=D0=B9=D0=BD?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=BE=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 03/74] =?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 04/74] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20ac?= =?UTF-8?q?tion=20=D0=B4=D0=BB=D1=8F=20=D0=B8=D0=BD=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=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/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 05/74] =?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 06/74] =?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 07/74] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B0=20=D1=82=D0=B5=D0=BC?= =?UTF-8?q?=D1=83=20=D1=81=D1=82=D0=B0=D1=80=D1=82/=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2.=20=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=81=D0=B2=D0=BE=D0=B8=D1=85=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE?= =?UTF-8?q?=D0=BA.=20=D0=9E=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B0=D0=B2=D1=82=D0=BE=D1=81=D0=BE=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=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 08/74] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D1=83?= =?UTF-8?q?=20=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 From 168819a17e46e4d2baa6eaf9e4049a7e7ef723df Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 26 May 2022 14:31:20 +0300 Subject: [PATCH 09/74] Brokly changes --- components/aux_ac/climate.py | 127 +++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 7 deletions(-) diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index c6ba1d1..a3c9676 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -12,9 +12,19 @@ from esphome.const import ( 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 +39,22 @@ CODEOWNERS = ["@GrKoR"] DEPENDENCIES = ["climate", "uart"] AUTO_LOAD = ["sensor", "binary_sensor"] -CONF_SUPPORTED_MODES = 'supported_modes' -CONF_SUPPORTED_SWING_MODES = 'supported_swing_modes' -CONF_SUPPORTED_PRESETS = 'supported_presets' CONF_SHOW_ACTION = 'show_action' CONF_INDOOR_TEMPERATURE = 'indoor_temperature' +CONF_OUTDOOR_TEMPERATURE = 'outdoor_temperature' +ICON_OUTDOOR_TEMPERATURE = 'mdi:home-thermometer-outline' +CONF_INBOUND_TEMPERATURE = 'inbound_temperature' +ICON_INBOUND_TEMPERATURE = 'mdi:thermometer-plus' +CONF_OUTBOUND_TEMPERATURE = 'outbound_temperature' +ICON_OUTBOUND_TEMPERATURE = 'mdi:thermometer-minus' +CONF_STRANGE_TEMPERATURE = 'strange_temperature' +ICON_STRANGE_TEMPERATURE = 'mdi:thermometer-lines' CONF_DISPLAY_STATE = 'display_state' - +CONF_INVERTOR_POWER = 'invertor_power' +CONF_DEFROST_STATE = 'defrost_state' +ICON_DEFROST = "mdi:snowflake-melt" CONF_DISPLAY_INVERTED = 'display_inverted' -ICON_DISPLAY = "mdi: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) @@ -102,6 +119,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 +142,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 +194,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 +230,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 +280,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), @@ -185,6 +297,7 @@ async def display_on_to_code(config, action_id, template_arg, args): 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), From 801cd9dc32566ec3a177de272044c2fe0dc62eeb Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 15:44:50 +0300 Subject: [PATCH 10/74] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B8=D0=B7=20=D1=80=D0=BE=D0=B4=D0=B8=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8C=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D0=BE=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/automation.h | 4 +- components/aux_ac/aux_ac.h | 1624 ++++++++++++++++++++++---------- components/aux_ac/climate.py | 180 ++-- 3 files changed, 1275 insertions(+), 533 deletions(-) diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index a62fb27..7a31105 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 @@ -59,7 +59,7 @@ namespace aux_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..1f0dd34 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.4"; const char *const Constants::TAG = "AirCon"; -const std::string Constants::MUTE = "mute"; -const std::string Constants::TURBO = "turbo"; -const std::string Constants::CLEAN = "clean"; -const std::string Constants::FEEL = "feel"; -const std::string Constants::HEALTH = "health"; -const std::string Constants::ANTIFUNGUS = "antifugnus"; +const std::string Constants::MUTE = "Mute"; +const std::string Constants::TURBO = "Turbo"; +const std::string Constants::CLEAN = "Clean"; +const std::string Constants::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,209 @@ struct packet_big_info_body_t { uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x21); // пакет может рассылаться и в дежурном режиме (без запроса со стороны wifi-модуля) // в этом случае тут могут быть значения, отличные от 0x21 - uint8_t byte_C0; // не расшифрован, всегда 0xC0 - // для RoyalClima18HNI: всегда 0xE0 - uint8_t unknown1; // не расшифрован, как-то связан с режимом работы сплита; как вариант, отражает режим работы - // компрессора во внешнем блоке или что-то такое, потому что иногда включение сплита не сразу приводит к изменениям в этом байте + // БАЙТ2 + uint8_t reserv20 :5; // не расшифрован, всегда 0xC0 11000000 + bool is_invertor :1; // флаг инвертора + uint8_t reserv21 :2; // для RoyalClima18HNI: всегда 0xE0 11100000 + // Brokly: для Energolux Bern: 0xE0; иногда, с равными промежутками во времени проскакивает )xE4 + // предполагаю, что это байт конфига кондиционера (5 бит - инвертер), (2 бит - периодический мпульсный сигнал,период пимерно 500 сек) + // БАЙТ 3 + bool power:1; + bool sleep:1; + bool 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-модуля в этом байте скорость работы вентилятора - // fanSpeed: OFF=0x00, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07; режим CLEAN=0x01 + // 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 + + // Для кондея старт-стоп + // x xx + // C5 11000101 + // C4 11000100 + // 85 10000101 + // 84 10000100 + // 3D 00111101 + // 3C 00111100 + // 25 00100101 + // 24 00100100 + // 5 00000101 + // 4 00000100 + + + // БАЙТ5 + uint8_t realFanSpeed:3; // Brokly: Energolux Bern - подтверждаю, ВАЖНО !!!! Это реальная скорость фена + uint8_t reserv30:5; // та которая в данный момент. Например может быть установлен нагрев со скоростью + // вентилятора HI, но кондей еще не произвел достаточно тепла, и крутит на LOW, + // Тут будет отображаться LOW + // fanSpeed: OFF=0x00, MUTE=0x01, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07 // в дежурных пакетах тут похоже что-то другое - 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 zero3; // Brokly: полностью повторяет значение 9 байта + + // БАЙТ9 + uint8_t in_temperature_int; // Brokly: скорее всего это действительно какая то температура или дельта температур + // СКОРЕЕ ВСЕГО ЭТО ТЕМПЕРАТУРА ПОДАЧИ !!!!!! При охлаждении - холодная, при нагреве теплая + + // холоднее или горячее температуры в команте. В выключеном состоянии стремится к комнатной темп. + // у меня на трех инверторных кондиционерах Energolux серии Bern + // в выключеном состоянии значение этого байта находится на уровне 57-68 (мощность 0%) + // зависит от мощности работы компрессора (измерения при 12гр на улице) + // в режиме охлаждения уменьшается и при мощности 47% = 40 / 73% = 38 + // в режиме нагрева увеличивается и при мощности 47% = 70 / 73% = 75 / 84% = 84 + // изменение этого значения более вялое, с западыванием относительно изменения мощности + // видимо является реакцией (следствием работы) на изменение мощности инвертора + // учитывая стиль записи температур имеет смысл рассматривать это значение как увеличенное на 0x20 + + + + // этот байт как-то связан с температурой во внешнем блоке. Требуются дополнительные исследования. + // При выключенном сплите характер изменения значения примерно соответствует изменению температуры на улице. + // При включенном сплите значение может очень сильно скакать. + // По схеме wiring diagram сплит-системы, во внешнем блоке есть термодатчик, отслеживающий температуру испарителя. + // Возможно, этот байт как раз и отражает изменение температуры на испарителе. + // Но я не смог разобраться, как именно перевести эти значения в градусы. + // Кроме того, зимой даже в минусовую температуру этот байт не уходит ниже 0x33 по крайней мере + // для температур в диапазоне -5..-10 градусов Цельсия. + // БАЙТ10 + uint8_t zero4; // Brokly: полностью повторяет значение 9 байта + + // БАЙТ11 + uint8_t zero5; // всегда = 100 (0x64) + + // БАЙТ12 + uint8_t outdoor_temperature; // Brokly Energolux Bern: Внешняя температура формула T=БАЙТ12 - 0x20 + // Датчик на радиаторе внешнего блока, доступен для пользователя, без разборки блока + // температура внешнего теплообменника влияет на это значение (при работе на обогрев - понижает, при охлаждении или при разморозке - повышает) + // для RoyalClima18HNI: похоже на какую-то температуру, точно неизвестно + + // БАЙТ13 + uint8_t out_temperature_int; // всегда 0x00 + // для RoyalClima18HNI: 0x20 + // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ13 - 0x20 + // При охлаждении растет, при нагреве падает, можно делать вывод о режиме COOL или HEAT + // ПОХОЖЕ НА ТЕМПЕРАТУРУ ОБРАТКИ !!! + + // БАЙТ14 + uint8_t strange_temperature_int;// всегда 0x00 + // для RoyalClima18HNI: 0x20 + // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ14 - 0x20 + // показания РАСТЕТ ПРИ ВКЛЮЧЕНИИ ИНВЕРТОРА, при выключении падают до комнатной + // от режима охлаждения или нагрева не зависит !!! + + + // БАЙТ15 + uint8_t zero9; // всегда 0x00, Brokly: Energolux Bern, всегда 0x39 111001 + // БАЙТ16 + uint8_t invertor_power; // МОщность инвертера (Brokly: подтверждаю) // для RoyalClima18HNI: мощность инвертора (от 0 до 100) в % // например, разморозка внешнего блока происходит при 80% - 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 zero12; // + // Brokly: Energolux Bern : наложение на показания инвертора (от 144 до 174, когда инвертор отключен + // показания немного скачут в районе 149...154, при включении инвертора быстро растет, при выключении + // моментально падает до 149...154, бывают опускания ниже этих значений до 144, чаще в момент первоначального + // включения инвертора, а потом вверх, не всегда. При включении уходит в 0 на одну посылку + + // БАЙТ19 + uint8_t zero13; // Brokly: Energolux Bern : включение 144 -> 124 -> 110 далее все время держим 110 + + // БАЙТ20 + uint8_t zero14; // + // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 45, когда инвертор отключен и тут 0 + // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика дрожащая нестабильная + // колебания в районе +-2...4 единицы + // БАЙТ21 + uint8_t zero15; // всегда 0x00 Brokly: подтверждаю + + // БАЙТ22 + uint8_t zero16; // всегда 0x00 Brokly: подтверждаю + + // БАЙТ23 + uint8_t ambient_temperature_frac:4; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы // подробнее смотреть ambient_temperature_int // для RoyalClima18HNI: старшие 4 бита - 0x2 + uint8_t reserv023:4; }; // тело малого информационного пакета @@ -282,11 +434,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 +451,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 }; +enum ac_health_status : uint8_t { AC_HEALTH_ERROR_NO = 0x00, AC_HEALTH_ERROR_ACT = 0x01, AC_HEALTH_STATUS_UNTOUCHED = 0xFF }; // целевая температура #define AC_TEMP_TARGET_INT_PART_MASK 0b11111000 #define AC_TEMP_TARGET_FRAC_PART_MASK 0b10000000 +// задержка отключения кондиционера +#define AC_TIMER_MINUTES_MASK 0b00111111 +#define AC_TIMER_HOURS_MASK 0b00011111 + +// включение таймера сна +#define AC_TIMER_MASK 0b01000000 +enum ac_timer : uint8_t {AC_TIMER_OFF = 0x00, AC_TIMER_ON = 0x40, AC_TIMER_UNTOUCHED = 0xFF}; + // основные режимы работы кондиционера #define AC_MODE_MASK 0b11100000 enum ac_mode : uint8_t { AC_MODE_AUTO = 0x00, AC_MODE_COOL = 0x20, AC_MODE_DRY = 0x40, AC_MODE_HEAT = 0x80, AC_MODE_FAN = 0xC0, AC_MODE_UNTOUCHED = 0xFF }; @@ -361,20 +524,47 @@ 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 1 + /** команда для кондиционера * * ВАЖНО! В коде используется копирование команд простым присваиванием. * Если в структуру будут введены указатели, то копирование надо будет изменить! */ + +// данные структур содержат настройку, специально вынес в макрос +#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;\ + bool temp_target_matter\ + +// чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше +// из-за такого приема нужно контролировать размер копируемых данных руками +#define AC_COMMAND_BASE_SIZE 21 + 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 +574,39 @@ struct ac_command_t { ac_fanmute fanMute; ac_display display; ac_mildew mildew; + ac_timer timer; + uint8_t timer_hours; + uint8_t timer_minutes; + float temp_target; + bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную +*/ + AC_COMMAND_BASE; + ac_health_status health_status; + float temp_ambient; // внутренняя температура + int8_t temp_outdoor; // внешняя температура + int8_t temp_inbound; // температура входящая + int8_t temp_outbound; // температура исходящая + int8_t temp_strange; // непонятная температура, понаблюдаем + ac_realFan realFanSpeed; // текущая скорость вентилятора + uint8_t invertor_power; // мощность инвертора + uint8_t pressure; // предположительно давление + bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) }; +// структура для сохранения данных +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 +614,6 @@ typedef ac_command_t ac_state_t; // текущее состояние пара //**************************************************************************************************************************************************** - - /***************************************************************************************************************************************************** * структуры и типы для последовательности команд ***************************************************************************************************************************************************** @@ -424,7 +643,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 +679,23 @@ 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 _store_settings = false; + // флаги для сохранения пресетов + bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено + // время последнего запроса статуса у кондея uint32_t _dataMillis; // периодичность обновления статуса кондея, по дефолту AC_STATES_REQUEST_INTERVAL @@ -473,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{}; @@ -512,7 +756,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // входящий и исходящий пакеты packet_t _inPacket; packet_t _outPacket; - + // пакет для тестирования всякой фигни packet_t _outTestPacket; @@ -671,7 +915,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; } } - + // заполняет структуру команды нейтральными значениями void _clearCommand(ac_command_t * cmd){ cmd->clean = AC_CLEAN_UNTOUCHED; @@ -688,12 +932,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); @@ -1036,9 +1287,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 +1304,60 @@ 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); _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(); + } break; } @@ -1133,16 +1425,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 +1467,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 +1621,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 +1658,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 +1704,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_status != AC_HEALTH_STATUS_UNTOUCHED){ + pack->body[10] = (pack->body[10] & ~AC_HEALTH_STATUS_MASK) | cmd->health_status; + } + // дисплей if (cmd->display != AC_DISPLAY_UNTOUCHED){ pack->body[12] = (pack->body[12] & ~AC_DISPLAY_MASK) | cmd->display; @@ -1414,6 +1723,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]); @@ -1583,7 +1894,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; @@ -1598,14 +1909,17 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } // сенсоры, отображающие параметры сплита - //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 +1940,57 @@ 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); // просто копируем из массива + _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } else { + _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } + } + } + // запись данных в массив персетов + void save_preset(ac_command_t* cmd){ + uint8_t num_preset = get_num_preset(cmd); + if(memcmp(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE) != 0){ // содержимое пресетов разное + memcpy(&(global_presets[num_preset]), cmd, AC_COMMAND_BASE_SIZE); // копируем пресет в массив + #if defined(ESP32) + _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + if(storage.save(global_presets)){ + if(!global_preferences->sync()) // сохраняем все пресеты + _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } else { + _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } + #endif + } else { + _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } + } + public: // инициализация объекта void initAC(esphome::uart::UARTComponent *parent = nullptr){ @@ -1636,9 +2000,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _clearPacket(&_outTestPacket); _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - _outTestPacket.header->wifi = AC_PACKET_ANSWER; - - _setStateMachineState(ACSM_IDLE); + _outTestPacket.header->wifi = AC_PACKET_ANSWER; + + _setStateMachineState(ACSM_IDLE); _ac_serial = parent; _hw_initialized = (_ac_serial != nullptr); _has_connection = false; @@ -1656,8 +2020,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 +2035,295 @@ 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__); - + if(_is_invertor){ // анализ режима для инвертора, точнее потому что использует показания мощности инвертора + static uint32_t timerInv = 0; + if(_current_ac_state.invertor_power == 0){ // инвертор выключен + timerInv = millis(); + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && + _current_ac_state.power == AC_POWER_OFF ){ // внутренний кулер остановлен, кондей выключен + this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает + } else { + int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; + if (delta_temp > 0 && delta_temp < 2 && + (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else if (_current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || + _current_ac_state.realFanSpeed == AC_REAL_FAN_OFF ){ // кулер чуть вертится + this->action = climate::CLIMATE_ACTION_IDLE; // кондей в простое + } else { + this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + } + } + } else if(millis()-timerInv > 2000){ // инвертор включен, но нужно дождаться реакции на его включение + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE ){ //медленное вращение + if(_current_ac_state.temp_ambient - _current_ac_state.temp_inbound > 0){ //холодный радиатор + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else { // теплый радиатор, видимо переходный режим + this->action = climate::CLIMATE_ACTION_IDLE; + } + } else { + int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; + if(delta_temp < -2){ // входящая температура выше комнатной, быстрый фен - ОБОГРЕВ + this->action = climate::CLIMATE_ACTION_HEATING; + } else if(delta_temp > 2){ // ниже, быстрый фен - ОХЛАЖДЕНИЕ + this->action = climate::CLIMATE_ACTION_COOLING; + } else { // просто вентиляция + this->action = climate::CLIMATE_ACTION_IDLE; + } + } + } else { + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE){ + this->action = climate::CLIMATE_ACTION_IDLE; + } else { + this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + } + } + } else { + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && + _current_ac_state.power == AC_POWER_OFF){ + this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает + } else { + int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; // разность температуры между комнатной и входящей + if (delta_temp > 0 && delta_temp < 2 && + (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else if(_current_ac_state.realFanSpeed != AC_REAL_FAN_OFF && + _current_ac_state.realFanSpeed != AC_REAL_FAN_MUTE){ + if(delta_temp > 2){ + this->action = climate::CLIMATE_ACTION_COOLING; + } else if(delta_temp < -2){ + this->action = climate::CLIMATE_ACTION_HEATING; + } else { + this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + } + } else { + this->action = climate::CLIMATE_ACTION_IDLE; + } + } + } + _debugMsg(F("Action mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->action); /*************************** POWER & MODE ***************************/ - 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 +2332,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 +2358,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 +2366,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } else { sensor_display_->publish_state(false); } - break; + break; default: // могут быть и другие состояния, поэтому так - break; + break; } + } } // вывод в дебаг текущей конфигурации компонента @@ -1936,8 +2380,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()) { @@ -1956,6 +2402,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,13 +2524,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } this->dump_traits_(Constants::TAG); - } // вызывается пользователем из интерфейса ESPHome или Home Assistant void control(const esphome::climate::ClimateCall &call) override { bool hasCommand = false; ac_command_t cmd; + _clearCommand(&cmd); // не забываем очищать, а то будет мусор // User requested mode change @@ -1986,6 +2541,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { case climate::CLIMATE_MODE_OFF: hasCommand = true; cmd.power = AC_POWER_OFF; + load_preset(&cmd, POS_MODE_OFF); + cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать this->mode = mode; break; @@ -1993,6 +2550,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_COOL; + load_preset(&cmd, POS_MODE_COOL); this->mode = mode; break; @@ -2000,6 +2558,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_HEAT; + load_preset(&cmd, POS_MODE_HEAT); this->mode = mode; break; @@ -2007,6 +2566,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_AUTO; + load_preset(&cmd, POS_MODE_AUTO); + cmd.temp_target = 25; // зависимость от режима HEAT_COOL + cmd.temp_target_matter = true; + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL this->mode = mode; break; @@ -2014,6 +2577,14 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_FAN; + load_preset(&cmd, POS_MODE_FAN); + cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN + cmd.temp_target_matter = true; + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN + cmd.sleep = AC_SLEEP_OFF; + if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ + cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN + } this->mode = mode; break; @@ -2021,6 +2592,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_DRY; + load_preset(&cmd, POS_MODE_DRY); + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY + cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY this->mode = mode; break; @@ -2028,210 +2602,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_status = 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_status = 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_status = 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_status = 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 = _store_settings; // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка } } @@ -2530,19 +3187,33 @@ 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; } 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_set){ //нужно сохранить пресет + _new_command_set = false; + save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов + } + + // отрабатываем состояния конечного автомата switch (_ac_state) { case ACSM_RECEIVING_PACKET: // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны @@ -2571,12 +3242,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..5c98437 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 ( @@ -34,10 +43,22 @@ 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" +CONF_STORE_SETTINGS = 'store_settings' + aux_ac_ns = cg.esphome_ns.namespace("aux_ac") AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) @@ -45,7 +66,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 +102,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 +113,19 @@ 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_STORE_SETTINGS, default="false"): cv.boolean, + cv.Optional(CONF_DEFROST_STATE, default="false"): cv.boolean, + cv.Optional(CONF_INVERTOR_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_POWER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, + } + ), cv.Optional(CONF_INDOOR_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, icon=ICON_THERMOMETER, @@ -113,6 +137,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 +189,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,15 +225,46 @@ 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])) cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED])) + cg.add(var.set_store_settings(config[CONF_STORE_SETTINGS])) if CONF_SUPPORTED_MODES in config: cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) if CONF_SUPPORTED_SWING_MODES in config: @@ -166,8 +276,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 +283,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 71a16717c42d6b2e08fdebd1c8745eb8fc94e7f9 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 15:45:45 +0300 Subject: [PATCH 11/74] =?UTF-8?q?=D0=9D=D0=B0=20=D0=B2=D1=81=D1=8F=D0=BA?= =?UTF-8?q?=D0=B8=D0=B9=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B9,=20=D0=B0?= =?UTF-8?q?=20=D0=B2=D0=B4=D1=80=D1=83=D0=B3...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/advanced/ac-energolux-bern.yaml | 144 +++++++++++++++++++++++ 1 file changed, 144 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..69035c3 --- /dev/null +++ b/examples/advanced/ac-energolux-bern.yaml @@ -0,0 +1,144 @@ +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 + store_settings: true + indoor_temperature: + name: ${upper_devicename} Indoor Temperature + id: ${low_devicename}_indoor_temp + internal: false + outdoor_temperature: + name: ${upper_devicename} Outdoor Temperature + id: ${low_devicename}_outdoor_temp + internal: false + outbound_temperature: + name: ${upper_devicename} Colant Outbound Temperature + id: ${low_devicename}_outbound_temp + internal: false + inbound_temperature: + name: ${upper_devicename} Colant Inbound Temperature + id: ${low_devicename}_inbound_temp + internal: false + strange_temperature: + name: ${upper_devicename} 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 4644ee08e22f408559c1eff68b0af40459a6ff27 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 16:10:26 +0300 Subject: [PATCH 12/74] =?UTF-8?q?=D0=A1=D0=BB=D0=B8=D0=BB/=D0=BF=D0=BE?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/automation.h | 4 +-- components/aux_ac/aux_ac.h | 26 ++++++++--------- components/aux_ac/climate.py | 52 ++++++++++++++++++++++++++++++++-- 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index 7a31105..4d137b6 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 @@ -59,7 +59,7 @@ namespace aux_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 1f0dd34..c8a53c3 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -452,13 +452,13 @@ enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTO 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_ERROR_NO = 0x00, AC_HEALTH_ERROR_ACT = 0x01, AC_HEALTH_STATUS_UNTOUCHED = 0xFF }; +enum ac_health_status : uint8_t { AC_HEALTH_STATUS_OFF = 0x00, AC_HEALTH_STATUS_ON = 0x01, AC_HEALTH_STATUS_UNTOUCHED = 0xFF }; // целевая температура #define AC_TEMP_TARGET_INT_PART_MASK 0b11111000 @@ -756,7 +756,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // входящий и исходящий пакеты packet_t _inPacket; packet_t _outPacket; - + // пакет для тестирования всякой фигни packet_t _outTestPacket; @@ -915,7 +915,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; } } - + // заполняет структуру команды нейтральными значениями void _clearCommand(ac_command_t * cmd){ cmd->clean = AC_CLEAN_UNTOUCHED; @@ -944,7 +944,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd->temp_strange = 0; cmd->realFanSpeed = AC_REAL_FAN_UNTOUCHED; }; - + // очистка буфера размером AC_BUFFER_SIZE void _clearBuffer(uint8_t * buf){ memset(buf, 0, AC_BUFFER_SIZE); @@ -1287,7 +1287,7 @@ 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(); @@ -2000,9 +2000,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _clearPacket(&_outTestPacket); _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - _outTestPacket.header->wifi = AC_PACKET_ANSWER; - - _setStateMachineState(ACSM_IDLE); + _outTestPacket.header->wifi = AC_PACKET_ANSWER; + + _setStateMachineState(ACSM_IDLE); _ac_serial = parent; _hw_initialized = (_ac_serial != nullptr); _has_connection = false; @@ -2772,7 +2772,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.sleep = AC_SLEEP_ON; cmd.iFeel = AC_IFEEL_OFF; // для логики пресетов cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_ERROR_NO; + cmd.health_status = AC_HEALTH_STATUS_OFF; this->preset = preset; } else { _debugMsg(F("SLEEP preset is suitable in COOL,HEAT and AUTO modes only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); @@ -2782,7 +2782,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // выбран пустой пресет, сбрасываем все настройки hasCommand = true; cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_ERROR_NO; + cmd.health_status = AC_HEALTH_STATUS_OFF; cmd.sleep = AC_SLEEP_OFF; // для логики пресетов cmd.iFeel = AC_IFEEL_OFF; // для логики пресетов this->preset = preset; @@ -2802,13 +2802,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.iFeel = AC_IFEEL_ON; cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_ERROR_NO; + cmd.health_status = AC_HEALTH_STATUS_OFF; 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_status = AC_HEALTH_ERROR_ACT; + cmd.health_status = AC_HEALTH_STATUS_ON; cmd.iFeel = AC_IFEEL_ON; // зависимость от health cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 5c98437..9d5796e 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, @@ -66,6 +67,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, @@ -102,6 +104,14 @@ 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 @@ -283,11 +293,47 @@ 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 53b09f20669aedc843c4282753eaf30e02278e98 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 16:32:32 +0300 Subject: [PATCH 13/74] Update automation.h --- components/aux_ac/automation.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index aa46168..ee96bca 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -17,7 +17,8 @@ namespace aux_ac { protected: AirCon *ac_; - + }; + template class AirConDisplayOnAction : public Action { @@ -31,4 +32,4 @@ namespace aux_ac { }; } // namespace aux_ac -} // namespace esphome \ No newline at end of file +} // namespace esphome From f8064f0c87bae1d9a99a3979356483ccb9c8ddaa Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 16:34:50 +0300 Subject: [PATCH 14/74] Add files via upload --- components/aux_ac/automation.h | 33 ++++++++++++- components/aux_ac/aux_ac.h | 86 ++++++++++++++++++++++++++++++---- components/aux_ac/climate.py | 12 +++-- 3 files changed, 118 insertions(+), 13 deletions(-) diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index aa46168..a62fb27 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -17,6 +17,7 @@ namespace aux_ac { protected: AirCon *ac_; + }; template class AirConDisplayOnAction : public Action @@ -28,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 diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 3b470e0..c8a53c3 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -410,7 +410,7 @@ struct packet_small_info_body_t { // биты 0..5 растут на 1 каждую минуту, возможно внутренний таймер для включения/выключения по времени uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 - uint8_t fan_turbo_and_mute; // бит 7 = режим TURBO, бит 6 - режим MUTE; остальные не известны + uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны uint8_t mode; // режим работы сплита: // AUTO : bits[7, 6, 5] = [0, 0, 0] // COOL : bits[7, 6, 5] = [0, 0, 1] @@ -757,6 +757,9 @@ 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; @@ -912,7 +915,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; } } - + // заполняет структуру команды нейтральными значениями void _clearCommand(ac_command_t * cmd){ cmd->clean = AC_CLEAN_UNTOUCHED; @@ -921,7 +924,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_error = AC_HEALTH_ERROR_UNTOUCHED; + cmd->health_status = AC_HEALTH_STATUS_UNTOUCHED; cmd->iFeel = AC_IFEEL_UNTOUCHED; cmd->louver.louver_h = AC_LOUVERH_UNTOUCHED; cmd->louver.louver_v = AC_LOUVERV_UNTOUCHED; @@ -941,7 +944,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd->temp_strange = 0; cmd->realFanSpeed = AC_REAL_FAN_UNTOUCHED; }; - + // очистка буфера размером AC_BUFFER_SIZE void _clearBuffer(uint8_t * buf){ memset(buf, 0, AC_BUFFER_SIZE); @@ -1269,9 +1272,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_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_HEALTH_STATUS_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.health_status != (ac_health_status)stateByte); + _current_ac_state.health_status = (ac_health_status)stateByte; stateByte = small_info_body->status & AC_CLEAN_MASK; stateChangedFlag = stateChangedFlag || (_current_ac_state.clean != (ac_clean)stateByte); @@ -1284,7 +1287,7 @@ 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(); @@ -1995,6 +1998,10 @@ 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); @@ -2802,7 +2809,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.health = AC_HEALTH_ON; cmd.health_status = AC_HEALTH_STATUS_ON; - cmd.iFeel = AC_IFEEL_ON; // зависимость от health + cmd.iFeel = AC_IFEEL_ON; // зависимость от health cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health cmd.sleep = AC_SLEEP_OFF; // для логики пресетов @@ -3110,6 +3117,67 @@ 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; } diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index dbd52f8..414d17a 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -39,6 +39,9 @@ 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' @@ -57,6 +60,7 @@ CONF_DISPLAY_INVERTED = 'display_inverted' ICON_DISPLAY = "mdi:clock-digital" CONF_STORE_SETTINGS = 'store_settings' + aux_ac_ns = cg.esphome_ns.namespace("aux_ac") AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) Capabilities = aux_ac_ns.namespace("Constants") @@ -100,14 +104,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 @@ -282,6 +287,8 @@ 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), @@ -299,7 +306,6 @@ async def display_on_to_code(config, action_id, template_arg, args): 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), From 56a2c34d69c56fb429b8c90f7e49df2a32b7901e Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 16:35:21 +0300 Subject: [PATCH 15/74] Add files via upload From 3130cf3705cd198937a6db0f346a34436b447260 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:18:32 +0300 Subject: [PATCH 16/74] Update aux_ac_simple.yaml --- examples/simple/aux_ac_simple.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/simple/aux_ac_simple.yaml b/examples/simple/aux_ac_simple.yaml index df12e23..3da24b1 100644 --- a/examples/simple/aux_ac_simple.yaml +++ b/examples/simple/aux_ac_simple.yaml @@ -1,5 +1,5 @@ external_components: - - source: github://GrKoR/esphome_aux_ac_component + - source: github://Brokly/esphome_aux_ac_component components: [ aux_ac ] refresh: 0s @@ -43,4 +43,4 @@ uart: climate: - platform: aux_ac - name: "AC Name" \ No newline at end of file + name: "AC Name" From 3f44a8e06f2157cd1fad40e782b9870e687723f6 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:23:54 +0300 Subject: [PATCH 17/74] Update ac-energolux-bern.yaml --- examples/advanced/ac-energolux-bern.yaml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml index 69035c3..b9ac1f7 100644 --- a/examples/advanced/ac-energolux-bern.yaml +++ b/examples/advanced/ac-energolux-bern.yaml @@ -1,20 +1,22 @@ external_components: - source: - #type: git - #url: https://github.com/GrKoR/esphome_aux_ac_component - type: local - path: my_components + type: git + url: https://github.com/Brokly/esphome_aux_ac_component + #type: local + #path: my_components components: [ aux_ac ] refresh: 0s esphome: name: $devicename + platform: ESP8266 + board: esp12e -esp32: - board: nodemcu-32s - framework: - type: arduino - +#esp32: +# board: nodemcu-32s +# framework: +# type: arduino + wifi: ssid: !secret wifi_ssid password: !secret wifi_pass @@ -34,9 +36,10 @@ debug: logger: level: DEBUG + baud_rate: 0 api: -# password: !secret api_pass + password: !secret api_pass ota: password: !secret ota_pass From bea59ada1c4d69b875b392c71a9b9b2e4e3ce592 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 26 May 2022 19:36:38 +0300 Subject: [PATCH 18/74] partial complete --- components/aux_ac/aux_ac.h | 1087 ++++++++++++++++++++++++++---------- 1 file changed, 803 insertions(+), 284 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index fcb3fd2..5d316ca 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -14,6 +14,40 @@ #include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/core/helpers.h" +/* TODO: Помечаю, чтобы не забыть. + + GK: + Не понятно пока, зачем сохранять настройки в энергонезависимую память. + Вроде бы компоненту это не требуется - он отражает текущее состояние кондея. + А кондей при включении после пропадания питания сам переходит в последний включенный режим. + В общем, пока не понятно. Ждём пояснений =) + + Brokly: У меня пульт выставляет свои настройки в завасимости от режима. У нагрева свои градусы со шторками , + у охлаждения свои градусы со своими шторками + + GK: ну то есть ты сохраняешь в памяти эти настройки, чтобы в умном доме вместо отправки указания + "выстави нагрев до 24 градусов + шторки так-то", отправлять просто команду "выстави нагрев", + а уже есп будет подтягивать температуру и шторки из памяти. Так? или я не понял? + + Brokly: Да. А что эта функция чему то мешает ? + + GK: фича не мешает, но наверное может вести к проблемам. Типа износа ячеек памяти... а может и не привести... + пока не уверен, что эту фичу надо в мастер кидать. + плюс исходники компонента вбирают в себя дополнительный функционал, который можно реализовать иначе. + Как минимум, старт с определенного пресета можно выполнить настройками в yaml (через глобальные переменные). + Не факт, что это будет проще и\или оптимальнее. Но зато фичи компонента будут соответствовать принципам атомарности, + в нем не будет дополнительного кода, который может в компоненте не быть. + в общем, надо будет взвесить все за и против +*/ +#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,14 +83,14 @@ 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.4"; const char *const Constants::TAG = "AirCon"; -const std::string Constants::MUTE = "mute"; -const std::string Constants::TURBO = "turbo"; -const std::string Constants::CLEAN = "clean"; -const std::string Constants::FEEL = "feel"; -const std::string Constants::HEALTH = "health"; -const std::string Constants::ANTIFUNGUS = "antifugnus"; +const std::string Constants::MUTE = "Mute"; +const std::string Constants::TURBO = "Turbo"; +const std::string Constants::CLEAN = "Clean"; +const std::string Constants::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; @@ -73,80 +107,68 @@ enum acsm_state : uint8_t { ACSM_SENDING_PACKET, // отправляем пакет сплиту }; -/** - * Кондиционер отправляет пакеты следующей структуры: - * HEADER: 8 bytes - * BODY: 0..24 bytes - * CRC: 2 bytes - * Весь пакет максимум 34 байта - * По крайней мере все встреченные мной пакеты имели такой размер и структуру. - **/ +// структура пакета описана тут: +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_structure #define AC_HEADER_SIZE 8 #define AC_MAX_BODY_SIZE 24 +// стандартно длина пакета не более 34 байт +// но встретилось исключение Royal Clima (как минимум, модель CO-D xxHNI) - у них 35 байт +// пожтому буффер увеличен #define AC_BUFFER_SIZE 35 /** * таймаут загрузки пакета * * через такое количиство миллисекунд конечный автомат перейдет из состояния ACSM_RECEIVING_PACKET в ACSM_IDLE, если пакет не будет загружен - * расчетное время передачи 1 бита при скорости 4800 примерно 0,208 миллисекунд; - * 1 байт передается 11 битами (1 стартовый, 8 бит данных, 1 бит четности и 1 стоповый бит) или 2,30 мс. - * максимальный размер пакета AC_BUFFER_SIZE = 34 байта => 78,2 мсек. Плюс накладные расходы. - * Скорее всего на получение пакета должно хватать 100 мсек. - * - * По факту проверка показала: - * - если отрабатывать по 1 символу из UART на один вызов loop, то на 10 байт пинг-пакета требуется 166 мсек. - * То есть примерно по 16,6 мсек на байт. Примем 17 мсек. - * Значит на максимальный пакет потребуется 17*34 = 578 мсек. Примем 600 мсек. - * - если отрабатывать пакет целиком или хотя бы имеющимися в буфере UART кусками, то на 10 байт пинг-пакета требуется 27 мсек. - * То есть примерно по 2,7 мсек. на байт. Что близко к расчетным значениям. Примем 3 мсек. - * Значит на максимальный пакет потребуется 3*34 = 102 мсек. Примем 150 мсек. - * Опыт показал, что 150 мсек вполне хватает на большие пакеты + * По расчетам выходит: + * - получение и обработка посимвольно не должна длиться дольше 600 мсек. + * - получение и обработка пакетов целиком не должна длиться дольше 150 мсек. + * Мы будем обрабатывать пакетами. **/ -#define AC_PACKET_TIMEOUT 150 // 150 мсек - отработка буфера UART за раз, 600 мсек - отработка буфера UART по 1 байту за вызов loop +#define AC_PACKET_TIMEOUT 150 // типы пакетов -#define AC_PTYPE_PING 0x01 // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает -#define AC_PTYPE_CMD 0x06 // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита -#define AC_PTYPE_INFO 0x07 // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля -#define AC_PTYPE_INIT 0x09 // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. -#define AC_PTYPE_UNKN 0x0b // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_types +#define AC_PTYPE_PING 0x01 // ping-пакет +#define AC_PTYPE_CMD 0x06 // команда сплиту +#define AC_PTYPE_INFO 0x07 // информационный пакет +#define AC_PTYPE_INIT 0x09 // инициирующий пакет +#define AC_PTYPE_UNKN 0x0b // какой-то странный пакет // типы команд -#define AC_CMD_STATUS_BIG 0x21 // большой пакет статуса кондиционера -#define AC_CMD_STATUS_SMALL 0x11 // маленький пакет статуса кондиционера -#define AC_CMD_STATUS_PERIODIC 0x2C // иногда встречается, сплит её рассылает по своему разумению; (вроде бы может быть и другой код! надо больше данных) +// смотреть тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_type_cmd #define AC_CMD_SET_PARAMS 0x01 // команда установки параметров кондиционера +#define AC_CMD_STATUS_SMALL 0x11 // маленький пакет статуса кондиционера +#define AC_CMD_STATUS_BIG 0x21 // большой пакет статуса кондиционера +// TODO: Нужно посмотреть, где используется AC_CMD_STATUS_PERIODIC, и изменить логику. +// на сегодня уже известно, что периодически рассылаются команды в диапазоне 0x20..0x2F +#define AC_CMD_STATUS_PERIODIC 0x2C // иногда встречается // значения байтов в пакетах #define AC_PACKET_START_BYTE 0xBB // Стартовый байт любого пакета 0xBB, других не встречал #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; + 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]; }; +// структура пекета struct packet_t { uint32_t msec; // значение millis в момент определения корректности пакета packet_header_t * header; @@ -157,129 +179,180 @@ struct packet_t { }; // тело ответа на пинг +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_type_ping 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; + uint8_t byte_27 = 0x27; + uint8_t zero1 = 0; + uint8_t zero2 = 0; + uint8_t zero3 = 0; + uint8_t zero4 = 0; + uint8_t zero5 = 0; + uint8_t zero6 = 0; }; // тело большого информационного пакета +// 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 - // для RoyalClima18HNI: всегда 0xE0 - uint8_t unknown1; // не расшифрован, как-то связан с режимом работы сплита; как вариант, отражает режим работы - // компрессора во внешнем блоке или что-то такое, потому что иногда включение сплита не сразу приводит к изменениям в этом байте - // - // Встречались такие значения: - // 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-модуля в этом байте скорость работы вентилятора - // fanSpeed: OFF=0x00, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07; режим CLEAN=0x01 - // в дежурных пакетах тут похоже что-то другое - uint8_t zero2; // всегда 0x00 - uint8_t ambient_temperature_int; // целая часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // перевод по формуле 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 - // для RoyalClima18HNI: мощность инвертора (от 0 до 100) в % - // например, разморозка внешнего блока происходит при 80% - uint8_t zero11; // всегда 0x00 - uint8_t zero12; // всегда 0x00 - uint8_t zero13; // всегда 0x00 - uint8_t zero14; // всегда 0x00 + // байт 0 тела (байт 8 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b08 + uint8_t byte_01 = 0x01; + + // байт 1 тела (байт 9 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b09 + uint8_t cmd_answer; + + // байт 2 тела (байт 10 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b10 + uint8_t reserv20 :2; + bool is_invertor_periodic :1; // флаг периодического пакета инверторного кондиционера + uint8_t reserv23 :2; + bool is_invertor :1; // флаг инвертора + uint8_t reserv26 :2; + + // байт 3 тела (байт 11 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b11 + 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, + // 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 тела (байт 12 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b12 + uint8_t reserv40 :4; + bool needDefrost :1; + bool defrostMode :1; + bool reserv46 :1; + bool cleen :1; + // Для кондея старт-стоп + // x xx + // C5 1100 0101 + // C4 1100 0100 + // 85 1000 0101 + // 84 1000 0100 + // 3D 0011 1101 + // 3C 0011 1100 + // 25 0010 0101 + // 24 0010 0100 + // 5 0000 0101 + // 4 0000 0100 + + // байт 5 тела (байт 13 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b13 + uint8_t realFanSpeed:3; // реальная (не заданная) скорость вентилятора + uint8_t reserv53:5; + // в дежурных пакетах тут похоже что-то другое + + // байт 6 тела (байт 14 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b14 + bool reserv60:1; + uint8_t fanPWM:7; // ШИМ вентилятора + + // байт 7 тела (байт 15 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b15 + uint8_t ambient_temperature_int; + + // байт 8 тела (байт 16 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b16 + uint8_t zero3; // не расшифрован, у кого-то всегда 0x00, у кого-то повторяет значение байта 17 пакета. Непонятно. + + // байт 9 тела (байт 17 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b17 + uint8_t in_temperature_int; // какая-то температура, детали см. в описании на гитхабе + + // байт 10 тела (байт 18 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b18 + uint8_t zero4; // не расшифрован + + // байт 11 тела (байт 19 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b19 + uint8_t zero5; // всегда 0x00 или 0x64 + + // байт 12 тела (байт 20 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b20 + uint8_t outdoor_temperature; // Внешняя температура; формула T - 0x20 + + // байт 13 тела (байт 21 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b21 + uint8_t out_temperature_int; // похоже на температуру обратки, T - 0x20 + + // байт 14 тела (байт 22 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b22 + uint8_t strange_temperature_int; // от режима не зависит, растет при включении инвертора; температура двигателя? + + // байт 15 тела (байт 23 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b23 + uint8_t zero9; // не расшифрован, подробнее в описании + + // байт 16 тела (байт 24 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b24 + uint8_t invertor_power; // мощность инвертора (от 0 до 100) в % + + // байт 17 тела (байт 25 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b25 + //TODO: в описание протокола требуется пояснение от Brokly + uint8_t zero11; // не расшифрован, подробнее в описании. + + // байт 18 тела (байт 26 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b26 + uint8_t zero12; // не расшифрован, подробнее в описании. + + // байт 19 тела (байт 27 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b27 + //TODO: в описание протокола требуется пояснение от Brokly + uint8_t zero13; // не расшифрован, подробнее в описании. + + // байт 20 тела (байт 28 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b28 + uint8_t zero14; // не расшифрован, подробнее в описании. + + // байт 21 тела (байт 29 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b29 uint8_t zero15; // всегда 0x00 + + // байт 22 тела (байт 30 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b30 uint8_t zero16; // всегда 0x00 - uint8_t ambient_temperature_frac; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // подробнее смотреть ambient_temperature_int - // для RoyalClima18HNI: старшие 4 бита - 0x2 + + // байт 23 тела (байт 31 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b31 + uint8_t ambient_temperature_frac:4; // дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы + uint8_t reserv234:1; + bool unknown:1; // для `Royal Clima 18HNI` в этом бите `1`. Не понятно, что это значит. У других сплитов такое не встречалось. + uint8_t reserv236:2; }; // тело малого информационного пакета +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11 struct packet_small_info_body_t { - uint8_t byte_01; // не расшифрован, всегда 0x01 - uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x11); - // в пакетах сплита другие варианты не встречаются - // в отправляемых wifi-модулем пакетах тут может быть 0x01, если требуется установить режим работы - uint8_t target_temp_int_and_v_louver; // целая часть целевой температуры и положение вертикальных жалюзи - // три младших бита - положение вертикальных жалюзи - // если они все = 0, то вертикальный SWING включен - // если они все = 1, то выключен вертикальный SWING - // протокол универсильный, другие комбинации битов могут задавать какие-то положения - // вертикальных жалюзи, но у меня на пульте таких возможностей нет, надо экспериментировать. - // пять старших бит - целая часть целевой температуры - // температура определяется по формуле: - // 8 + (target_temp_int_and_v_louver >> 3) + (0.5 * (target_temp_frac >> 7)) - uint8_t h_louver; // старшие 3 бита - положение горизонтальных жалюзи, остальное не изучено и всегда было 0 - // если все 3 бита = 0, то горизонтальный SWING включен - // если все 3 бита = 1, то горизонтальный SWING отключен - // надо изучить другие комбинации - uint8_t target_temp_frac; // старший бит - дробная часть целевой температуры - // остальные биты до конца не изучены: - // бит 6 был всегда 0 - // биты 0..5 растут на 1 каждую минуту, возможно внутренний таймер для включения/выключения по времени - uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны - // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 - uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны - uint8_t mode; // режим работы сплита: - // AUTO : bits[7, 6, 5] = [0, 0, 0] - // COOL : bits[7, 6, 5] = [0, 0, 1] - // DRY : bits[7, 6, 5] = [0, 1, 0] - // HEAT : bits[7, 6, 5] = [1, 0, 1] - // FAN : bits[7, 6, 5] = [1, 1, 1] - // Sleep function : bit 2 = 1 - // iFeel function : bit 3 = 1 + uint8_t byte_01; + uint8_t cmd_answer; + uint8_t target_temp_int_and_v_louver; + uint8_t h_louver; + uint8_t target_temp_frac; + uint8_t fan_speed; + uint8_t fan_turbo_and_mute; + uint8_t mode; uint8_t zero1; // всегда 0x00 uint8_t zero2; // всегда 0x00 - uint8_t status; // бит 5 = 1: включен, обычный режим работы (когда можно включить нагрев, охлаждение и т.п.) - // бит 2 = 1: режим самоочистки, должен запускаться только при бит 5 = 0 - // бит 0 и бит 1: активация режима ионизатора воздуха (не проверен, у меня его нет) + uint8_t status; uint8_t zero3; // всегда 0x00 - uint8_t display_and_mildew; // бит4 = 1, чтобы погасить дисплей на внутреннем блоке сплита - // бит3 = 1, чтобы включить функцию "антиплесень" (после отключения как-то прогревает или просушивает теплообменник, чтобы на нем не росла плесень) + uint8_t display_and_mildew; uint8_t zero4; // всегда 0x00 - uint8_t target_temp_frac2; // дробная часть целевой температуры, может быть только 0x00 и 0x05 - // при установке температуры тут 0x00, а заданная температура передается в target_temp_int_and_v_louver и target_temp_frac - // после установки сплит в информационных пакетах тут начинает показывать дробную часть - // не очень понятно, зачем так сделано + uint8_t target_temp_frac2; }; @@ -288,6 +361,11 @@ struct packet_small_info_body_t { //*************************************************** ПАРАМЕТРЫ РАБОТЫ КОНДИЦИОНЕРА ****************************************************************** //**************************************************************************************************************************************************** // для всех параметров ниже вариант X_UNTOUCHED = 0xFF означает, что этот параметр команды должен остаться тот, который уже установлен + +// для показаний о реальной скорости фена из большого пакета +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 }; + // питание кондиционера #define AC_POWER_MASK 0b00100000 enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTOUCHED = 0xFF }; @@ -296,19 +374,23 @@ 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_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 }; @@ -325,12 +407,12 @@ enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTO enum ac_ifeel : uint8_t { AC_IFEEL_OFF = 0x00, AC_IFEEL_ON = 0x08, AC_IFEEL_UNTOUCHED = 0xFF }; // Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V +// TODO: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V #define AC_LOUVERV_MASK 0b00000111 enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_UPDOWN = 0x00, AC_LOUVERV_OFF = 0x07, AC_LOUVERV_UNTOUCHED = 0xFF }; // Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H +// TODO: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H #define AC_LOUVERH_MASK 0b11100000 enum ac_louver_H : uint8_t { AC_LOUVERH_SWING_LEFTRIGHT = 0x00, AC_LOUVERH_OFF = 0xE0, AC_LOUVERH_UNTOUCHED = 0xFF }; @@ -361,12 +443,60 @@ 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 }; +// маска счетчика минут прошедших с последней команды +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b12 +#define AC_MIN_COUTER 0b00111111 + + /** команда для кондиционера * * ВАЖНО! В коде используется копирование команд простым присваиванием. * Если в структуру будут введены указатели, то копирование надо будет изменить! */ + +//***************************************************************************** +// TODO: блок кода под сохранение пресетов. После решения - убрать +// данные структур содержат настройку, специально вынес в макрос +#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;\ + bool temp_target_matter\ + +// чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше +// из-за такого приема нужно контролировать размер копируемых данных руками +#define AC_COMMAND_BASE_SIZE 21 + +// структура для сохранения данных +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 +}; +//***************************************************************************** + struct ac_command_t { + /* ac_power power; float temp_target; bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную @@ -384,6 +514,18 @@ struct ac_command_t { ac_fanmute fanMute; ac_display display; ac_mildew mildew; + */ + AC_COMMAND_BASE; + ac_health_error health_error; + 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; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) }; typedef ac_command_t ac_state_t; // текущее состояние параметров кондея можно хранить в таком же формате, как и комманды @@ -418,13 +560,10 @@ typedef ac_command_t ac_state_t; // текущее состояние пара // максимальная длина последовательности; больше вроде бы не требовалось #define AC_SEQUENCE_MAX_LEN 0x0F -// в пакетах никогда не встречалось значение 0xFF (только в CRC), поэтому решено его использовать как признак не важного значение байта -//#define AC_SEQUENCE_ANY_BYTE 0xFF - // дефолтный таймаут входящего пакета в миллисекундах // если для входящего пакета в последовательности указан таймаут 0, то используется значение по-умолчанию // если нужный пакет не поступил в течение указанного времени, то последовательность прерывается с ошибкой -#define AC_SEQUENCE_DEFAULT_TIMEOUT 500 +#define AC_SEQUENCE_DEFAULT_TIMEOUT 580 // Brokly: пришлось увеличить с 500 до 580 enum sequence_item_type_t : uint8_t { AC_SIT_NONE = 0x00, // пустой элемент последовательности @@ -459,6 +598,28 @@ struct sequence_item_t { class AirCon : public esphome::Component, public esphome::climate::Climate { private: + + + //***************************************************************************** + // TODO: блок кода под сохранение пресетов. После решения - убрать + // массив для сохранения данных глобальных персетов + ac_save_command_t global_presets[POS_MODE_OFF+1]; + #if defined(ESP32) + // тут будем хранить данные глобальных пресетов во флеше + // ВНИМАНИЕ на данный момент 22.05.22 ESPHOME 20022.5.0 имеет ошибку + // траблтикет: https://github.com/esphome/issues/issues/3298 + // из-за этого сохранение в энергонезависимую память не работает !!! + ESPPreferenceObject storage = global_preferences->make_preference(this->get_object_id_hash(), true); + #endif + // настройка-ключ, для включения сохранения - восстановления настроек каждого + // режима работы в отдельности, то есть каждый режим работы имеет свои настройки + // температуры, шторок, скорости вентилятора, пресетов + bool _store_settings = false; + // флаги для сохранения пресетов + bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено + //***************************************************************************** + + // время последнего запроса статуса у кондея uint32_t _dataMillis; // периодичность обновления статуса кондея, по дефолту AC_STATES_REQUEST_INTERVAL @@ -474,6 +635,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // если тут true, то 1 потушит дисплей, а 0 включит. bool _display_inverted = false; + // флаг типа кондиционера. инвертор - true, ON/OFF - false, начальная установка false + // в таком режиме точность и скорость определения реального состояния системы для инвертора, + // будет работать, но будет ниже, переменная устанавливается при первом получении большого пакета; + // если эта переменная установлена, то режим работы не инверторного кондиционера будет распознаваться + // как "в простое" (IDLE) + bool _is_invertor = false; + // поддерживаемые кондиционером опции std::set _supported_modes{}; std::set _supported_swing_modes{}; @@ -680,7 +848,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,10 +856,17 @@ 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 @@ -1021,9 +1196,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); @@ -1051,6 +1226,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; @@ -1058,11 +1236,46 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _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(); @@ -1132,17 +1345,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { void _debugMsg(const String &msg, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0, ... ){ if (dbgLevel < ESPHOME_LOG_LEVEL_NONE) dbgLevel = ESPHOME_LOG_LEVEL_NONE; if (dbgLevel > ESPHOME_LOG_LEVEL_VERY_VERBOSE) dbgLevel = ESPHOME_LOG_LEVEL_VERY_VERBOSE; - - // 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 +1388,40 @@ 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 += "["; - - memset(textBuf, 0, 11); - sprintf(textBuf, "%02X", packet->data[i]); - st += textBuf; + #ifdef HOLMS + // если этот дефайн объявлен, то в лог попадут только пакеты больше указанного в дефайне размера + // весь вывод будет в десятичном виде, а не в шестнадцатиричном + dbgLevel = ESPHOME_LOG_LEVEL_ERROR; + if(packet->header->body_length > HOLMS){ + for (int i=0; ibytesLoaded; i++){ + sprintf(textBuf, "%03d;", packet->data[i]); + st += textBuf; + } + if (line == 0) line = __LINE__; + _debugMsg(st, dbgLevel, line); + } + #else + for (int i=0; ibytesLoaded; i++){ + // для нормальных пакетов надо заключить заголовок в [] + if ((!notAPacket) && (i == 0)) st += "["; + // для нормальных пакетов надо заключить CRC в [] + if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE)) st += "["; + + memset(textBuf, 0, 11); + sprintf(textBuf, "%02X", packet->data[i]); + st += textBuf; - // для нормальных пакетов надо заключить заголовок в [] - if ((!notAPacket) && (i == AC_HEADER_SIZE-1)) st += "]"; - // для нормальных пакетов надо заключить CRC в [] - if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE+2-1)) st += "]"; + // для нормальных пакетов надо заключить заголовок в [] + if ((!notAPacket) && (i == AC_HEADER_SIZE-1)) st += "]"; + // для нормальных пакетов надо заключить CRC в [] + if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE+2-1)) st += "]"; - st += " "; - } + st += " "; + } - if (line == 0) line = __LINE__; - _debugMsg(st, dbgLevel, line); + if (line == 0) line = __LINE__; + _debugMsg(st, dbgLevel, line); + #endif } /** расчет CRC16 для блока данных data длиной len @@ -1358,6 +1580,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } + // обнулить счетчик минут с последней команды + // GK: считаю, что так делать не надо. Штатный wifi-модуль не сбрасывает счетчик минут. + // 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,9 +1627,15 @@ 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){ @@ -1598,13 +1830,17 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } // сенсоры, отображающие параметры сплита - //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; // загружает на выполнение последовательность команд на включение/выключение табло с температурой @@ -1627,6 +1863,56 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return true; } + // номер глобального пресета от режима работы + uint8_t get_num_preset(ac_command_t* cmd){ + if(cmd->power == AC_POWER_OFF){ + return POS_MODE_OFF; + } else if(cmd->mode == AC_MODE_AUTO){ + return POS_MODE_AUTO; + } else if(cmd->mode == AC_MODE_COOL){ + return POS_MODE_COOL; + } else if(cmd->mode == AC_MODE_DRY){ + return POS_MODE_DRY; + } else if(cmd->mode == AC_MODE_FAN){ + return POS_MODE_FAN; + } else if(cmd->mode == AC_MODE_HEAT){ + return POS_MODE_HEAT; + } + cmd->power = AC_POWER_OFF; + return POS_MODE_OFF; + } + + // восстановление данных из пресета + void load_preset(ac_command_t* cmd, uint8_t num_preset){ + if(num_preset < sizeof(global_presets)/sizeof(global_presets[0])){ // проверка выхода за пределы массива + if(cmd->power == global_presets[num_preset].power && cmd->mode == global_presets[num_preset].mode){ //контроль инициализации + memcpy(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE); // просто копируем из массива + _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } else { + _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } + } + } + + // запись данных в массив персетов + void save_preset(ac_command_t* cmd){ + uint8_t num_preset = get_num_preset(cmd); + if(memcmp(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE) != 0){ // содержимое пресетов разное + memcpy(&(global_presets[num_preset]), cmd, AC_COMMAND_BASE_SIZE); // копируем пресет в массив + #if defined(ESP32) + _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + if(storage.save(global_presets)){ + if(!global_preferences->sync()) // сохраняем все пресеты + _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } else { + _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } + #endif + } else { + _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } + } + public: // инициализация объекта void initAC(esphome::uart::UARTComponent *parent = nullptr){ @@ -1656,7 +1942,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,43 +1958,106 @@ 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__); + if(_is_invertor){ // анализ режима для инвертора, точнее потому что использует показания мощности инвертора + static uint32_t timerInv = 0; + if(_current_ac_state.invertor_power == 0){ // инвертор выключен + timerInv = millis(); + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && + _current_ac_state.power == AC_POWER_OFF ){ // внутренний кулер остановлен, кондей выключен + this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает + } else { + int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; + if (delta_temp > 0 && delta_temp < 2 && + (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else if (_current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || + _current_ac_state.realFanSpeed == AC_REAL_FAN_OFF ){ // кулер чуть вертится + this->action = climate::CLIMATE_ACTION_IDLE; // кондей в простое + } else { + this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + } + } + } else if(millis()-timerInv > 2000){ // инвертор включен, но нужно дождаться реакции на его включение + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE ){ //медленное вращение + if(_current_ac_state.temp_ambient - _current_ac_state.temp_inbound > 0){ //холодный радиатор + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else { // теплый радиатор, видимо переходный режим + this->action = climate::CLIMATE_ACTION_IDLE; + } + } else { + int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; + if(delta_temp < -2){ // входящая температура выше комнатной, быстрый фен - ОБОГРЕВ + this->action = climate::CLIMATE_ACTION_HEATING; + } else if(delta_temp > 2){ // ниже, быстрый фен - ОХЛАЖДЕНИЕ + this->action = climate::CLIMATE_ACTION_COOLING; + } else { // просто вентиляция + this->action = climate::CLIMATE_ACTION_IDLE; + } + } + } else { + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE){ + this->action = climate::CLIMATE_ACTION_IDLE; + } else { + this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + } + } + } else { + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && + _current_ac_state.power == AC_POWER_OFF){ + this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает + } else { + int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; // разность температуры между комнатной и входящей + if (delta_temp > 0 && delta_temp < 2 && + (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else if(_current_ac_state.realFanSpeed != AC_REAL_FAN_OFF && + _current_ac_state.realFanSpeed != AC_REAL_FAN_MUTE){ + if(delta_temp > 2){ + this->action = climate::CLIMATE_ACTION_COOLING; + } else if(delta_temp < -2){ + this->action = climate::CLIMATE_ACTION_HEATING; + } else { + this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + } + } else { + this->action = climate::CLIMATE_ACTION_IDLE; + } + } + } + _debugMsg(F("Action mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->action); + + /*************************** POWER & MODE ***************************/ - 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; case AC_MODE_COOL: this->mode = climate::CLIMATE_MODE_COOL; - // TODO: надо реализовать отображение action - this->action = climate::CLIMATE_ACTION_COOLING; break; case AC_MODE_DRY: this->mode = climate::CLIMATE_MODE_DRY; - // TODO: надо реализовать отображение action - this->action = climate::CLIMATE_ACTION_DRYING; break; case AC_MODE_HEAT: this->mode = climate::CLIMATE_MODE_HEAT; - // TODO: надо реализовать отображение action - this->action = climate::CLIMATE_ACTION_HEATING; break; case AC_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; - // TODO: надо реализовать отображение action - this->action = climate::CLIMATE_ACTION_FAN; break; default: @@ -1711,9 +2066,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } 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); @@ -1748,9 +2100,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // 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; - } + //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: @@ -1765,9 +2117,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // MUTE работает только в режиме FAN. В режиме COOL кондей команду принимает, но MUTE не устанавливается switch (_current_ac_state.fanMute) { case AC_FANMUTE_ON: - if (_current_ac_state.mode == AC_MODE_FAN) { + //if (_current_ac_state.mode == AC_MODE_FAN) { this->custom_fan_mode = Constants::MUTE; - } + //} break; case AC_FANMUTE_OFF: @@ -1778,6 +2130,38 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); + //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ + + /*************************** iFEEL CUSTOM PRESET ***************************/ + // режим поддержки температуры в районе пульта, работает только при включенном конедее + switch (_current_ac_state.iFeel) { + case AC_IFEEL_ON: + if ( _current_ac_state.power == AC_POWER_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 ***************************/ + // режим работы ионизатора + switch (_current_ac_state.health) { + case AC_HEALTH_ON: + if ( _current_ac_state.power == AC_POWER_ON) this->custom_preset = Constants::HEALTH; + break; + + case AC_HEALTH_OFF: + default: + if (this->custom_preset == Constants::HEALTH) this->custom_preset = (std::string)""; + break; + } + + _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); + /*************************** SLEEP PRESET ***************************/ // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. @@ -1785,12 +2169,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // Восстанавливается ли температура через 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.mode == AC_MODE_COOL + // or _current_ac_state.mode == AC_MODE_HEAT) { + if ( _current_ac_state.power == AC_POWER_ON) this->preset = climate::CLIMATE_PRESET_SLEEP; + //} break; case AC_SLEEP_OFF: @@ -1805,11 +2187,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // режим очистки кондиционера, включается (или должен включаться) при 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; - - } + if (_current_ac_state.power == AC_POWER_OFF) this->custom_preset = Constants::CLEAN; break; case AC_CLEAN_OFF: @@ -1820,44 +2198,18 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _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: - this->custom_preset = Constants::HEALTH; - break; - - case AC_HEALTH_OFF: - default: - if (this->custom_preset == Constants::HEALTH) this->custom_preset = (std::string)""; - break; - } - - _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); - /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ // пресет просушки кондиционера после выключения // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - // TODO: не реализован, у меня отсутствует + // + // Brokly: + // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет + // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии + // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки + // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало + // и затыкается. switch (_current_ac_state.mildew) { case AC_MILDEW_ON: this->custom_preset = Constants::ANTIFUNGUS; @@ -1874,21 +2226,31 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** LOUVERs ***************************/ this->swing_mode = climate::CLIMATE_SWING_OFF; - 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.power == AC_POWER_OFF) { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } else { 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; + 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; + } } } _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; // в AUTO зашита температура 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; @@ -1903,11 +2265,26 @@ 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()) { @@ -1929,6 +2306,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // могут быть и другие состояния, поэтому так break; } + } } // вывод в дебаг текущей конфигурации компонента @@ -1936,8 +2314,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()) { @@ -1956,6 +2336,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()) { @@ -1986,6 +2475,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { case climate::CLIMATE_MODE_OFF: hasCommand = true; cmd.power = AC_POWER_OFF; + load_preset(&cmd, POS_MODE_OFF); + cmd.temp_target = _current_ac_state.temp_ambient; // TODO: не нужно же this->mode = mode; break; @@ -1993,6 +2484,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_COOL; + load_preset(&cmd, POS_MODE_COOL); this->mode = mode; break; @@ -2000,6 +2492,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_HEAT; + load_preset(&cmd, POS_MODE_HEAT); this->mode = mode; break; @@ -2007,6 +2500,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_AUTO; + load_preset(&cmd, POS_MODE_AUTO); + cmd.temp_target = 25; // зависимость от режима HEAT_COOL + cmd.temp_target_matter = true; + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL this->mode = mode; break; @@ -2014,6 +2511,14 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_FAN; + load_preset(&cmd, POS_MODE_FAN); + cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN + cmd.temp_target_matter = true; + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN + cmd.sleep = AC_SLEEP_OFF; + if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ + cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN + } this->mode = mode; break; @@ -2021,6 +2526,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_DRY; + load_preset(&cmd, POS_MODE_DRY); + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY + cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY this->mode = mode; break; @@ -2031,6 +2539,17 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } +/* +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@ +@@@@ +@@@@ ОСТАНОВИЛСЯ ТУТ!! +@@@@ +@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +*/ + + // User requested fan_mode change if (call.get_fan_mode().has_value()) { ClimateFanMode fanmode = *call.get_fan_mode(); From 75bac4c29020836155b64de136f92f48cfc13a32 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:43:24 +0300 Subject: [PATCH 19/74] Update ac-energolux-bern.yaml --- examples/advanced/ac-energolux-bern.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml index b9ac1f7..7f3e8f7 100644 --- a/examples/advanced/ac-energolux-bern.yaml +++ b/examples/advanced/ac-energolux-bern.yaml @@ -52,8 +52,10 @@ web_server: uart: id: ac_uart_bus - tx_pin: GPIO16 - rx_pin: GPIO17 + #tx_pin: GPIO16 + #rx_pin: GPIO17 + tx_pin: GPIO1 + rx_pin: GPIO3 baud_rate: 4800 data_bits: 8 parity: EVEN From b8ebe409194232ac15c12ec15da4e5e6d208229c Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:46:12 +0300 Subject: [PATCH 20/74] Update ac_kitchen.yaml --- examples/advanced/ac_kitchen.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/advanced/ac_kitchen.yaml b/examples/advanced/ac_kitchen.yaml index 91ead20..867ead3 100644 --- a/examples/advanced/ac_kitchen.yaml +++ b/examples/advanced/ac_kitchen.yaml @@ -6,7 +6,8 @@ #=================================================================================== substitutions: - devicename: kitchen_ac + devicename: kitchen-ac + low_devicename: kitchen_ac upper_devicename: Kitchen AC # use different wifi_ip and wifi_ota_ip in case of esp ip-address change @@ -14,4 +15,4 @@ substitutions: wifi_ip: !secret wifi_ip_kitchen wifi_ota_ip: !secret wifi_ota_ip_kitchen -<<: !include ac_common.yaml \ No newline at end of file +<<: !include ac-energolux-bern.yaml From b6918b3d278bf4e39f0d1a22de3d8d953e0437c2 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:47:44 +0300 Subject: [PATCH 21/74] Update ac_livingroom.yaml --- examples/advanced/ac_livingroom.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/advanced/ac_livingroom.yaml b/examples/advanced/ac_livingroom.yaml index 08f985b..f874727 100644 --- a/examples/advanced/ac_livingroom.yaml +++ b/examples/advanced/ac_livingroom.yaml @@ -6,7 +6,8 @@ #=================================================================================== substitutions: - devicename: livingroom_ac + devicename: livingroom-ac + low_devicename: livingroom_ac upper_devicename: Livingroom AC # use different wifi_ip and wifi_ota_ip in case of esp ip-address change @@ -14,4 +15,4 @@ substitutions: wifi_ip: !secret wifi_ip_livingroom wifi_ota_ip: !secret wifi_ota_ip_livingroom -<<: !include ac_common.yaml \ No newline at end of file +<<: !include ac-energolux-bern.yaml From 33018e906b85b885d453abd304a33137e9efdd1b Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:48:05 +0300 Subject: [PATCH 22/74] Delete ac_common.yaml --- examples/advanced/ac_common.yaml | 122 ------------------------------- 1 file changed, 122 deletions(-) delete mode 100644 examples/advanced/ac_common.yaml diff --git a/examples/advanced/ac_common.yaml b/examples/advanced/ac_common.yaml deleted file mode 100644 index e012151..0000000 --- a/examples/advanced/ac_common.yaml +++ /dev/null @@ -1,122 +0,0 @@ -# DON'T COMPILE THIS FILE -# This file contains common settings for all air conditioners of your house -external_components: - - source: github://GrKoR/esphome_aux_ac_component - components: [ aux_ac ] - refresh: 0s - -esphome: - name: $devicename - platform: ESP8266 - board: esp12e - -wifi: - ssid: !secret wifi_ssid - password: !secret wifi_pass - manual_ip: - static_ip: ${wifi_ip} - gateway: !secret wifi_gateway - subnet: !secret wifi_subnet - ap: - ssid: ${upper_devicename} Hotspot - password: !secret wifi_ap_pass - use_address: ${wifi_ota_ip} - -captive_portal: -debug: - -logger: - level: DEBUG - baud_rate: 0 - # set hardware_uart to UART1 and comment out baud_rate above in case of boot crashes - # it is suitable if you need hardware loggin - # hardware_uart: UART1 - -api: - password: !secret api_pass - -ota: - password: !secret ota_pass - -web_server: - port: 80 - auth: - username: !secret web_server_user - password: !secret web_server_password - -# UART0 configuration for AUX air conditioner communication -uart: - id: ac_uart_bus - tx_pin: GPIO1 - rx_pin: GPIO3 - 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: true - indoor_temperature: - name: ${upper_devicename} Indoor Temperature - id: ${devicename}_indoor_temp - internal: false - display_state: - name: $upper_devicename Display State - id: ${devicename}_display_state - internal: false - visual: - min_temperature: 16 - max_temperature: 32 - temperature_step: 0.5 - 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: - # just wifi signal strength for debug purpose only - - 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(${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 ea0f4f68a9fb3d51918ac8745927d986fe0cd90f Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:48:38 +0300 Subject: [PATCH 23/74] Rename ac_kitchen.yaml to ac_kitchen_bern.yaml --- examples/advanced/{ac_kitchen.yaml => ac_kitchen_bern.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/advanced/{ac_kitchen.yaml => ac_kitchen_bern.yaml} (100%) diff --git a/examples/advanced/ac_kitchen.yaml b/examples/advanced/ac_kitchen_bern.yaml similarity index 100% rename from examples/advanced/ac_kitchen.yaml rename to examples/advanced/ac_kitchen_bern.yaml From 2e9f03437841e65fb309c4309f71ff7b4485f554 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:49:02 +0300 Subject: [PATCH 24/74] Rename ac_livingroom.yaml to ac_livingroom_bern.yaml --- examples/advanced/{ac_livingroom.yaml => ac_livingroom_bern.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/advanced/{ac_livingroom.yaml => ac_livingroom_bern.yaml} (100%) diff --git a/examples/advanced/ac_livingroom.yaml b/examples/advanced/ac_livingroom_bern.yaml similarity index 100% rename from examples/advanced/ac_livingroom.yaml rename to examples/advanced/ac_livingroom_bern.yaml From c962aa11aaa1e7fd90bda71c88ed13feed62b677 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:49:22 +0300 Subject: [PATCH 25/74] Rename ac_livingroom_bern.yaml to ac-livingroom-bern.yaml --- .../advanced/{ac_livingroom_bern.yaml => ac-livingroom-bern.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/advanced/{ac_livingroom_bern.yaml => ac-livingroom-bern.yaml} (100%) diff --git a/examples/advanced/ac_livingroom_bern.yaml b/examples/advanced/ac-livingroom-bern.yaml similarity index 100% rename from examples/advanced/ac_livingroom_bern.yaml rename to examples/advanced/ac-livingroom-bern.yaml From 5f3f9c69cf73bded2f82920224e7bda9fc300f69 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 19:49:43 +0300 Subject: [PATCH 26/74] Rename ac_kitchen_bern.yaml to ac-kitchen-bern.yaml --- examples/advanced/{ac_kitchen_bern.yaml => ac-kitchen-bern.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/advanced/{ac_kitchen_bern.yaml => ac-kitchen-bern.yaml} (100%) diff --git a/examples/advanced/ac_kitchen_bern.yaml b/examples/advanced/ac-kitchen-bern.yaml similarity index 100% rename from examples/advanced/ac_kitchen_bern.yaml rename to examples/advanced/ac-kitchen-bern.yaml From 934f8ee256ce197a9422a47e40933e7186ed921d Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 20:16:13 +0300 Subject: [PATCH 27/74] Add files via upload --- components/aux_ac/automation.h | 3 +-- components/aux_ac/aux_ac.h | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index ad540bc..a62fb27 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -19,7 +19,6 @@ namespace aux_ac { AirCon *ac_; }; - template class AirConDisplayOnAction : public Action { @@ -63,4 +62,4 @@ namespace aux_ac { }; } // namespace aux_ac -} // namespace esphome +} // 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 c8a53c3..49768b4 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -19,7 +19,7 @@ #warning "Saving presets does not work with ESP8266" #endif -//#define HOLMS 19 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны +#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны namespace esphome { namespace aux_ac { @@ -189,9 +189,8 @@ struct packet_big_info_body_t { // БАЙТ 3 bool power:1; bool sleep:1; - bool v_louver:1; - bool h_louver:1; - bool louvers_on:1; + bool louver_V:1; + uint8_t louver_H:2; // у шторок лево-право, почему то два бита uint8_t mode:3; // enum { AC_BIG_MODE_AUTO = 0, // AC_BIG_MODE_COOL = 1, // AC_BIG_MODE_DRY = 2, @@ -216,6 +215,8 @@ struct packet_big_info_body_t { // 0x41 1000001 - DRY // 0x21 100001 - COOL // 0x81 10000001 - HEAT + // 0x85 10000101 - HEAT+шторки верх-низ + // 0x99 10011001 - HEAT+шторки влево вправо // 0xC1 11000001 - FAN // 7 и 6 бит связаны // 0x80 10000000 - продувка после переключения из HEAT в OFF // 0xC5 11000101 - FAN+шторки верх-низ @@ -225,8 +226,8 @@ struct packet_big_info_body_t { // 0x39 111001 - COOL+шторки лево-право // Очевидно битовые, но связные, поля, предположительные зависимости // ВНИМАНИЕ : режимы номинальны, например в режиме АВТО нагрев или охлаждение не отображаются - // 7+6+5 4 3 2 1 0 - // MODE LouvON LouH LouV SLEEP ON/OFF + // 7+6+5 4+3 2 1 0 + // MODE Louv_L Louv_H SLEEP ON/OFF // // ФУНКЦМЯ CLEEN, HEALTH, ANTIFUNGUS на данный байт не влияют // @@ -235,11 +236,11 @@ struct packet_big_info_body_t { // AC_BIG_MODE_COOL = 0x20, // AC_BIG_MODE_HEAT = 0x80, // AC_BIG_MODE_FAN = 0xC0} + // #define AC_BIG_MASK_MODE b00011100 + // enum { AC_BIG_LOUVERS_H = 0x04, + // AC_BIG_LOUVERS_L = 0x18, + // AC_BIG_LOUVERS_BOTH = 0x1C} // #define AC_BIG_MASK_POWER b00000001 - // #define AC_BIG_MASK_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 // @@ -411,6 +412,7 @@ struct packet_small_info_body_t { uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны + // БФЙТ 7 uint8_t mode; // режим работы сплита: // AUTO : bits[7, 6, 5] = [0, 0, 0] // COOL : bits[7, 6, 5] = [0, 0, 1] @@ -529,7 +531,7 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // настройка усреднения фильтра температуры. Это значение - взнос нового измерения // в усредненные показания в процентах -#define OUTDOOR_FILTER_PESCENT 1 +#define OUTDOOR_FILTER_PESCENT 0.2 /** команда для кондиционера * From 5e80fca6d8f9145bd26c298b43c82b6bd272487f Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 22:59:19 +0300 Subject: [PATCH 28/74] Add files via upload --- automation.h | 65 + aux_ac.h | 3194 ++++++++++++++++++++++++++++++++++++++++++++++++++ climate.py | 341 ++++++ 3 files changed, 3600 insertions(+) create mode 100644 automation.h create mode 100644 aux_ac.h create mode 100644 climate.py diff --git a/automation.h b/automation.h new file mode 100644 index 0000000..a62fb27 --- /dev/null +++ b/automation.h @@ -0,0 +1,65 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "aux_ac.h" + +namespace esphome { +namespace aux_ac { + + template + class AirConDisplayOffAction : public Action + { + public: + explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {} + + void play(Ts... x) override { this->ac_->displayOffSequence(); } + + protected: + AirCon *ac_; + }; + + template + class AirConDisplayOnAction : public Action + { + public: + explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {} + + void play(Ts... x) override { this->ac_->displayOnSequence(); } + + protected: + AirCon *ac_; + }; + + template + class AirConSendTestPacketAction : public Action + { + public: + explicit AirConSendTestPacketAction(AirCon *ac) : ac_(ac) {} + void set_data_template(std::function(Ts...)> func) { + this->data_func_ = func; + this->static_ = false; + } + void set_data_static(const std::vector &data) { + this->data_static_ = data; + this->static_ = true; + } + + void play(Ts... x) override { + if (this->static_) { + this->ac_->sendTestPacket(this->data_static_); + } else { + auto val = this->data_func_(x...); + this->ac_->sendTestPacket(val); + } + } + + protected: + AirCon *ac_; + bool static_{false}; + std::function(Ts...)> data_func_{}; + std::vector data_static_{}; + }; + +} // namespace aux_ac +} // namespace esphome \ No newline at end of file diff --git a/aux_ac.h b/aux_ac.h new file mode 100644 index 0000000..e8efa3e --- /dev/null +++ b/aux_ac.h @@ -0,0 +1,3194 @@ +// Custom ESPHome component for AUX-based air conditioners +// Need some soldering skills +// Source code and detailed instructions are available on github: https://github.com/GrKoR/esphome_aux_ac_component +/// немного переработанная версия старого компонента +#pragma once + +#include +#include "esphome.h" +#include +#include "esphome/core/component.h" +#include "esphome/components/climate/climate.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/core/helpers.h" +#if defined(ESP32) + #include "esphome/core/preferences.h" +#else + #warning "Saving presets does not work with ESP8266" +#endif + +//#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны + +namespace esphome { +namespace aux_ac { + + +using climate::ClimatePreset; +using climate::ClimateTraits; +using climate::ClimateMode; +using climate::ClimateSwingMode; +using climate::ClimateFanMode; + + +class Constants { +public: + static const std::string AC_FIRMWARE_VERSION; + + static const char *const TAG; + static const std::string MUTE; + static const std::string TURBO; + static const std::string CLEAN; + static const std::string HEALTH; + static const std::string ANTIFUNGUS; + + /// минимальная и максимальная температура в градусах Цельсия, ограничения самого кондиционера + static const float AC_MIN_TEMPERATURE; + static const float AC_MAX_TEMPERATURE; + /// шаг изменения целевой температуры, градусы Цельсия + static const float AC_TEMPERATURE_STEP; + + // периодичность опроса кондиционера на предмет изменения состояния + // изменение параметров с пульта не сообщается в UART, поэтому надо запрашивать состояние, чтобы быть в курсе + // значение в миллисекундах + static const uint32_t AC_STATES_REQUEST_INTERVAL; +}; + +const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; +const char *const Constants::TAG = "AirCon"; +const std::string Constants::MUTE = "mute"; +const std::string Constants::TURBO = "turbo"; +const std::string Constants::CLEAN = "clean"; +const std::string Constants::HEALTH = "health"; +const std::string Constants::ANTIFUNGUS = "antifungus"; +const float Constants::AC_MIN_TEMPERATURE = 16.0; +const float Constants::AC_MAX_TEMPERATURE = 32.0; +const float Constants::AC_TEMPERATURE_STEP = 0.5; +const uint32_t Constants::AC_STATES_REQUEST_INTERVAL = 7000; + +class AirCon; + +// состояния конечного автомата компонента +enum acsm_state : uint8_t { + ACSM_IDLE = 0, // ничего не делаем, ждем, на что бы среагировать + ACSM_RECEIVING_PACKET, // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны + ACSM_PARSING_PACKET, // разбираем полученный пакет + ACSM_SENDING_PACKET, // отправляем пакет сплиту +}; + +/** + * Кондиционер отправляет пакеты следующей структуры: + * HEADER: 8 bytes + * BODY: 0..24 bytes + * CRC: 2 bytes + * Весь пакет максимум 34 байта + * По крайней мере все встреченные мной пакеты имели такой размер и структуру. + **/ +#define AC_HEADER_SIZE 8 +#define AC_MAX_BODY_SIZE 24 +#define AC_BUFFER_SIZE 35 + +/** + * таймаут загрузки пакета + * + * через такое количиство миллисекунд конечный автомат перейдет из состояния ACSM_RECEIVING_PACKET в ACSM_IDLE, если пакет не будет загружен + * расчетное время передачи 1 бита при скорости 4800 примерно 0,208 миллисекунд; + * 1 байт передается 11 битами (1 стартовый, 8 бит данных, 1 бит четности и 1 стоповый бит) или 2,30 мс. + * максимальный размер пакета AC_BUFFER_SIZE = 34 байта => 78,2 мсек. Плюс накладные расходы. + * Скорее всего на получение пакета должно хватать 100 мсек. + * + * По факту проверка показала: + * - если отрабатывать по 1 символу из UART на один вызов loop, то на 10 байт пинг-пакета требуется 166 мсек. + * То есть примерно по 16,6 мсек на байт. Примем 17 мсек. + * Значит на максимальный пакет потребуется 17*34 = 578 мсек. Примем 600 мсек. + * - если отрабатывать пакет целиком или хотя бы имеющимися в буфере UART кусками, то на 10 байт пинг-пакета требуется 27 мсек. + * То есть примерно по 2,7 мсек. на байт. Что близко к расчетным значениям. Примем 3 мсек. + * Значит на максимальный пакет потребуется 3*34 = 102 мсек. Примем 150 мсек. + * Опыт показал, что 150 мсек вполне хватает на большие пакеты + **/ +#define AC_PACKET_TIMEOUT 150 // 150 мсек - отработка буфера UART за раз, 600 мсек - отработка буфера UART по 1 байту за вызов loop + +// типы пакетов +#define AC_PTYPE_PING 0x01 // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает +#define AC_PTYPE_CMD 0x06 // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита +#define AC_PTYPE_INFO 0x07 // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля +#define AC_PTYPE_INIT 0x09 // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. +#define AC_PTYPE_UNKN 0x0b // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует + +// типы команд +#define AC_CMD_STATUS_BIG 0x21 // большой пакет статуса кондиционера +#define AC_CMD_STATUS_SMALL 0x11 // маленький пакет статуса кондиционера +#define AC_CMD_STATUS_PERIODIC 0x2C // иногда встречается, сплит её рассылает по своему разумению; (вроде бы может быть и другой код! надо больше данных) +#define AC_CMD_SET_PARAMS 0x01 // команда установки параметров кондиционера + +// значения байтов в пакетах +#define AC_PACKET_START_BYTE 0xBB // Стартовый байт любого пакета 0xBB, других не встречал +#define AC_PACKET_ANSWER 0x80 // признак ответа wifi-модуля + +// заголовок пакета +struct packet_header_t { + uint8_t start_byte; // стартовый бит пакета, всегда 0xBB + uint8_t _unknown1; // не расшифрован + uint8_t packet_type; // тип пакета: + // 0x01 - пинг + // 0x06 - команда кондиционеру + // 0x07 - информационный пакет со статусом кондиционера + // 0x09 - (не разбирался) инициирование коннекта wifi-модуля с приложением на телефоне, с ESP работает и без этого + // 0x0b - (не разбирался) wifi-модуль так сигналит, когда не получает пинги от кондиционера и в каких-то еще случаях + uint8_t wifi; // признак пакета от wifi-модуля + // 0x80 - для всех сообщений, посылаемых модулем + // 0x00 - для всех сообщений, посылаемых кондиционером + uint8_t ping_answer_01; // не расшифрован, почти всегда 0x00, только в ответе на ping этот байт равен 0x01 + uint8_t _unknown2; // не расшифрован + uint8_t body_length; // длина тела пакета в байтах + uint8_t _unknown3; // не расшифрован +}; + +// CRC пакета +union packet_crc_t { + uint16_t crc16; + uint8_t crc[2]; +}; + +struct packet_t { + uint32_t msec; // значение millis в момент определения корректности пакета + packet_header_t * header; + packet_crc_t * crc; + uint8_t * body; // указатель на первый байт тела; можно приведением типов указателей обращаться к отдельным битам как к полям соответсвующей структуры + uint8_t bytesLoaded; //количество загруженных в пакет байт, включая CRC + uint8_t data[AC_BUFFER_SIZE]; +}; + +// тело ответа на пинг +struct packet_ping_answer_body_t { + uint8_t byte_1C = 0x1C; // первый байт всегда 0x1C + uint8_t byte_27 = 0x27; // второй байт тела пинг-ответа всегда 0x27 + uint8_t zero1 = 0; // всегда 0x00 + uint8_t zero2 = 0; // всегда 0x00 + uint8_t zero3 = 0; // всегда 0x00 + uint8_t zero4 = 0; // всегда 0x00 + uint8_t zero5 = 0; // всегда 0x00 + uint8_t zero6 = 0; // всегда 0x00 +}; + +// тело большого информационного пакета +struct packet_big_info_body_t { + uint8_t byte_01; // всегда 0x01 + uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x21); + // пакет может рассылаться и в дежурном режиме (без запроса со стороны wifi-модуля) + // в этом случае тут могут быть значения, отличные от 0x21 + // БАЙТ2 + uint8_t reserv20 :5; // не расшифрован, всегда 0xC0 11000000 + bool is_invertor :1; // флаг инвертора + uint8_t reserv21 :2; // для RoyalClima18HNI: всегда 0xE0 11100000 + // Brokly: для Energolux Bern: 0xE0; иногда, с равными промежутками во времени проскакивает )xE4 + // предполагаю, что это байт конфига кондиционера (5 бит - инвертер), (2 бит - периодический мпульсный сигнал,период пимерно 500 сек) + // БАЙТ 3 + bool power:1; + bool sleep:1; + bool louver_V:1; + uint8_t louver_H:2; // у шторок лево-право, почему то два бита + uint8_t mode:3; // enum { AC_BIG_MODE_AUTO = 0, + // AC_BIG_MODE_COOL = 1, + // AC_BIG_MODE_DRY = 2, + // AC_BIG_MODE_HEAT = 4, + // AC_BIG_MODE_FAN = 6} + // + // + // Встречались такие значения: + // 0x04 100 - сплит выключен, до этого работал (статус держится 1 час после выкл.) + // 0x05 101 - режим AUTO + // 0x24 100100 - режим OFF + // 0x25 100101 - режим COOL + // 0x39 111001 - ?? + // 0x45 1000101 - режим DRY + // 0x85 10000101 - режим HEAT + // 0xC4 11000100 - режим OFF, выключен давно, зима + // 0xC5 11000101 - режим FAN + // Brokly: + // Встречались такие значения : + // 0x00 00000000 - OFF + // 0x01 00000001 - AUTO // режим авто, нет отдельного бита + // 0x41 1000001 - DRY + // 0x21 100001 - COOL + // 0x81 10000001 - HEAT + // 0x85 10000101 - HEAT+шторки верх-низ + // 0x99 10011001 - HEAT+шторки влево вправо + // 0xC1 11000001 - FAN // 7 и 6 бит связаны + // 0x80 10000000 - продувка после переключения из HEAT в OFF + // 0xC5 11000101 - FAN+шторки верх-низ + // 0xDD 11011101 - FAN+шторки лево-право/верх-низ + // 0xD9 11011001 - FAN+шторки лево-право + // 0xD8 11011000 - из FAN+шторки лево-право в OFF + // 0x39 111001 - COOL+шторки лево-право + // Очевидно битовые, но связные, поля, предположительные зависимости + // ВНИМАНИЕ : режимы номинальны, например в режиме АВТО нагрев или охлаждение не отображаются + // 7+6+5 4+3 2 1 0 + // MODE Louv_L Louv_H SLEEP ON/OFF + // + // ФУНКЦМЯ CLEEN, HEALTH, ANTIFUNGUS на данный байт не влияют + // + // #define AC_BIG_MASK_MODE b11100000 + // enum { AC_BIG_MODE_DRY = 0x40, + // AC_BIG_MODE_COOL = 0x20, + // AC_BIG_MODE_HEAT = 0x80, + // AC_BIG_MODE_FAN = 0xC0} + // #define AC_BIG_MASK_MODE b00011100 + // enum { AC_BIG_LOUVERS_H = 0x04, + // AC_BIG_LOUVERS_L = 0x18, + // AC_BIG_LOUVERS_BOTH = 0x1C} + // #define AC_BIG_MASK_POWER b00000001 + // #define AC_BIG_MASK_SLEEP b00000010 + // #define AC_BIG_MASK_COOL b00100000 + // + // БАЙТ 4 + uint8_t reserv40:4; // 8 + bool needDefrost:1; // 5 бит начало разморозки(накопление тепла) + bool defrostMode:1; // 6 бит режим разморозки внешнего блока (прогрев испарителя) + bool reserv41:1; // для RoyalClima18HNI: режим разморозки внешнего блока - 0x20, в других случаях 0x00 + bool cleen:1; // 8 бит CLEAN + + // Для кондея старт-стоп + // x xx + // C5 11000101 + // C4 11000100 + // 85 10000101 + // 84 10000100 + // 3D 00111101 + // 3C 00111100 + // 25 00100101 + // 24 00100100 + // 5 00000101 + // 4 00000100 + + + // БАЙТ5 + uint8_t realFanSpeed:3; // Brokly: Energolux Bern - подтверждаю, ВАЖНО !!!! Это реальная скорость фена + uint8_t reserv30:5; // та которая в данный момент. Например может быть установлен нагрев со скоростью + // вентилятора HI, но кондей еще не произвел достаточно тепла, и крутит на LOW, + // Тут будет отображаться LOW + // fanSpeed: OFF=0x00, MUTE=0x01, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07 + // в дежурных пакетах тут похоже что-то другое + // БАЙТ6 + bool reserv60:1; + uint8_t fanPWM:7; // скорость шима вентилятора + // 126...128 - turbo + // 100...113 - hi + // 84...85 - mid + // 59...62 - low + // 0 - off + // + // БАЙТ7 + uint8_t ambient_temperature_int; // Brokly: ПОДТВЕРЖДАЮ это точно показания датчика под крышкой внутреннего блока, + // физически доступен для пользователя + // целая часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы + // перевод по формуле T = Тin - 0x20 + Tid/10 + // где + // Tin - целая часть температуры + // Tid - десятичная часть температуры + + // В ВЫКЛЮЧЕНОМ СОСТОНИИ занчение 7 8 9 10 равны !!!!!! + // А значит это термодатчики внутри внутреннего блока !!!!!! + + // БАЙТ8 + uint8_t zero3; // Brokly: полностью повторяет значение 9 байта + + // БАЙТ9 + uint8_t in_temperature_int; // Brokly: скорее всего это действительно какая то температура или дельта температур + // СКОРЕЕ ВСЕГО ЭТО ТЕМПЕРАТУРА ПОДАЧИ !!!!!! При охлаждении - холодная, при нагреве теплая + + // холоднее или горячее температуры в команте. В выключеном состоянии стремится к комнатной темп. + // у меня на трех инверторных кондиционерах Energolux серии Bern + // в выключеном состоянии значение этого байта находится на уровне 57-68 (мощность 0%) + // зависит от мощности работы компрессора (измерения при 12гр на улице) + // в режиме охлаждения уменьшается и при мощности 47% = 40 / 73% = 38 + // в режиме нагрева увеличивается и при мощности 47% = 70 / 73% = 75 / 84% = 84 + // изменение этого значения более вялое, с западыванием относительно изменения мощности + // видимо является реакцией (следствием работы) на изменение мощности инвертора + // учитывая стиль записи температур имеет смысл рассматривать это значение как увеличенное на 0x20 + + + + // этот байт как-то связан с температурой во внешнем блоке. Требуются дополнительные исследования. + // При выключенном сплите характер изменения значения примерно соответствует изменению температуры на улице. + // При включенном сплите значение может очень сильно скакать. + // По схеме wiring diagram сплит-системы, во внешнем блоке есть термодатчик, отслеживающий температуру испарителя. + // Возможно, этот байт как раз и отражает изменение температуры на испарителе. + // Но я не смог разобраться, как именно перевести эти значения в градусы. + // Кроме того, зимой даже в минусовую температуру этот байт не уходит ниже 0x33 по крайней мере + // для температур в диапазоне -5..-10 градусов Цельсия. + // БАЙТ10 + uint8_t zero4; // Brokly: полностью повторяет значение 9 байта + + // БАЙТ11 + uint8_t zero5; // всегда = 100 (0x64) + + // БАЙТ12 + uint8_t outdoor_temperature; // Brokly Energolux Bern: Внешняя температура формула T=БАЙТ12 - 0x20 + // Датчик на радиаторе внешнего блока, доступен для пользователя, без разборки блока + // температура внешнего теплообменника влияет на это значение (при работе на обогрев - понижает, при охлаждении или при разморозке - повышает) + // для RoyalClima18HNI: похоже на какую-то температуру, точно неизвестно + + // БАЙТ13 + uint8_t out_temperature_int; // всегда 0x00 + // для RoyalClima18HNI: 0x20 + // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ13 - 0x20 + // При охлаждении растет, при нагреве падает, можно делать вывод о режиме COOL или HEAT + // ПОХОЖЕ НА ТЕМПЕРАТУРУ ОБРАТКИ !!! + + // БАЙТ14 + uint8_t strange_temperature_int;// всегда 0x00 + // для RoyalClima18HNI: 0x20 + // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ14 - 0x20 + // показания РАСТЕТ ПРИ ВКЛЮЧЕНИИ ИНВЕРТОРА, при выключении падают до комнатной + // от режима охлаждения или нагрева не зависит !!! + + + // БАЙТ15 + uint8_t zero9; // всегда 0x00, Brokly: Energolux Bern, всегда 0x39 111001 + // БАЙТ16 + uint8_t invertor_power; // МОщность инвертера (Brokly: подтверждаю) + // для RoyalClima18HNI: мощность инвертора (от 0 до 100) в % + // например, разморозка внешнего блока происходит при 80% + // БАЙТ17 + uint8_t zero11; // всегда 0x00 + // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 22, когда инвертор отключен и тут 0 + // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика достаточно плавна + + // БАЙТ18 + uint8_t zero12; // + // Brokly: Energolux Bern : наложение на показания инвертора (от 144 до 174, когда инвертор отключен + // показания немного скачут в районе 149...154, при включении инвертора быстро растет, при выключении + // моментально падает до 149...154, бывают опускания ниже этих значений до 144, чаще в момент первоначального + // включения инвертора, а потом вверх, не всегда. При включении уходит в 0 на одну посылку + + // БАЙТ19 + uint8_t zero13; // Brokly: Energolux Bern : включение 144 -> 124 -> 110 далее все время держим 110 + + // БАЙТ20 + uint8_t zero14; // + // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 45, когда инвертор отключен и тут 0 + // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика дрожащая нестабильная + // колебания в районе +-2...4 единицы + // БАЙТ21 + uint8_t zero15; // всегда 0x00 Brokly: подтверждаю + + // БАЙТ22 + uint8_t zero16; // всегда 0x00 Brokly: подтверждаю + + // БАЙТ23 + uint8_t ambient_temperature_frac:4; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы + // подробнее смотреть ambient_temperature_int + // для RoyalClima18HNI: старшие 4 бита - 0x2 + uint8_t reserv023:4; +}; + +// тело малого информационного пакета +struct packet_small_info_body_t { + uint8_t byte_01; // не расшифрован, всегда 0x01 + uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x11); + // в пакетах сплита другие варианты не встречаются + // в отправляемых wifi-модулем пакетах тут может быть 0x01, если требуется установить режим работы + uint8_t target_temp_int_and_v_louver; // целая часть целевой температуры и положение вертикальных жалюзи + // три младших бита - положение вертикальных жалюзи + // если они все = 0, то вертикальный SWING включен + // если они все = 1, то выключен вертикальный SWING + // протокол универсильный, другие комбинации битов могут задавать какие-то положения + // вертикальных жалюзи, но у меня на пульте таких возможностей нет, надо экспериментировать. + // пять старших бит - целая часть целевой температуры + // температура определяется по формуле: + // 8 + (target_temp_int_and_v_louver >> 3) + (0.5 * (target_temp_frac >> 7)) + uint8_t h_louver; // старшие 3 бита - положение горизонтальных жалюзи, остальное не изучено и всегда было 0 + // если все 3 бита = 0, то горизонтальный SWING включен + // если все 3 бита = 1, то горизонтальный SWING отключен + // надо изучить другие комбинации + uint8_t target_temp_frac; // старший бит - дробная часть целевой температуры + // остальные биты до конца не изучены: + // бит 6 был всегда 0 + // биты 0..5 растут на 1 каждую минуту, возможно внутренний таймер для включения/выключения по времени + uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны + // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 + uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны + // БФЙТ 7 + uint8_t mode; // режим работы сплита: + // AUTO : bits[7, 6, 5] = [0, 0, 0] + // COOL : bits[7, 6, 5] = [0, 0, 1] + // DRY : bits[7, 6, 5] = [0, 1, 0] + // HEAT : bits[7, 6, 5] = [1, 0, 1] + // FAN : bits[7, 6, 5] = [1, 1, 1] + // Sleep function : bit 2 = 1 + // iFeel function : bit 3 = 1 + uint8_t zero1; // всегда 0x00 + uint8_t zero2; // всегда 0x00 + uint8_t status; // бит 5 = 1: включен, обычный режим работы (когда можно включить нагрев, охлаждение и т.п.) + // бит 2 = 1: режим самоочистки, должен запускаться только при бит 5 = 0 + // бит 0 и бит 1: активация режима ионизатора воздуха (не проверен, у меня его нет) + uint8_t zero3; // всегда 0x00 + uint8_t display_and_mildew; // бит4 = 1, чтобы погасить дисплей на внутреннем блоке сплита + // бит3 = 1, чтобы включить функцию "антиплесень" (после отключения как-то прогревает или просушивает теплообменник, чтобы на нем не росла плесень) + uint8_t zero4; // всегда 0x00 + uint8_t target_temp_frac2; // дробная часть целевой температуры, может быть только 0x00 и 0x05 + // при установке температуры тут 0x00, а заданная температура передается в target_temp_int_and_v_louver и target_temp_frac + // после установки сплит в информационных пакетах тут начинает показывать дробную часть + // не очень понятно, зачем так сделано +}; + +//**************************************************************************************************************************************************** +//*************************************************** ПАРАМЕТРЫ РАБОТЫ КОНДИЦИОНЕРА ****************************************************************** +//**************************************************************************************************************************************************** + +// для показаний о реальной скорости фена из большого пакета +enum ac_realFan : uint8_t { AC_REAL_FAN_OFF = 0x00, AC_REAL_FAN_MUTE = 0x01, AC_REAL_FAN_LOW = 0x02, AC_REAL_FAN_MID = 0x04, + AC_REAL_FAN_HIGH = 0x06, AC_REAL_FAN_TURBO = 0x07, AC_REAL_FAN_UNTOUCHED = 0xFF }; + +// для всех параметров ниже вариант X_UNTOUCHED = 0xFF означает, что этот параметр команды должен остаться тот, который уже установлен +// питание кондиционера +#define AC_POWER_MASK 0b00100000 +enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTOUCHED = 0xFF }; + +// режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF +#define AC_CLEAN_MASK 0b00000100 +enum ac_clean : uint8_t { AC_CLEAN_OFF = 0x00, AC_CLEAN_ON = 0x04, AC_CLEAN_UNTOUCHED = 0xFF }; + +// для включения ионизатора нужно установить второй бит в байте +// по результату этот бит останется установленным, но кондиционер еще и установит первый бит +#define AC_HEALTH_MASK 0b00000010 +enum ac_health : uint8_t { AC_HEALTH_OFF = 0x00, AC_HEALTH_ON = 0x02, AC_HEALTH_UNTOUCHED = 0xFF }; + +// Статус ионизатора. Если бит поднят, то обнаружена ошибка ключения ионизатора +#define AC_HEALTH_STATUS_MASK 0b00000001 +enum ac_health_status : uint8_t { AC_HEALTH_STATUS_OFF = 0x00, AC_HEALTH_STATUS_ON = 0x01, AC_HEALTH_STATUS_UNTOUCHED = 0xFF }; + +// целевая температура +#define AC_TEMP_TARGET_INT_PART_MASK 0b11111000 +#define AC_TEMP_TARGET_FRAC_PART_MASK 0b10000000 + +// задержка отключения кондиционера +#define AC_TIMER_MINUTES_MASK 0b00111111 +#define AC_TIMER_HOURS_MASK 0b00011111 + +// включение таймера сна +#define AC_TIMER_MASK 0b01000000 +enum ac_timer : uint8_t {AC_TIMER_OFF = 0x00, AC_TIMER_ON = 0x40, AC_TIMER_UNTOUCHED = 0xFF}; + +// основные режимы работы кондиционера +#define AC_MODE_MASK 0b11100000 +enum ac_mode : uint8_t { AC_MODE_AUTO = 0x00, AC_MODE_COOL = 0x20, AC_MODE_DRY = 0x40, AC_MODE_HEAT = 0x80, AC_MODE_FAN = 0xC0, AC_MODE_UNTOUCHED = 0xFF }; + +// Ночной режим (SLEEP). Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. +// COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. +// HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. +// Восстанавливается ли температура через 7 часов при отключении режима - не понятно. +#define AC_SLEEP_MASK 0b00000100 +enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTOUCHED = 0xFF }; + +// функция iFeel - поддерживате температуру по датчику в пульте ДУ, а не во внутреннем блоке кондиционера +#define AC_IFEEL_MASK 0b00001000 +enum ac_ifeel : uint8_t { AC_IFEEL_OFF = 0x00, AC_IFEEL_ON = 0x08, AC_IFEEL_UNTOUCHED = 0xFF }; + +// Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. +// ToDo: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V +#define AC_LOUVERV_MASK 0b00000111 +enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_UPDOWN = 0x00, AC_LOUVERV_OFF = 0x07, AC_LOUVERV_UNTOUCHED = 0xFF }; + +// Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. +// ToDo: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H +#define AC_LOUVERH_MASK 0b11100000 +enum ac_louver_H : uint8_t { AC_LOUVERH_SWING_LEFTRIGHT = 0x00, AC_LOUVERH_OFF = 0xE0, AC_LOUVERH_UNTOUCHED = 0xFF }; + +struct ac_louver { + ac_louver_H louver_h; + ac_louver_V louver_v; +}; + +// скорость вентилятора +#define AC_FANSPEED_MASK 0b11100000 +enum ac_fanspeed : uint8_t { AC_FANSPEED_HIGH = 0x20, AC_FANSPEED_MEDIUM = 0x40, AC_FANSPEED_LOW = 0x60, AC_FANSPEED_AUTO = 0xA0, AC_FANSPEED_UNTOUCHED = 0xFF }; + +// TURBO работает только в режимах COOL и HEAT +#define AC_FANTURBO_MASK 0b01000000 +enum ac_fanturbo : uint8_t { AC_FANTURBO_OFF = 0x00, AC_FANTURBO_ON = 0x40, AC_FANTURBO_UNTOUCHED = 0xFF }; + +// MUTE работает только в режиме FAN. В режиме COOL кондей команду принимает, но MUTE не устанавливается +#define AC_FANMUTE_MASK 0b10000000 +enum ac_fanmute : uint8_t { AC_FANMUTE_OFF = 0x00, AC_FANMUTE_ON = 0x80, AC_FANMUTE_UNTOUCHED = 0xFF }; + +// включение-выключение дисплея на корпусе внутреннего блока +#define AC_DISPLAY_MASK 0b00010000 +enum ac_display : uint8_t { AC_DISPLAY_OFF = 0x00, AC_DISPLAY_ON = 0x10, AC_DISPLAY_UNTOUCHED = 0xFF }; + +// включение-выключение функции "Антиплесень". +// По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. Уличный блок при этом гудит и тарахтит. +// Возможно, прогревается теплообменник для высыхания. Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. +#define AC_MILDEW_MASK 0b00001000 +enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_UNTOUCHED = 0xFF }; + +// маска счетчика минут прошедших с последней команды +#define AC_MIN_COUTER 0b00111111 // + +// настройка усреднения фильтра температуры. Это значение - взнос нового измерения +// в усредненные показания в процентах +#define OUTDOOR_FILTER_PESCENT 0.2 + +/** команда для кондиционера + * + * ВАЖНО! В коде используется копирование команд простым присваиванием. + * Если в структуру будут введены указатели, то копирование надо будет изменить! +*/ + +// данные структур содержат настройку, специально вынес в макрос +#define AC_COMMAND_BASE float temp_target;\ + ac_power power;\ + ac_clean clean;\ + ac_health health;\ + ac_mode mode;\ + ac_sleep sleep;\ + ac_louver louver;\ + ac_fanspeed fanSpeed;\ + ac_fanturbo fanTurbo;\ + ac_fanmute fanMute;\ + ac_display display;\ + ac_mildew mildew;\ + ac_timer timer;\ + uint8_t timer_hours;\ + uint8_t timer_minutes;\ + bool temp_target_matter + +// чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше +// из-за такого приема нужно контролировать размер копируемых данных руками +#define AC_COMMAND_BASE_SIZE 20 + +struct ac_command_t { +/* + ac_power power; + ac_clean clean; + ac_health health; // включение ионизатора + ac_mode mode; + ac_sleep sleep; + ac_louver louver; + ac_fanspeed fanSpeed; + ac_fanturbo fanTurbo; + ac_fanmute fanMute; + ac_display display; + ac_mildew mildew; + ac_timer timer; + uint8_t timer_hours; + uint8_t timer_minutes; + float temp_target; + bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную +*/ + AC_COMMAND_BASE; + ac_health_status health_status; + float temp_ambient; // внутренняя температура + int8_t temp_outdoor; // внешняя температура + int8_t temp_inbound; // температура входящая + int8_t temp_outbound; // температура исходящая + int8_t temp_strange; // непонятная температура, понаблюдаем + ac_realFan realFanSpeed; // текущая скорость вентилятора + uint8_t invertor_power; // мощность инвертора + uint8_t pressure; // предположительно давление + bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) + ac_ifeel iFeel; +}; + +// структура для сохранения данных +struct ac_save_command_t { + AC_COMMAND_BASE; +}; + +// номера сохранений пресетов +enum store_pos : uint8_t { + POS_MODE_AUTO = 0, + POS_MODE_COOL, + POS_MODE_DRY, + POS_MODE_HEAT, + POS_MODE_FAN, + POS_MODE_OFF}; + +typedef ac_command_t ac_state_t; // текущее состояние параметров кондея можно хранить в таком же формате, как и комманды + +//**************************************************************************************************************************************************** +//************************************************ КОНЕЦ ПАРАМЕТРОВ РАБОТЫ КОНДИЦИОНЕРА ************************************************************** +//**************************************************************************************************************************************************** + + +/***************************************************************************************************************************************************** + * структуры и типы для последовательности команд + ***************************************************************************************************************************************************** + * + * Последовательность команд позволяет выполнить несколько последовательных команд с контролем получаемых в ответ пакетов. + * Если требуется, в получаемых в ответ пакетах можно контролировать значение любых байт. + * Для входящего пакета байт, значение которого не проверяется, должен быть установлен в AC_SEQUENCE_ANY_BYTE. + * Контроль возможен только для входящих пакетов, исходящие отправляются "как есть". + * + * Для исходящих пакетов значения CRC могут не рассчитываться, контрольная сумма будет рассчитана автоматически. + * Для входящих пакетов значение CRC также можно не рассчитывать, установив байты CRC в AC_SEQUENCE_ANY_BYTE, + * так как контроль CRC для получаемых пакетов выполняется автоматически при получении. + * + * Для входящих пакетов в последовательности можно указать таймаут. Если таймаут равен 0, то используется значение AC_SEQUENCE_DEFAULT_TIMEOUT. + * Если в течение указанного времени подходящий пакет не будет получен, то последовательность прерывается с ошибкой. + * Пинг-пакеты в последовательности игнорируются. + * + * Пауза в последовательности задается значением timeout элемента AC_DELAY. Никакие другие параметры такого элемента можно не заполнять. + * + **/ +// максимальная длина последовательности; больше вроде бы не требовалось +#define AC_SEQUENCE_MAX_LEN 0x0F + +// в пакетах никогда не встречалось значение 0xFF (только в CRC), поэтому решено его использовать как признак не важного значение байта +//#define AC_SEQUENCE_ANY_BYTE 0xFF + +// дефолтный таймаут входящего пакета в миллисекундах +// если для входящего пакета в последовательности указан таймаут 0, то используется значение по-умолчанию +// если нужный пакет не поступил в течение указанного времени, то последовательность прерывается с ошибкой +// Brokly: пришлось увеличить +#define AC_SEQUENCE_DEFAULT_TIMEOUT 580 + +enum sequence_item_type_t : uint8_t { + AC_SIT_NONE = 0x00, // пустой элемент последовательности + AC_SIT_DELAY = 0x01, // пауза в последовательности на нужное количество миллисекунд + AC_SIT_FUNC = 0x02 // рабочий элемент последовательности +}; + +// тип пакета в массиве последовательности +// информирует о том, что за пакет лежит в поле packet элемента последовательности +enum sequence_packet_type_t : uint8_t { + AC_SPT_CLEAR = 0x00, // пустой пакет + AC_SPT_RECEIVED_PACKET = 0x01, // полученный пакет + AC_SPT_SENT_PACKET = 0x02 // отправленный пакет +}; + +/** элемент последовательности + * Поля item_type, func, timeout и cmd устанавливаются ручками и задают параметры выполнения шага последовательности. + * Поля msec, packet_type и packet заполняются движком при обработке последовательности. + **/ +struct sequence_item_t { + sequence_item_type_t item_type; // тип элемента последовательности + bool (AirCon::*func)(); // указатель на функцию, отрабатывающую шаг последовательности + uint16_t timeout; // допустимый таймаут в ожидании пакета (применим только для входящих пакетов) + ac_command_t cmd; // новое состояние сплита, нужно для передачи кондиционеру команд + //******* поля ниже заполняются функциями обработки последовательности *********** + uint32_t msec; // время старта текущего шага последовательности (для входящего пакета и паузы) + sequence_packet_type_t packet_type; // тип пакета (входящий, исходящий или вовсе не пакет) + packet_t packet; // данные пакета +}; +/*****************************************************************************************************************************************************/ + + +class AirCon : public esphome::Component, public esphome::climate::Climate { + private: + + // массив для сохранения данных глобальных персетов + ac_save_command_t global_presets[POS_MODE_OFF+1]; + #if defined(ESP32) + // тут будем хранить данные глобальных пресетов во флеше + // ВНИМАНИЕ на данный момент 22.05.22 ESPHOME 20022.5.0 имеет ошибку + // траблтикет: https://github.com/esphome/issues/issues/3298 + // из-за этого сохранение в энергонезависимую память не работает !!! + ESPPreferenceObject storage = global_preferences->make_preference(this->get_object_id_hash(), true); + #endif + // настройка-ключ, для включения сохранения - восстановления настроек каждого + // режима работы в отдельности, то есть каждый режим работы имеет свои настройки + // температуры, шторок, скорости вентилятора, пресетов + bool _store_settings = false; + // флаги для сохранения пресетов + bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено + + // время последнего запроса статуса у кондея + uint32_t _dataMillis; + // периодичность обновления статуса кондея, по дефолту AC_STATES_REQUEST_INTERVAL + uint32_t _update_period = Constants::AC_STATES_REQUEST_INTERVAL; + + // надо ли отображать текущий режим работы внешнего блока + // в режиме нагрева, например, кондиционер может как греть воздух, так и работать в режиме вентилятора, если целевая темпреатура достигнута + // по дефолту показываем + bool _show_action = true; + + // как отрабатывается включание-выключение дисплея. + // если тут false, то 1 в соответствующем бите включает дисплей, а 0 выключает. + // если тут true, то 1 потушит дисплей, а 0 включит. + bool _display_inverted = false; + + // флаг типа кондиционера инвертор - true, ON/OFF - false, начальная установка false + // в таком режиме точность и скорость определения реального состояния системы для инвертора, + // будет работать, но будет ниже, переменная устанавливается при первом получении большого пакета; + // если эта переменная установлена, то режим работы не инверторного кондиционера будет распознаваться + // как "в простое" (IDLE) + bool _is_invertor = false; + + // поддерживаемые кондиционером опции + std::set _supported_modes{}; + std::set _supported_swing_modes{}; + std::set _supported_presets{}; + std::set _supported_custom_presets{}; + std::set _supported_custom_fan_modes{}; + + // состояние конечного автомата + acsm_state _ac_state = ACSM_IDLE; + + // текущее состояние задаваемых пользователем параметров системы + ac_state_t _current_ac_state; + + // флаг подключения к UART + bool _hw_initialized = false; + // указатель на UART, по которому общаемся с кондиционером + esphome::uart::UARTComponent *_ac_serial; + + // UART wrappers: peek + int peek() { + uint8_t data; + if (!_ac_serial->peek_byte(&data)) return -1; + return data; + } + + // UART wrappers: read + int read() { + uint8_t data; + if (!_ac_serial->read_byte(&data)) return -1; + return data; + } + + // флаг обмена пакетами с кондиционером (если проходят пинги, значит есть коннект) + bool _has_connection = false; + + // входящий и исходящий пакеты + packet_t _inPacket; + packet_t _outPacket; + + // пакет для тестирования всякой фигни + packet_t _outTestPacket; + + // последовательность пакетов текущий шаг в последовательности + sequence_item_t _sequence[AC_SEQUENCE_MAX_LEN]; + uint8_t _sequence_current_step; + + // флаг успешного выполнения стартовой последовательности команд + bool _startupSequenceComlete = false; + + // очистка последовательности команд + void _clearSequence(){ + for (uint8_t i = 0; i < AC_SEQUENCE_MAX_LEN; i++) { + _sequence[i].item_type = AC_SIT_NONE; + _sequence[i].func = nullptr; + _sequence[i].timeout = 0; + _sequence[i].msec = 0; + _sequence[i].packet_type = AC_SPT_CLEAR; + _clearPacket(&_sequence[i].packet); + _clearCommand(&_sequence[i].cmd); + } + _sequence_current_step = 0; + } + + // проверяет, есть ли свободные шаги в последовательности команд + bool _hasFreeSequenceStep(){ + return (_getNextFreeSequenceStep() < AC_SEQUENCE_MAX_LEN); + } + + // возвращает индекс первого пустого шага последовательности команд + uint8_t _getNextFreeSequenceStep(){ + for (size_t i = 0; i < AC_SEQUENCE_MAX_LEN; i++) { + if (_sequence[i].item_type == AC_SIT_NONE){ + return i; + } + } + // если свободных слотов нет, то возвращаем значение за пределом диапазона + return AC_SEQUENCE_MAX_LEN; + } + + // возвращает количество свободных шагов в последовательности + uint8_t _getFreeSequenceSpace() { + return (AC_SEQUENCE_MAX_LEN - _getNextFreeSequenceStep()); + } + + // добавляет шаг в последовательность команд + // возвращает false, если не нашлось места для шага + bool _addSequenceStep(const sequence_item_type_t item_type, bool (AirCon::*func)() = nullptr, ac_command_t *cmd = nullptr, uint16_t timeout = AC_SEQUENCE_DEFAULT_TIMEOUT){ + if (!_hasFreeSequenceStep()) return false; // если места нет, то уходим + if (item_type == AC_SIT_NONE) return false; // глупость какая-то, уходим + if ((item_type == AC_SIT_FUNC) && (func == nullptr)) return false; // должна быть передана функция для такого типа шага + if ((item_type != AC_SIT_DELAY) && (item_type != AC_SIT_FUNC)){ + // какой-то неизвестный тип + _debugMsg(F("_addSequenceStep: unknown sequence item type = %u"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__, item_type); + return false; + } + + uint8_t step = _getNextFreeSequenceStep(); + + _sequence[step].item_type = item_type; + + // если задержка нулевая, то присваиваем дефолтную задержку + if (timeout == 0) timeout = AC_SEQUENCE_DEFAULT_TIMEOUT; + _sequence[step].timeout = timeout; + + _sequence[step].func = func; + if (cmd != nullptr) _sequence[step].cmd = *cmd; // так как в структуре команды только простые типы, то можно вот так присваивать + + return true; + } + + // добавляет в последовательность шаг с задержкой + bool _addSequenceDelayStep(uint16_t timeout){ + return this->_addSequenceStep(AC_SIT_DELAY, nullptr, nullptr, timeout); + } + + // добавляет в последовательность функциональный шаг + bool _addSequenceFuncStep(bool (AirCon::*func)(), ac_command_t *cmd = nullptr, uint16_t timeout = AC_SEQUENCE_DEFAULT_TIMEOUT){ + return this->_addSequenceStep(AC_SIT_FUNC, func, cmd, timeout); + } + + // выполняет всю логику очередного шага последовательности команд + void _doSequence(){ + if (!hasSequence()) return; + + // если шаг уже максимальный из возможных + if (_sequence_current_step >= AC_SEQUENCE_MAX_LEN) { + // значит последовательность закончилась, надо её очистить + // при очистке последовательности будет и _sequence_current_step обнулён + _debugMsg(F("Sequence [step %u]: maximum step reached"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + _clearSequence(); + return; + } + + // смотрим тип текущего элемента в последовательности + switch (_sequence[_sequence_current_step].item_type) { + case AC_SIT_FUNC: { + // если указатель на функцию пустой, то прерываем последовательность + if (_sequence[_sequence_current_step].func == nullptr) { + _debugMsg(F("Sequence [step %u]: function pointer is NULL, sequence broken"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); + _clearSequence(); + return; + } + + // сохраняем время начала паузы + if (_sequence[_sequence_current_step].msec == 0) { + _sequence[_sequence_current_step].msec = millis(); + _debugMsg(F("Sequence [step %u]: step started"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + } + + // если таймаут не указан, берем значение по-умолчанию + if (_sequence[_sequence_current_step].timeout == 0 ) _sequence[_sequence_current_step].timeout = AC_SEQUENCE_DEFAULT_TIMEOUT; + + // если время вышло, то отчитываемся в лог и очищаем последовательность + if (millis() - _sequence[_sequence_current_step].msec >= _sequence[_sequence_current_step].timeout) { + _debugMsg(F("Sequence [step %u]: step timed out (it took %u ms instead of %u ms)"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step, millis() - _sequence[_sequence_current_step].msec, _sequence[_sequence_current_step].timeout); + _clearSequence(); + return; + } + + // можно вызывать функцию + // она самомтоятельно загружает отправляемые/полученные пакеты в packet последовательности + // а также самостоятельно увеличивает счетчик шагов последовательности _sequence_current_step + // единственное исключение - таймауты + if (!(this->*_sequence[_sequence_current_step].func)()) { + _debugMsg(F("Sequence [step %u]: error was occur in step function"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step, millis() - _sequence[_sequence_current_step].msec); + _clearSequence(); + return; + } + break; + } + + case AC_SIT_DELAY: { // это пауза в последовательности + // пауза задается параметром timeout элемента последовательности + // начало паузы сохраняется в параметре msec + + // сохраняем время начала паузы + if (_sequence[_sequence_current_step].msec == 0) { + _sequence[_sequence_current_step].msec = millis(); + _debugMsg(F("Sequence [step %u]: begin delay (%u ms)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step, _sequence[_sequence_current_step].timeout); + } + + // если время вышло, то переходим на следующий шаг + if (millis() - _sequence[_sequence_current_step].msec >= _sequence[_sequence_current_step].timeout) { + _debugMsg(F("Sequence [step %u]: delay culminated (plan = %u ms, fact = %u ms)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step, _sequence[_sequence_current_step].timeout, millis() - _sequence[_sequence_current_step].msec); + _sequence_current_step++; + } + break; + } + + case AC_SIT_NONE: // шаги закончились + default: // или какой-то мусор в последовательности + // надо очистить последовательность и уходить + _debugMsg(F("Sequence [step %u]: sequence complete"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + _clearSequence(); + break; + } + } + + // заполняет структуру команды нейтральными значениями + void _clearCommand(ac_command_t * cmd){ + cmd->clean = AC_CLEAN_UNTOUCHED; + cmd->display = AC_DISPLAY_UNTOUCHED; + cmd->fanMute = AC_FANMUTE_UNTOUCHED; + cmd->fanSpeed = AC_FANSPEED_UNTOUCHED; + cmd->fanTurbo = AC_FANTURBO_UNTOUCHED; + cmd->health = AC_HEALTH_UNTOUCHED; + cmd->health_status = AC_HEALTH_STATUS_UNTOUCHED; + cmd->iFeel = AC_IFEEL_UNTOUCHED; + cmd->louver.louver_h = AC_LOUVERH_UNTOUCHED; + cmd->louver.louver_v = AC_LOUVERV_UNTOUCHED; + cmd->mildew = AC_MILDEW_UNTOUCHED; + cmd->mode = AC_MODE_UNTOUCHED; + cmd->power = AC_POWER_UNTOUCHED; + cmd->sleep = AC_SLEEP_UNTOUCHED; + cmd->timer = AC_TIMER_UNTOUCHED; + cmd->timer_hours = 0; + cmd->timer_minutes = 0; + cmd->temp_target = 0; + cmd->temp_target_matter = false; + cmd->temp_ambient = 0; + cmd->temp_outdoor = 0; + cmd->temp_inbound = 0; + cmd->temp_outbound = 0; + cmd->temp_strange = 0; + cmd->realFanSpeed = AC_REAL_FAN_UNTOUCHED; + }; + + // очистка буфера размером AC_BUFFER_SIZE + void _clearBuffer(uint8_t * buf){ + memset(buf, 0, AC_BUFFER_SIZE); + } + + // очистка структуры пакета по указателю + void _clearPacket(packet_t * pckt){ + if (pckt == nullptr) { + _debugMsg(F("Clear packet error: pointer is NULL!"), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return; + } + pckt->crc = nullptr; + pckt->header = (packet_header_t *)(pckt->data); // заголовок же всегда стартует с начала пакета + pckt->msec = 0; + pckt->bytesLoaded = 0; + pckt->body = nullptr; + _clearBuffer(pckt->data); + } + + // очистка входящего пакета + void _clearInPacket(){ + _clearPacket(&_inPacket); + } + + // очистка исходящего пакета + void _clearOutPacket(){ + _clearPacket(&_outPacket); + _outPacket.header->start_byte = AC_PACKET_START_BYTE; // для исходящего сразу ставим стартовый байт + _outPacket.header->wifi = AC_PACKET_ANSWER; // для исходящего пакета сразу ставим признак ответа + } + + // копирует пакет из одной структуры в другую с корректным переносом указателей на заголовки и т.п. + bool _copyPacket(packet_t *dest, packet_t *src){ + if (dest == nullptr) return false; + if (src == nullptr) return false; + + dest->msec = src->msec; + dest->bytesLoaded = src->bytesLoaded; + memcpy(dest->data, src->data, AC_BUFFER_SIZE); + dest->header = (packet_header_t *)&dest->data; + if (dest->header->body_length > 0) dest->body = &dest->data[AC_HEADER_SIZE]; + dest->crc = (packet_crc_t *)&dest->data[AC_HEADER_SIZE + dest->header->body_length]; + + return true; + } + + // устанавливает состояние конечного автомата + // можно и напрямую устанавливать переменную, но для целей отладки лучше так + void _setStateMachineState(acsm_state state = ACSM_IDLE){ + if (_ac_state == state) return; // состояние не меняется + + _ac_state = state; + + switch (state) { + case ACSM_IDLE: + _debugMsg(F("State changed to ACSM_IDLE."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + break; + + case ACSM_RECEIVING_PACKET: + _debugMsg(F("State changed to ACSM_RECEIVING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + break; + + case ACSM_PARSING_PACKET: + _debugMsg(F("State changed to ACSM_PARSING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + break; + + case ACSM_SENDING_PACKET: + _debugMsg(F("State changed to ACSM_SENDING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + break; + + default: + _debugMsg(F("State changed to ACSM_IDLE by default. Given state is %02X."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, state); + _ac_state = ACSM_IDLE; + break; + } + } + + // состояние конечного автомата: IDLE + void _doIdleState(){ + // вначале нужно выполнить очередной шаг последовательности команд + _doSequence(); + + // Если нет входящих данных, значит можно отправить исходящий пакет, если он есть + if (_ac_serial->available() == 0) { + // если есть пакет на отправку, то надо отправлять + // вначале думал, что сейчас отправка пакетов тут не нужна, т.к. состояние ACSM_SENDING_PACKET устанавливается сразу в парсере пакетов + // но потом понял, что у нас пакеты уходят не только когда надо отвечать, но и мы можем быть инициаторами + // поэтому вызов отправки тут пригодится + if (_outPacket.msec > 0) _setStateMachineState(ACSM_SENDING_PACKET); + // иначе просто выходим + return; + }; + + if (this->peek() == AC_PACKET_START_BYTE) { + // если во входящий пакет что-то уже загружено, значит это какие-то ошибочные данные или неизвестные пакеты + // надо эту инфу вывалить в лог + if (_inPacket.bytesLoaded > 0){ + _debugMsg(F("Start byte received but there are some unparsed bytes in the buffer:"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__); + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); + } + _clearInPacket(); + _inPacket.msec = millis(); + _setStateMachineState(ACSM_RECEIVING_PACKET); + //******************************************** экспериментальная секция ************************************************************* + // пробуем сократить время ответа с помощью прямых вызовов обработчиков, а не через состояние IDLE + //_doReceivingPacketState(); + // получилось всё те же 123 мсек. Только изредка падает до 109 мсек. Странно. + // логический анализатор показал примерно то же время от начала запроса до окончания ответа. + // запрос имеет длительность 18 мсек (лог.анализатор говорит 22,5 мсек). + // ответ имеет длительность 41 мсек по лог.анализатору. + // длительность паузы между запросом и ответом порядка 60 мсек. + // Скорее всего за один вызов _doReceivingPacketState не удается загрузить весь пакет (на момент вызова не все байы поступили в буфер UART) + // и поэтому программа отдает управление ESPHome для выполнения своих задач + // Стоит ли переделать код наоборот для непрерывного выполнения всё время, пока ожидается посылка - не знаю. Может быть такой риалтайм и не нужен. + //*********************************************************************************************************************************** + + } else { + while (_ac_serial->available() > 0) + { + // если наткнулись на старт пакета, то выходим из while + // если какие-то данные были загружены в буфер, то они будут выгружены в лог при загрузке нового пакета + if (this->peek() == AC_PACKET_START_BYTE) break; + + // читаем байт в буфер входящего пакета + _inPacket.data[_inPacket.bytesLoaded] = this->read(); + _inPacket.bytesLoaded++; + + // если буфер уже полон, надо его вывалить в лог и очистить + if (_inPacket.bytesLoaded >= AC_BUFFER_SIZE){ + _debugMsg(F("Some unparsed data on the bus:"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__); + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); + _clearInPacket(); + } + } + } + }; + + // состояние конечного автомата: ACSM_RECEIVING_PACKET + void _doReceivingPacketState(){ + while (_ac_serial-> available() > 0) { + // если в буфере пакета данных уже под завязку, то надо сообщить о проблеме и выйти + if (_inPacket.bytesLoaded >= AC_BUFFER_SIZE) { + _debugMsg(F("Receiver: packet buffer overflow!"), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); + _clearInPacket(); + _setStateMachineState(ACSM_IDLE); + return; + } + + _inPacket.data[_inPacket.bytesLoaded] = this->read(); + _inPacket.bytesLoaded++; + + // данных достаточно для заголовка + if (_inPacket.bytesLoaded == AC_HEADER_SIZE) { + // указатель заголовка установлен еще при обнулении пакета, его можно не трогать + //_inPacket.header = (packet_header_t *)(_inPacket.data); + + // уже знаем размер пакета и можем установить указатели на тело пакета и CRC + _inPacket.crc = (packet_crc_t *)&(_inPacket.data[AC_HEADER_SIZE + _inPacket.header->body_length]); + if (_inPacket.header->body_length > 0) _inPacket.body = &(_inPacket.data[AC_HEADER_SIZE]); + + _debugMsg(F("Header loaded: timestamp = %010u, start byte = %02X, packet type = %02X, body size = %02X"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.msec, _inPacket.header->start_byte, _inPacket.header->packet_type, _inPacket.header->body_length); + } + + // если все байты пакета загружены, надо его распарсить + // максимальный по размеру пакет будет упираться в размер буфера. если такой пакет здесь не уйдет на парсинг, + // то на следующей итерации будет ошибка о переполнении буфера, которая в начале цикла while + if (_inPacket.bytesLoaded == AC_HEADER_SIZE + _inPacket.header->body_length + 2) { + _debugMsg(F("Packet loaded: timestamp = %010u, start byte = %02X, packet type = %02X, body size = %02X, crc = [%02X, %02X]."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.msec, _inPacket.header->start_byte, _inPacket.header->packet_type, _inPacket.header->body_length, _inPacket.crc->crc[0], _inPacket.crc->crc[1]); + _debugMsg(F("Loaded %02u bytes for a %u ms."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.bytesLoaded, (millis() - _inPacket.msec)); + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + _setStateMachineState(ACSM_PARSING_PACKET); + return; + } + } + + // если пакет не загружен, а время вышло, то надо вернуться в IDLE + if (millis() - _inPacket.msec >= AC_PACKET_TIMEOUT) { + _debugMsg(F("Receiver: packet timed out!"), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); + _clearInPacket(); + _setStateMachineState(ACSM_IDLE); + return; + } + }; + + // состояние конечного автомата: ACSM_PARSING_PACKET + void _doParsingPacket(){ + if (!_checkCRC(&_inPacket)) { + _debugMsg(F("Parser: packet CRC fail!"), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_ERROR, __LINE__); + _clearInPacket(); + _setStateMachineState(ACSM_IDLE); + return; + } + + bool stateChangedFlag = false; // флаг, показывающий, изменилось ли состояние кондиционера + uint8_t stateByte = 0; // переменная для временного сохранения текущих параметров сплита для проверки их изменения + float stateFloat = 0.0; // переменная для временного сохранения текущих параметров сплита для проверки их изменения + + // вначале выводим полученный пакет в лог, чтобы он шел до информации об ответах и т.п. + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); + + // разбираем тип пакета + switch (_inPacket.header->packet_type) { + case AC_PTYPE_PING: { // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает + + if (_inPacket.header->body_length != 0 ) { // у входящего ping-пакета тело должно отсутствовать + // если тело есть, то жалуемся в лог + _debugMsg(F("Parser: ping packet should not to have body. Received one has body length %02X."), ESPHOME_LOG_LEVEL_WARN, __LINE__, _inPacket.header->body_length); + // очищаем пакет + _clearInPacket(); + _setStateMachineState(ACSM_IDLE); + break; + } + + _debugMsg(F("Parser: ping packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + // поднимаем флаг, что есть коннект с кондиционером + _has_connection = true; + + // надо отправлять ответ на пинг + _clearOutPacket(); + _outPacket.msec = millis(); + _outPacket.header->packet_type = AC_PTYPE_PING; + _outPacket.header->ping_answer_01 = 0x01; // только в ответе на пинг этот байт равен 0x01; что означает не ясно + _outPacket.header->body_length = 8; // в ответе на пинг у нас тело 8 байт + _outPacket.body = &(_outPacket.data[AC_HEADER_SIZE]); + + // заполняем тело пакета + packet_ping_answer_body_t * ping_body; + ping_body = (packet_ping_answer_body_t *) (_outPacket.body); + ping_body->byte_1C = 0x1C; + ping_body->byte_27 = 0x27; + + // расчет контрольной суммы и прописывание её в пакет + _outPacket.crc = (packet_crc_t *)&(_outPacket.data[AC_HEADER_SIZE + _outPacket.header->body_length]); + _setCRC16(&_outPacket); + _outPacket.bytesLoaded = AC_HEADER_SIZE + _outPacket.header->body_length + 2; + + _debugMsg(F("Parser: generated ping answer. Waiting for sending."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + // до отправки пинг-ответа проверяем, не выполнялась ли стартовая последовательность команд + // по задумке она выполняется после подключения к кондиционеру после ответа на первый пинг + // нужна для максимально быстрого определния текущих параметров кондиционера + if (!_startupSequenceComlete){ + _startupSequenceComlete = startupSequence(); + } + + // изначально предполагал, что передачу пакета на отправку выполнит обработчик IDLE, но показалось, что слишком долго + // логика отправки через IDLE в том, что получение запросов может быть важнее отправки ответов и IDLE позволяет реализовать такой приоритет + // но потом решил всё же напрямую отправлять в отправку + // в этом случае пинг-ответ заканчивает отправку спустя 144 мсек после стартового байта пинг-запроса + //_setStateMachineState(ACSM_IDLE); + _setStateMachineState(ACSM_SENDING_PACKET); + // решил провести эксперимент + // получилось от начала запроса до отправки ответа порядка 165 мсек., если отправка идет не сразу, а через состояние IDLE + // Если сразу отсюда отправляться в обработчик отправки, то время сокращается до 131 мсек. Основные потери идут до входа в парсер пакетов + break; + } + + case AC_PTYPE_CMD: { // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита + // сплит такие команды отправлять не должен, поэтому жалуемся в лог + _debugMsg(F("Parser: packet type=0x06 received from HVAC. This isn't expected."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + // очищаем пакет + _clearInPacket(); + _setStateMachineState(ACSM_IDLE); + break; + } + + case AC_PTYPE_INFO: { // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля + // смотрим тип поступившего пакета по второму байту тела + _debugMsg(F("Parser: status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + switch (_inPacket.body[1]) { + case AC_CMD_STATUS_SMALL: { // маленький пакет статуса кондиционера + _debugMsg(F("Parser: status packet type = small"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + stateChangedFlag = false; + + // будем обращаться к телу пакета через указатель на структуру + packet_small_info_body_t * small_info_body; + small_info_body = (packet_small_info_body_t *) (_inPacket.body); + + // в малом пакете передается большое количество установленных пользователем параметров работы + stateFloat = 8 + (small_info_body->target_temp_int_and_v_louver >> 3) + 0.5*(float)(small_info_body->target_temp_frac >> 7); + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_target != stateFloat); + _current_ac_state.temp_target = stateFloat; + _current_ac_state.temp_target_matter = true; + + stateByte = small_info_body->target_temp_int_and_v_louver & AC_LOUVERV_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.louver.louver_v != (ac_louver_V)stateByte); + _current_ac_state.louver.louver_v = (ac_louver_V)stateByte; + + stateByte = small_info_body->h_louver & AC_LOUVERH_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.louver.louver_h != (ac_louver_H)stateByte); + _current_ac_state.louver.louver_h = (ac_louver_H)stateByte; + + stateByte = small_info_body->fan_speed & AC_FANSPEED_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.fanSpeed != (ac_fanspeed)stateByte); + _current_ac_state.fanSpeed = (ac_fanspeed)stateByte; + + stateByte = small_info_body->fan_turbo_and_mute & AC_FANTURBO_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.fanTurbo != (ac_fanturbo)stateByte); + _current_ac_state.fanTurbo = (ac_fanturbo)stateByte; + + stateByte = small_info_body->fan_turbo_and_mute & AC_FANMUTE_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.fanMute != (ac_fanmute)stateByte); + _current_ac_state.fanMute = (ac_fanmute)stateByte; + + stateByte = small_info_body->mode & AC_MODE_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.mode != (ac_mode)stateByte); + _current_ac_state.mode = (ac_mode)stateByte; + + stateByte = small_info_body->mode & AC_SLEEP_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.sleep != (ac_sleep)stateByte); + _current_ac_state.sleep = (ac_sleep)stateByte; + + stateByte = small_info_body->mode & AC_IFEEL_MASK; + _current_ac_state.iFeel = (ac_ifeel)stateByte; + + stateByte = small_info_body->status & AC_POWER_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.power != (ac_power)stateByte); + _current_ac_state.power = (ac_power)stateByte; + + stateByte = small_info_body->status & AC_HEALTH_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.health != (ac_health)stateByte); + _current_ac_state.health = (ac_health)stateByte; + + stateByte = small_info_body->status & AC_HEALTH_STATUS_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.health_status != (ac_health_status)stateByte); + _current_ac_state.health_status = (ac_health_status)stateByte; + + stateByte = small_info_body->status & AC_CLEAN_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.clean != (ac_clean)stateByte); + _current_ac_state.clean = (ac_clean)stateByte; + + stateByte = small_info_body->display_and_mildew & AC_DISPLAY_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.display != (ac_display)stateByte); + _current_ac_state.display = (ac_display)stateByte; + + stateByte = small_info_body->display_and_mildew & AC_MILDEW_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.mildew != (ac_mildew)stateByte); + _current_ac_state.mildew = (ac_mildew)stateByte; + + // уведомляем об изменении статуса сплита + if (stateChangedFlag) stateChanged(); + + break; + } + + case AC_CMD_STATUS_BIG: // большой пакет статуса кондиционера + case AC_CMD_STATUS_PERIODIC: { // раз в 10 минут рассылается сплитом, структура аналогична большому пакету статуса + // TODO: вроде как AC_CMD_STATUS_PERIODIC могут быть и с другими кодами; пока что другие будут игнорироваться; если это будет критично, надо будет поправить + _debugMsg(F("Parser: status packet type = big or periodic"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + stateChangedFlag = false; + + // будем обращаться к телу пакета через указатель на структуру + packet_big_info_body_t * big_info_body; + big_info_body = (packet_big_info_body_t *) (_inPacket.body); + + // тип кондея (инвертор или старт стоп) + _is_invertor = big_info_body->is_invertor; + + // температура воздуха в помещении по версии сплит-системы + stateFloat = big_info_body->ambient_temperature_int - 0x20 + (float)(big_info_body->ambient_temperature_frac & 0x0f) / 10.0; + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_ambient != stateFloat); + _current_ac_state.temp_ambient = stateFloat; + + // некая температура из наружного блока, скорее всего температура испарителя + // temp = big_info_body->outdoor_temperature - 0x20; + // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах + { + const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; + const float antkoef = 1.0 - koef; + static float temp = _current_ac_state.temp_outdoor; + temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); + _current_ac_state.temp_outdoor = temp; + } + + // температура входящей магистрали + stateFloat = big_info_body->in_temperature_int - 0x20; + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_inbound != stateFloat); + _current_ac_state.temp_inbound = stateFloat; + + // температура исходящей магистрали + stateFloat = big_info_body->out_temperature_int - 0x20; + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outbound != stateFloat); + _current_ac_state.temp_outbound = stateFloat; + + // температура непонятная температура + stateFloat = big_info_body->strange_temperature_int - 0x20; + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_strange != stateFloat); + _current_ac_state.temp_strange = stateFloat; + + // реальная скорость проперлера + stateFloat = big_info_body->realFanSpeed; + stateChangedFlag = stateChangedFlag || (_current_ac_state.realFanSpeed != (ac_realFan)stateFloat); + _current_ac_state.realFanSpeed = (ac_realFan)stateFloat; + + // мощность инвертора + stateFloat = big_info_body->invertor_power; + stateChangedFlag = stateChangedFlag || (_current_ac_state.invertor_power != stateFloat); + _current_ac_state.invertor_power = stateFloat; + + // режим разморозки + bool temp = (big_info_body->needDefrost && big_info_body->defrostMode); + stateChangedFlag = stateChangedFlag || (_current_ac_state.defrost != temp); + _current_ac_state.defrost = temp; + + // уведомляем об изменении статуса сплита + if (stateChangedFlag) { + stateChanged(); + } + break; + } + + case AC_CMD_SET_PARAMS: { // такой статусный пакет присылается кондиционером в ответ на команду установки параметров + // в теле пакета нет ничего примечательного + // в байтах 2 и 3 тела похоже передается CRC пакета поступившей команды, на которую сплит отвечает + // но я решил этот момент тут не проверять и не контролировать. + // корректную установку параметров можно определить, запросив статус кондиционера сразу после получения этой команды кондея + // в настоящий момент проверка сделана в механизме sequences + // TODO: если доводить до идеала, то проверку байтов 2 и 3 можно сделать и тут + break; + } + + default: + _debugMsg(F("Parser: status packet type = unknown (%02X)"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _inPacket.body[1]); + break; + } + _setStateMachineState(ACSM_IDLE); + break; + } + + case AC_PTYPE_INIT: // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. + case AC_PTYPE_UNKN: // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует + default: + // игнорируем. Для нашего случая эти пакеты не важны + _setStateMachineState(ACSM_IDLE); + break; + } + + // если есть последовательность команд, то надо отработать проверку последовательности + if (hasSequence()) _doSequence(); + + // после разбора входящего пакета его надо очистить + _clearInPacket(); + } + + // состояние конечного автомата: ACSM_SENDING_PACKET + void _doSendingPacketState(){ + // если нет исходящего пакета, то выходим + if ((_outPacket.msec == 0) || (_outPacket.crc == nullptr) || (_outPacket.bytesLoaded == 0)) { + _debugMsg(F("Sender: no packet to send."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + _setStateMachineState(ACSM_IDLE); + return; + } + + _debugMsg(F("Sender: sending packet."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + _ac_serial->write_array(_outPacket.data, _outPacket.bytesLoaded); + _ac_serial->flush(); + + _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); + _debugMsg(F("Sender: %u bytes sent (%u ms)."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _outPacket.bytesLoaded, millis()-_outPacket.msec); + _clearOutPacket(); + + _setStateMachineState(ACSM_IDLE); + }; + + /** вывод отладочной информации в лог + * + * dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе. + * msg - сообщение, выводимое в лог + * line - строка, на которой произошел вызов (удобно при отладке) + */ + void _debugMsg(const String &msg, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0, ... ){ + if (dbgLevel < ESPHOME_LOG_LEVEL_NONE) dbgLevel = ESPHOME_LOG_LEVEL_NONE; + if (dbgLevel > ESPHOME_LOG_LEVEL_VERY_VERBOSE) dbgLevel = ESPHOME_LOG_LEVEL_VERY_VERBOSE; + + if (line == 0) line = __LINE__; // если строка не передана, берем текущую строку + + va_list vl; + va_start(vl, line); + esp_log_vprintf_(dbgLevel, Constants::TAG, line, msg.c_str(), vl); // так тоже вроде через Ж*, только ее не видно :) + va_end(vl); + } + + /** выводим данные пакета в лог для отладки + * + * dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе. + * packet - указатель на пакет для вывода; + * если указатель на crc равен nullptr или первый байт в буфере не AC_PACKET_START_BYTE, то считаем, что передан битый пакет + * или не пакет вовсе. Для такого выводим только массив байт. + * Для нормального пакета данные выводятся с форматированием. + * line - строка, на которой произошел вызов (удобно при отладке) + **/ + void _debugPrintPacket(packet_t * packet, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0){ + // определяем, полноценный ли пакет нам передан + bool notAPacket = false; + // указатель заголовка всегда установден на начало буфера + notAPacket = notAPacket || (packet->crc == nullptr); + notAPacket = notAPacket || (packet->data[0] != AC_PACKET_START_BYTE); + + String st = ""; + char textBuf[11]; + + // заполняем время получения пакета + memset(textBuf, 0, 11); + sprintf(textBuf, "%010u", packet->msec); + st = st + textBuf + ": "; + + // формируем преамбулы + if (packet == &_inPacket) { + st += "[<=] "; // преамбула входящего пакета + } else if (packet == &_outPacket) { + st += "[=>] "; // преамбула исходящего пакета + } else { + st += "[--] "; // преамбула для "непакета" + } + + // формируем данные + #ifdef HOLMS + dbgLevel = ESPHOME_LOG_LEVEL_ERROR; + if(packet->header->body_length > HOLMS){ + for (int i=0; ibytesLoaded; i++){ + sprintf(textBuf, "%03d;", packet->data[i]); + st += textBuf; + } + if (line == 0) line = __LINE__; + _debugMsg(st, dbgLevel, line); + } + #else + for (int i=0; ibytesLoaded; i++){ + // для нормальных пакетов надо заключить заголовок в [] + if ((!notAPacket) && (i == 0)) st += "["; + // для нормальных пакетов надо заключить CRC в [] + if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE)) st += "["; + + //memset(textBuf, 0, 11); + sprintf(textBuf, "%02X", packet->data[i]); + st += textBuf; + + // для нормальных пакетов надо заключить заголовок в [] + if ((!notAPacket) && (i == AC_HEADER_SIZE-1)) st += "]"; + // для нормальных пакетов надо заключить CRC в [] + if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE+2-1)) st += "]"; + st += " "; + } + if (line == 0) line = __LINE__; + _debugMsg(st, dbgLevel, line); + #endif + + } + + /** расчет CRC16 для блока данных data длиной len + * data - данные для расчета CRC16, указатель на массив байт + * len - длина блока данных для расчета, в байтах + * + * возвращаем uint16_t CRC16 + **/ + uint16_t _CRC16(uint8_t *data, uint8_t len){ + uint32_t crc = 0; + + // выделяем буфер для расчета CRC и копируем в него переданные данные + // это нужно для того, чтобы в случае нечетной длины данных можно было дополнить тело пакета + // одним нулевым байтом и не попортить загруженный пакет (ведь в загруженном сразу за телом идёт CRC) + uint8_t _crcBuffer[AC_BUFFER_SIZE]; + memset(_crcBuffer, 0, AC_BUFFER_SIZE); + memcpy(_crcBuffer, data, len); + + // если длина данных нечетная, то надо сделать четной, дополнив данные в конце нулевым байтом + // но так как выше буфер заполняли нулями, то отдельно тут присваивать 0x00 нет смысла + if ((len%2) == 1) len++; + + // рассчитываем CRC16 + uint32_t word = 0; + for (uint8_t i=0; i < len; i+=2){ + word = (_crcBuffer[i] << 8) + _crcBuffer[i+1]; + crc += word; + } + crc = (crc >> 16) + (crc & 0xFFFF); + crc = ~ crc; + + return crc & 0xFFFF; + } + + // расчитываем CRC16 и заполняем эти данные в структуре пакета + void _setCRC16(packet_t* pack = nullptr){ + // если пакет не указан, то устанавливаем CRC для исходящего пакета + if (pack == nullptr) pack = &_outPacket; + + packet_crc_t crc; + crc.crc16 = _CRC16(pack->data, AC_HEADER_SIZE + pack->header->body_length); + + // если забыли указатель на crc установить, то устанавливаем + if (pack->crc == nullptr) pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); + + pack->crc->crc[0] = crc.crc[1]; + pack->crc->crc[1] = crc.crc[0]; + return; + } + + // проверяет CRC пакета по указателю + bool _checkCRC(packet_t* pack = nullptr){ + // если пакет не указан, то проверяем входящий + if (pack == nullptr) pack = &_inPacket; + if (pack->bytesLoaded < AC_HEADER_SIZE) { + _debugMsg(F("CRC check: incoming packet size error."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + // если забыли указатель на crc установить, то устанавливаем + if (pack->crc == nullptr) pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); + + packet_crc_t crc; + crc.crc16 = _CRC16(pack->data, AC_HEADER_SIZE + pack->header->body_length); + + return ((pack->crc->crc[0] == crc.crc[1]) && (pack->crc->crc[1] == crc.crc[0])); + } + + // заполняет пакет по ссылке командой запроса маленького пакета статуса + void _fillStatusSmall(packet_t * pack = nullptr){ + // по умолчанию заполняем исходящий пакет + if (pack == nullptr) pack = &_outPacket; + + // присваиваем параметры пакета + pack->msec = millis(); + pack->header->start_byte = AC_PACKET_START_BYTE; + pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа + pack->header->packet_type = AC_PTYPE_CMD; + pack->header->body_length = 2; // тело команды 2 байта + pack->body = &(pack->data[AC_HEADER_SIZE]); + pack->body[0] = AC_CMD_STATUS_SMALL; + pack->body[1] = 0x01; // он всегда 0x01 + pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; + + // рассчитываем и записываем в пакет CRC + pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); + _setCRC16(pack); + } + + // заполняет пакет по ссылке командой запроса большого пакета статуса + void _fillStatusBig(packet_t * pack = nullptr){ + // по умолчанию заполняем исходящий пакет + if (pack == nullptr) pack = &_outPacket; + + // присваиваем параметры пакета + pack->msec = millis(); + pack->header->start_byte = AC_PACKET_START_BYTE; + pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа + pack->header->packet_type = AC_PTYPE_CMD; + pack->header->body_length = 2; // тело команды 2 байта + pack->body = &(pack->data[AC_HEADER_SIZE]); + pack->body[0] = AC_CMD_STATUS_BIG; + pack->body[1] = 0x01; // он всегда 0x01 + pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; + + // рассчитываем и записываем в пакет CRC + pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); + _setCRC16(pack); + } + + /** заполняет пакет по ссылке командой установки параметров + * + * указатель на пакет может отсутствовать, тогда заполняется _outPacket + * указатель на команду также может отсутствовать, тогда используется текущее состояние из _current_ac_state + * все *__UNTOUCHED параметры заполняются из _current_ac_state + **/ + void _fillSetCommand(bool clrPacket = false, packet_t * pack = nullptr, ac_state_t *cmd = nullptr){ + // по умолчанию заполняем исходящий пакет + if (pack == nullptr) pack = &_outPacket; + + // очищаем пакет, если это указано + if (clrPacket) _clearPacket(pack); + + // заполняем его параметрами из _current_ac_state + if (cmd != &_current_ac_state) { + _fillSetCommand(false, pack, &_current_ac_state); + } + + // если команда не указана, значит выходим + if (cmd == nullptr) return; + + // команда указана, дополнительно внесем в пакет те параметры, которые установлены в команде + + // присваиваем параметры пакета + pack->msec = millis(); + pack->header->start_byte = AC_PACKET_START_BYTE; + pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа + pack->header->packet_type = AC_PTYPE_CMD; + pack->header->body_length = 15; // тело команды 15 байт, как у Small status + pack->body = &(pack->data[AC_HEADER_SIZE]); + pack->body[0] = AC_CMD_SET_PARAMS; // устанавливаем параметры + pack->body[1] = 0x01; // он всегда 0x01 + pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; + + // целевая температура кондиционера + if (cmd->temp_target_matter){ + // устраняем выход за границы диапазона (это ограничение самого кондиционера) + if (cmd->temp_target < Constants::AC_MIN_TEMPERATURE) cmd->temp_target = Constants::AC_MIN_TEMPERATURE; + if (cmd->temp_target > Constants::AC_MAX_TEMPERATURE) cmd->temp_target = Constants::AC_MAX_TEMPERATURE; + + // целая часть температуры + pack->body[2] = (pack->body[2] & ~AC_TEMP_TARGET_INT_PART_MASK) | (((uint8_t)(cmd->temp_target) - 8) << 3); + + // дробная часть температуры + if (cmd->temp_target - (uint8_t)(cmd->temp_target) > 0) { + pack->body[4] = (pack->body[4] & ~AC_TEMP_TARGET_FRAC_PART_MASK) | 1; + } else { + pack->body[4] = (pack->body[4] & ~AC_TEMP_TARGET_FRAC_PART_MASK) | 0; + } + } + + // обнулить счетчик минут с последней команды + pack->body[4] &= ~ AC_MIN_COUTER ; + + // вертикальные жалюзи + if (cmd->louver.louver_v != AC_LOUVERV_UNTOUCHED){ + pack->body[2] = (pack->body[2] & ~AC_LOUVERV_MASK) | cmd->louver.louver_v; + } + + // горизонтальные жалюзи + if (cmd->louver.louver_h != AC_LOUVERH_UNTOUCHED){ + pack->body[3] = (pack->body[3] & ~AC_LOUVERH_MASK) | cmd->louver.louver_h; + } + + // скорость вентилятора + if (cmd->fanSpeed != AC_FANSPEED_UNTOUCHED){ + pack->body[5] = (pack->body[5] & ~AC_FANSPEED_MASK) | cmd->fanSpeed; + } + + // спец.режимы вентилятора: TURBO + if (cmd->fanTurbo != AC_FANTURBO_UNTOUCHED){ + pack->body[6] = (pack->body[6] & ~AC_FANTURBO_MASK) | cmd->fanTurbo; + } + + // спец.режимы вентилятора: MUTE + if (cmd->fanMute != AC_FANMUTE_UNTOUCHED){ + pack->body[6] = (pack->body[6] & ~AC_FANMUTE_MASK) | cmd->fanMute; + } + + // режим кондея + if (cmd->mode != AC_MODE_UNTOUCHED){ + pack->body[7] = (pack->body[7] & ~AC_MODE_MASK) | cmd->mode; + } + if (cmd->sleep != AC_SLEEP_UNTOUCHED){ + pack->body[7] = (pack->body[7] & ~AC_SLEEP_MASK) | cmd->sleep; + } + if (cmd->iFeel != AC_IFEEL_UNTOUCHED){ + pack->body[7] = (pack->body[7] & ~AC_IFEEL_MASK) | cmd->iFeel; + } + + // питание вкл/выкл + if (cmd->power != AC_POWER_UNTOUCHED){ + pack->body[10] = (pack->body[10] & ~AC_POWER_MASK) | cmd->power; + } + if (cmd->clean != AC_CLEAN_UNTOUCHED){ + pack->body[10] = (pack->body[10] & ~AC_CLEAN_MASK) | cmd->clean; + } + // ионизатор + if (cmd->health != AC_HEALTH_UNTOUCHED){ + pack->body[10] = (pack->body[10] & ~AC_HEALTH_MASK) | cmd->health; + } + + // какой то флаг ионизатора + if (cmd->health_status != AC_HEALTH_STATUS_UNTOUCHED){ + pack->body[10] = (pack->body[10] & ~AC_HEALTH_STATUS_MASK) | cmd->health_status; + } + + // дисплей + if (cmd->display != AC_DISPLAY_UNTOUCHED){ + pack->body[12] = (pack->body[12] & ~AC_DISPLAY_MASK) | cmd->display; + } + + // антиплесень + if (cmd->mildew != AC_MILDEW_UNTOUCHED){ + pack->body[12] = (pack->body[12] & ~AC_MILDEW_MASK) | cmd->mildew; + } + + + + // рассчитываем и записываем в пакет CRC + pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); + _setCRC16(pack); + } + + // отправка запроса на маленький статусный пакет + bool sq_requestSmallStatus(){ + // если исходящий пакет не пуст, то выходим и ждем освобождения + if (_outPacket.bytesLoaded > 0) return true; + + _fillStatusSmall(&_outPacket); + _fillStatusSmall(&_sequence[_sequence_current_step].packet); + _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; + + // Отчитываемся в лог + _debugMsg(F("Sequence [step %u]: small status request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + // увеличиваем текущий шаг + _sequence_current_step++; + return true; + } + + // проверка ответа на запрос маленького статусного пакета + bool sq_controlSmallStatus(){ + // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим + if (_inPacket.bytesLoaded == 0) return true; + + // Пинги игнорируем + if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; + + // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать + _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); + _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; + + // проверяем ответ + bool relevant = true; + relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); + relevant = (relevant && (_inPacket.header->body_length == 0x0F)); + relevant = (relevant && (_inPacket.body[0] == 0x01)); + relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_SMALL)); + + // если пакет подходит, значит можно переходить к следующему шагу + if (relevant) { + _debugMsg(F("Sequence [step %u]: correct small status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + _sequence_current_step++; + } else { + // если пакет не подходящий, то отчитываемся в лог... + _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); + _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_SMALL); + // ...и прерываем последовательность, так как вернем false + } + return relevant; + } + + // отправка запроса на большой статусный пакет + bool sq_requestBigStatus(){ + // если исходящий пакет не пуст, то выходим и ждем освобождения + if (_outPacket.bytesLoaded > 0) return true; + + _fillStatusBig(&_outPacket); + _fillStatusBig(&_sequence[_sequence_current_step].packet); + _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; + + // Отчитываемся в лог + _debugMsg(F("Sequence [step %u]: big status request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + // увеличиваем текущий шаг + _sequence_current_step++; + return true; + } + + // проверка ответа на запрос большого статусного пакета + bool sq_controlBigStatus(){ + // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим + if (_inPacket.bytesLoaded == 0) return true; + + // Пинги игнорируем + if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; + + // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать + _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); + _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; + + // проверяем ответ + bool relevant = true; + relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); + relevant = (relevant && (_inPacket.header->body_length == 0x18 || _inPacket.header->body_length == 0x19)); // канальник Royal Clima отвечает пакетом длиной 0x19 + relevant = (relevant && (_inPacket.body[0] == 0x01)); + relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_BIG)); + + // если пакет подходит, значит можно переходить к следующему шагу + if (relevant) { + _debugMsg(F("Sequence [step %u]: correct big status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + _sequence_current_step++; + } else { + // если пакет не подходящий, то отчитываемся в лог... + _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); + _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_BIG); + // ...и прерываем последовательность + } + return relevant; + } + + // отправка запроса на выполнение команды + bool sq_requestDoCommand(){ + // если исходящий пакет не пуст, то выходим и ждем освобождения + if (_outPacket.bytesLoaded > 0) return true; + + _fillSetCommand(true, &_outPacket, &_sequence[_sequence_current_step].cmd); + _fillSetCommand(true, &_sequence[_sequence_current_step].packet, &_sequence[_sequence_current_step].cmd); + _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; + + // Отчитываемся в лог + _debugMsg(F("Sequence [step %u]: doCommand request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + // увеличиваем текущий шаг + _sequence_current_step++; + return true; + } + + // проверка ответа на выполнение команды + bool sq_controlDoCommand(){ + // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим + if (_inPacket.bytesLoaded == 0) return true; + + // Пинги игнорируем + if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; + + // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать + _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); + _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; + + // проверяем ответ + bool relevant = true; + relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); + relevant = (relevant && (_inPacket.header->body_length == 0x04)); + relevant = (relevant && (_inPacket.body[0] == 0x01)); + relevant = (relevant && (_inPacket.body[1] == AC_CMD_SET_PARAMS)); + // байты 2 и 3 обычно равны CRC отправленного пакета с командой + relevant = (relevant && (_inPacket.body[2] == _sequence[_sequence_current_step-1].packet.crc->crc[0])); + relevant = (relevant && (_inPacket.body[3] == _sequence[_sequence_current_step-1].packet.crc->crc[1])); + + // если пакет подходит, значит можно переходить к следующему шагу + if (relevant) { + _debugMsg(F("Sequence [step %u]: correct doCommand packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + _sequence_current_step++; + } else { + // если пакет не подходящий, то отчитываемся в лог... + _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); + _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_BIG); + // ...и прерываем последовательность + } + return relevant; + } + + // отправка запроса с тестовым пакетом + bool sq_requestTestPacket(){ + // если исходящий пакет не пуст, то выходим и ждем освобождения + if (_outPacket.bytesLoaded > 0) return true; + + _copyPacket(&_outPacket, &_outTestPacket); + _copyPacket(&_sequence[_sequence_current_step].packet, &_outTestPacket); + _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; + + // Отчитываемся в лог + _debugMsg(F("Sequence [step %u]: Test Packet request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); + _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + // увеличиваем текущий шаг + _sequence_current_step++; + return true; + } + + // сенсоры, отображающие параметры сплита + esphome::sensor::Sensor *sensor_indoor_temperature_ = nullptr; + esphome::sensor::Sensor *sensor_outdoor_temperature_ = nullptr; + esphome::sensor::Sensor *sensor_inbound_temperature_ =nullptr; + esphome::sensor::Sensor *sensor_outbound_temperature_ =nullptr; + esphome::sensor::Sensor *sensor_strange_temperature_ =nullptr; + // текущая мощность компрессора + esphome::sensor::Sensor *sensor_invertor_power_ = nullptr; + // бинарный сенсор, отображающий состояние дисплея + esphome::binary_sensor::BinarySensor *sensor_display_ = nullptr; + // бинарный сенсор состония разморозки + esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; + + // загружает на выполнение последовательность команд на включение/выключение табло с температурой + bool _displaySequence(ac_display dsp = AC_DISPLAY_ON){ + // нет смысла в последовательности, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("displaySequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + if (dsp == AC_DISPLAY_UNTOUCHED) return false; // выходим, чтобы не тратить время + + // формируем команду + ac_command_t cmd; + _clearCommand(&cmd); // не забываем очищать, а то будет мусор + cmd.display = dsp; + // добавляем команду в последовательность + if (!commandSequence(&cmd)) return false; + + _debugMsg(F("displaySequence: loaded (display = %02X)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, dsp); + return true; + } + + // номер глобального пресета от режима работы + uint8_t get_num_preset(ac_command_t* cmd){ + if(cmd->power == AC_POWER_OFF){ + return POS_MODE_OFF; + } else if(cmd->mode == AC_MODE_AUTO){ + return POS_MODE_AUTO; + } else if(cmd->mode == AC_MODE_COOL){ + return POS_MODE_COOL; + } else if(cmd->mode == AC_MODE_DRY){ + return POS_MODE_DRY; + } else if(cmd->mode == AC_MODE_FAN){ + return POS_MODE_FAN; + } else if(cmd->mode == AC_MODE_HEAT){ + return POS_MODE_HEAT; + } + cmd->power = AC_POWER_OFF; + return POS_MODE_OFF; + } + + // восстановление данных из пресета + void load_preset(ac_command_t* cmd, uint8_t num_preset){ + if(num_preset < sizeof(global_presets)/sizeof(global_presets[0])){ // проверка выхода за пределы массива + if(cmd->power == global_presets[num_preset].power && cmd->mode == global_presets[num_preset].mode){ //контроль инициализации + memcpy(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE); // просто копируем из массива + _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } else { + _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } + } + } + + // запись данных в массив персетов + void save_preset(ac_command_t* cmd){ + uint8_t num_preset = get_num_preset(cmd); + if(memcmp(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE) != 0){ // содержимое пресетов разное + memcpy(&(global_presets[num_preset]), cmd, AC_COMMAND_BASE_SIZE); // копируем пресет в массив + #if defined(ESP32) + _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + if(storage.save(global_presets)){ + if(!global_preferences->sync()) // сохраняем все пресеты + _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } else { + _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } + #endif + } else { + _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } + } + + public: + // инициализация объекта + void initAC(esphome::uart::UARTComponent *parent = nullptr){ + _dataMillis = millis(); + _clearInPacket(); + _clearOutPacket(); + + _clearPacket(&_outTestPacket); + _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; + _outTestPacket.header->wifi = AC_PACKET_ANSWER; + + _setStateMachineState(ACSM_IDLE); + _ac_serial = parent; + _hw_initialized = (_ac_serial != nullptr); + _has_connection = false; + + // заполняем структуру состояния начальными значениями + _clearCommand((ac_command_t *)&_current_ac_state); + + // очищаем последовательность пакетов + _clearSequence(); + + // выполнена ли уже стартовая последовательность команд (сбор информации о статусе кондея) + _startupSequenceComlete = false; + }; + + float get_setup_priority() const override { return esphome::setup_priority::DATA; } + + void set_indoor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_indoor_temperature_ = temperature_sensor; } + void set_outdoor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_outdoor_temperature_ = temperature_sensor; } + void set_inbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_inbound_temperature_ = temperature_sensor; } + void set_outbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_outbound_temperature_ = temperature_sensor; } + void set_strange_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_strange_temperature_ = temperature_sensor; } + void set_defrost_state(binary_sensor::BinarySensor *defrost_state) { sensor_defrost_ = defrost_state; } + void set_display_sensor(binary_sensor::BinarySensor *display_sensor) { sensor_display_ = display_sensor; } + void set_invertor_power_sensor(sensor::Sensor *invertor_power_sensor) { sensor_invertor_power_ = invertor_power_sensor; } + bool get_hw_initialized(){ return _hw_initialized; }; + bool get_has_connection(){ return _has_connection; }; + + // возвращает, есть ли елементы в последовательности команд + bool hasSequence(){ + return (_sequence[0].item_type != AC_SIT_NONE); + } + + // вызывается для обновления отображения состояния кондиционера, ДЛЯ ПУБЛИКАЦИИ + void stateChanged(){ + _debugMsg(F("State changed, let's publish it."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + if(_is_invertor){ // анализ режима для инвертора, точнее потому что использует показания мощности инвертора + static uint32_t timerInv = 0; + if(_current_ac_state.invertor_power == 0){ // инвертор выключен + timerInv = millis(); + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && + _current_ac_state.power == AC_POWER_OFF ){ // внутренний кулер остановлен, кондей выключен + this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает + } else { + int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; + if (delta_temp > 0 && delta_temp < 2 && + (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else if (_current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || + _current_ac_state.realFanSpeed == AC_REAL_FAN_OFF ){ // кулер чуть вертится + this->action = climate::CLIMATE_ACTION_IDLE; // кондей в простое + } else { + this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + } + } + } else if(millis()-timerInv > 2000){ // инвертор включен, но нужно дождаться реакции на его включение + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE ){ //медленное вращение + if(_current_ac_state.temp_ambient - _current_ac_state.temp_inbound > 0){ //холодный радиатор + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else { // теплый радиатор, видимо переходный режим + this->action = climate::CLIMATE_ACTION_IDLE; + } + } else { + int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; + if(delta_temp < -2){ // входящая температура выше комнатной, быстрый фен - ОБОГРЕВ + this->action = climate::CLIMATE_ACTION_HEATING; + } else if(delta_temp > 2){ // ниже, быстрый фен - ОХЛАЖДЕНИЕ + this->action = climate::CLIMATE_ACTION_COOLING; + } else { // просто вентиляция + this->action = climate::CLIMATE_ACTION_IDLE; + } + } + } else { + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE){ + this->action = climate::CLIMATE_ACTION_IDLE; + } else { + this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + } + } + } else { + if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && + _current_ac_state.power == AC_POWER_OFF){ + this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает + } else { + int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; // разность температуры между комнатной и входящей + if (delta_temp > 0 && delta_temp < 2 && + (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || + _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ + this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ + } else if(_current_ac_state.realFanSpeed != AC_REAL_FAN_OFF && + _current_ac_state.realFanSpeed != AC_REAL_FAN_MUTE){ + if(delta_temp > 2){ + this->action = climate::CLIMATE_ACTION_COOLING; + } else if(delta_temp < -2){ + this->action = climate::CLIMATE_ACTION_HEATING; + } else { + this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция + } + } else { + this->action = climate::CLIMATE_ACTION_IDLE; + } + } + } + _debugMsg(F("Action mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->action); + /*************************** POWER & MODE ***************************/ + if (_current_ac_state.power == AC_POWER_ON){ + switch (_current_ac_state.mode) { + case AC_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; // по факту режим, названный в AUX как AUTO, является режимом HEAT_COOL + break; + + case AC_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + + case AC_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + + case AC_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + + case AC_MODE_FAN: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + + default: + _debugMsg(F("Warning: unknown air conditioner mode."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + break; + } + } else { + this->mode = climate::CLIMATE_MODE_OFF; + } + + _debugMsg(F("Climate mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->mode); + + /*************************** FAN SPEED ***************************/ + if(_current_ac_state.power == AC_POWER_ON){ + this->fan_mode = climate::CLIMATE_FAN_OFF; + switch (_current_ac_state.fanSpeed) { + case AC_FANSPEED_HIGH: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + this->custom_fan_mode = (std::string)""; + break; + + case AC_FANSPEED_MEDIUM: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + this->custom_fan_mode = (std::string)""; + break; + + case AC_FANSPEED_LOW: + this->fan_mode = climate::CLIMATE_FAN_LOW; + break; + + case AC_FANSPEED_AUTO: + this->fan_mode = climate::CLIMATE_FAN_AUTO; + break; + + default: + break; + } + /*************************** TURBO FAN MODE ***************************/ + switch (_current_ac_state.fanTurbo) { + case AC_FANTURBO_ON: + this->custom_fan_mode = Constants::TURBO; + break; + case AC_FANTURBO_OFF: + default: + if (this->custom_fan_mode == Constants::TURBO) this->custom_fan_mode = (std::string)""; + break; + } + /*************************** MUTE FAN MODE ***************************/ + switch (_current_ac_state.fanMute) { + case AC_FANMUTE_ON: + this->custom_fan_mode = Constants::MUTE; + break; + case AC_FANMUTE_OFF: + default: + if (this->custom_fan_mode == Constants::MUTE) this->custom_fan_mode = (std::string)""; + break; + } + } else { // при выключеном питании публикуем фальшивый статус + //this->fan_mode = climate::CLIMATE_FAN_LOW ; + this->custom_fan_mode = Constants::MUTE; + } + + _debugMsg(F("Climate fan mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->fan_mode); + _debugMsg(F("Climate fan TURBO mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanTurbo); + _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); + + //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ + + /*************************** SLEEP PRESET ***************************/ + // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. + // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. + // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. + // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. + if(_current_ac_state.sleep == AC_SLEEP_ON && + _current_ac_state.power == AC_POWER_ON ) { + this->preset = climate::CLIMATE_PRESET_SLEEP; + } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { + this->preset = climate::CLIMATE_PRESET_NONE; + } + + _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); + + /*************************** HEALTH CUSTOM PRESET ***************************/ + // режим работы ионизатора + if(_current_ac_state.health == AC_HEALTH_ON && + _current_ac_state.power == AC_POWER_ON ) { + this->custom_preset = Constants::HEALTH; + } else if (this->custom_preset == Constants::HEALTH) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); + + /*************************** CLEAN CUSTOM PRESET ***************************/ + // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF + if(_current_ac_state.clean == AC_CLEAN_ON && + _current_ac_state.power == AC_POWER_OFF ) { + this->custom_preset = Constants::CLEAN; + } else if (this->custom_preset == Constants::CLEAN) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); + + /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ + // пресет просушки кондиционера после выключения + // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. + // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. + // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. + // + // Brokly: + // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет + // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии + // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки + // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало + // и затыкается. + if(_current_ac_state.mildew == AC_MILDEW_ON && + _current_ac_state.power == AC_POWER_OFF ) { + this->custom_preset = Constants::ANTIFUNGUS; + } else if (this->custom_preset == Constants::ANTIFUNGUS) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); + + /*************************** LOUVERs ***************************/ + + if( _current_ac_state.power == AC_POWER_OFF){ //ЕСЛИ КОНДЕЙ ВЫКЛЮЧЕН + this->swing_mode = climate::CLIMATE_SWING_OFF; + } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN && + _current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ + this->swing_mode = climate::CLIMATE_SWING_BOTH; + } else if (_current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN){ + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } + + _debugMsg(F("Climate swing mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->swing_mode); + + /*************************** TEMPERATURE ***************************/ + + if(_current_ac_state.mode == AC_MODE_FAN || _current_ac_state.power == AC_POWER_OFF){ + this->target_temperature = _current_ac_state.temp_ambient; + } else if (_current_ac_state.mode == AC_MODE_AUTO ){ + this->target_temperature = 25; + } else { + this->target_temperature = _current_ac_state.temp_target; + } + _debugMsg(F("Target temperature: %f"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->target_temperature); + this->current_temperature = _current_ac_state.temp_ambient; + _debugMsg(F("Room temperature: %f"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->current_temperature); + + /*********************************************************************/ + /*************************** PUBLISH STATE ***************************/ + /*********************************************************************/ + this->publish_state(); + // температура в комнате + if (sensor_indoor_temperature_ != nullptr) + sensor_indoor_temperature_->publish_state(_current_ac_state.temp_ambient); + // температура уличного блока + if (sensor_outdoor_temperature_ != nullptr) + sensor_outdoor_temperature_->publish_state(_current_ac_state.temp_outdoor); + // температура подводящей магистрали + if (sensor_inbound_temperature_ != nullptr) + sensor_inbound_temperature_->publish_state(_current_ac_state.temp_inbound); + // температура отводящей магистрали + if (sensor_outbound_temperature_ != nullptr) + sensor_outbound_temperature_->publish_state(_current_ac_state.temp_outbound); + // температура странного датчика + if (sensor_strange_temperature_ != nullptr) + sensor_strange_temperature_->publish_state(_current_ac_state.temp_strange); + // мощность инвертора + if (sensor_invertor_power_ != nullptr) + sensor_invertor_power_->publish_state(_current_ac_state.invertor_power); + // флаг режима разморозки + if (sensor_defrost_ != nullptr) + sensor_defrost_->publish_state(_current_ac_state.defrost); + // состояние дисплея + if (sensor_display_ != nullptr) { + switch (_current_ac_state.display) { + case AC_DISPLAY_ON: + if (this->get_display_inverted()) { + sensor_display_->publish_state(false); + } else { + sensor_display_->publish_state(true); + } + break; + + case AC_DISPLAY_OFF: + if (this->get_display_inverted()) { + sensor_display_->publish_state(true); + } else { + sensor_display_->publish_state(false); + } + break; + + default: + // могут быть и другие состояния, поэтому так + break; + } + } + } + + // вывод в дебаг текущей конфигурации компонента + void dump_config() { + ESP_LOGCONFIG(Constants::TAG, "AUX HVAC:"); + ESP_LOGCONFIG(Constants::TAG, " [x] Firmware version: %s", Constants::AC_FIRMWARE_VERSION.c_str()); + ESP_LOGCONFIG(Constants::TAG, " [x] Period: %dms", this->get_period()); + ESP_LOGCONFIG(Constants::TAG, " [x] Show action: %s", TRUEFALSE(this->get_show_action())); + ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", TRUEFALSE(this->get_display_inverted())); + ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); + ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); + if ((this->sensor_indoor_temperature_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); + if (!(this->sensor_indoor_temperature_)->get_device_class().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_indoor_temperature_)->get_device_class().c_str()); + } + ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_indoor_temperature_)->get_state_class()).c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_indoor_temperature_)->get_unit_of_measurement().c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_indoor_temperature_)->get_accuracy_decimals()); + if (!(this->sensor_indoor_temperature_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_indoor_temperature_)->get_icon().c_str()); + } + if (!(this->sensor_indoor_temperature_)->unique_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_indoor_temperature_)->unique_id().c_str()); + } + if ((this->sensor_indoor_temperature_)->get_force_update()) { + ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); + } + } + + if ((this->sensor_outdoor_temperature_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Outdoor Temperature"), (this->sensor_outdoor_temperature_)->get_name().c_str()); + if (!(this->sensor_outdoor_temperature_)->get_device_class().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_outdoor_temperature_)->get_device_class().c_str()); + } + ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_outdoor_temperature_)->get_state_class()).c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_outdoor_temperature_)->get_unit_of_measurement().c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_outdoor_temperature_)->get_accuracy_decimals()); + if (!(this->sensor_outdoor_temperature_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_outdoor_temperature_)->get_icon().c_str()); + } + if (!(this->sensor_outdoor_temperature_)->unique_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_outdoor_temperature_)->unique_id().c_str()); + } + if ((this->sensor_outdoor_temperature_)->get_force_update()) { + ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); + } + } + + if ((this->sensor_inbound_temperature_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Inbound Temperature"), (this->sensor_inbound_temperature_)->get_name().c_str()); + if (!(this->sensor_inbound_temperature_)->get_device_class().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_inbound_temperature_)->get_device_class().c_str()); + } + ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_inbound_temperature_)->get_state_class()).c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_inbound_temperature_)->get_unit_of_measurement().c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_inbound_temperature_)->get_accuracy_decimals()); + if (!(this->sensor_inbound_temperature_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_inbound_temperature_)->get_icon().c_str()); + } + if (!(this->sensor_inbound_temperature_)->unique_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_inbound_temperature_)->unique_id().c_str()); + } + if ((this->sensor_inbound_temperature_)->get_force_update()) { + ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); + } + } + + if ((this->sensor_outbound_temperature_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Outbound Temperature"), (this->sensor_outbound_temperature_)->get_name().c_str()); + if (!(this->sensor_outbound_temperature_)->get_device_class().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_outbound_temperature_)->get_device_class().c_str()); + } + ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_outbound_temperature_)->get_state_class()).c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_outbound_temperature_)->get_unit_of_measurement().c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_outbound_temperature_)->get_accuracy_decimals()); + if (!(this->sensor_outbound_temperature_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_outbound_temperature_)->get_icon().c_str()); + } + if (!(this->sensor_outbound_temperature_)->unique_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_outbound_temperature_)->unique_id().c_str()); + } + if ((this->sensor_outbound_temperature_)->get_force_update()) { + ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); + } + } + + if ((this->sensor_strange_temperature_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Strange Temperature"), (this->sensor_strange_temperature_)->get_name().c_str()); + if (!(this->sensor_strange_temperature_)->get_device_class().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_strange_temperature_)->get_device_class().c_str()); + } + ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_strange_temperature_)->get_state_class()).c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_strange_temperature_)->get_unit_of_measurement().c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_strange_temperature_)->get_accuracy_decimals()); + if (!(this->sensor_strange_temperature_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_strange_temperature_)->get_icon().c_str()); + } + if (!(this->sensor_strange_temperature_)->unique_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_strange_temperature_)->unique_id().c_str()); + } + if ((this->sensor_strange_temperature_)->get_force_update()) { + ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); + } + } + + if ((this->sensor_invertor_power_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Inverter Power"), (this->sensor_invertor_power_)->get_name().c_str()); + if (!(this->sensor_invertor_power_)->get_device_class().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_invertor_power_)->get_device_class().c_str()); + } + ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_invertor_power_)->get_state_class()).c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_invertor_power_)->get_unit_of_measurement().c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_invertor_power_)->get_accuracy_decimals()); + if (!(this->sensor_invertor_power_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_invertor_power_)->get_icon().c_str()); + } + if (!(this->sensor_invertor_power_)->unique_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_invertor_power_)->unique_id().c_str()); + } + if ((this->sensor_invertor_power_)->get_force_update()) { + ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); + } + } + + if ((this->sensor_defrost_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Defrost status"), (this->sensor_defrost_)->get_name().c_str()); + if (!(this->sensor_defrost_)->get_device_class().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_defrost_)->get_device_class().c_str()); + } + if (!(this->sensor_defrost_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_defrost_)->get_icon().c_str()); + } + if (!(this->sensor_defrost_)->get_object_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Object ID: '%s'", " ", (this->sensor_defrost_)->get_object_id().c_str()); + } + } + + if ((this->sensor_display_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Display"), (this->sensor_display_)->get_name().c_str()); + if (!(this->sensor_display_)->get_device_class().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_display_)->get_device_class().c_str()); + } + if (!(this->sensor_display_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_display_)->get_icon().c_str()); + } + if (!(this->sensor_display_)->get_object_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Object ID: '%s'", " ", (this->sensor_display_)->get_object_id().c_str()); + } + } + this->dump_traits_(Constants::TAG); + } + + // вызывается пользователем из интерфейса ESPHome или Home Assistant + void control(const esphome::climate::ClimateCall &call) override { + bool hasCommand = false; + ac_command_t cmd; + + _clearCommand(&cmd); // не забываем очищать, а то будет мусор + + // User requested mode change + if (call.get_mode().has_value()) { + ClimateMode mode = *call.get_mode(); + // Send mode to hardware + switch (mode) { + case climate::CLIMATE_MODE_OFF: + hasCommand = true; + cmd.power = AC_POWER_OFF; + load_preset(&cmd, POS_MODE_OFF); + cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать + this->mode = mode; + break; + + case climate::CLIMATE_MODE_COOL: + hasCommand = true; + cmd.power = AC_POWER_ON; + cmd.mode = AC_MODE_COOL; + load_preset(&cmd, POS_MODE_COOL); + this->mode = mode; + break; + + case climate::CLIMATE_MODE_HEAT: + hasCommand = true; + cmd.power = AC_POWER_ON; + cmd.mode = AC_MODE_HEAT; + load_preset(&cmd, POS_MODE_HEAT); + this->mode = mode; + break; + + case climate::CLIMATE_MODE_HEAT_COOL: + hasCommand = true; + cmd.power = AC_POWER_ON; + cmd.mode = AC_MODE_AUTO; + load_preset(&cmd, POS_MODE_AUTO); + cmd.temp_target = 25; // зависимость от режима HEAT_COOL + cmd.temp_target_matter = true; + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL + this->mode = mode; + break; + + case climate::CLIMATE_MODE_FAN_ONLY: + hasCommand = true; + cmd.power = AC_POWER_ON; + cmd.mode = AC_MODE_FAN; + load_preset(&cmd, POS_MODE_FAN); + cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN + cmd.temp_target_matter = true; + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN + cmd.sleep = AC_SLEEP_OFF; + if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ + cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN + } + this->mode = mode; + break; + + case climate::CLIMATE_MODE_DRY: + hasCommand = true; + cmd.power = AC_POWER_ON; + cmd.mode = AC_MODE_DRY; + load_preset(&cmd, POS_MODE_DRY); + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY + cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY + this->mode = mode; + break; + + case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) + default: + break; + } + } + + // User requested fan_mode change + if(_current_ac_state.power == AC_POWER_ON || cmd.power == AC_POWER_ON) { // пресеты для включенного кондея + // User requested swing_mode change + if (call.get_swing_mode().has_value()) { + ClimateSwingMode swingmode = *call.get_swing_mode(); + // Send fan mode to hardware + switch (swingmode) { + // The protocol allows other combinations for SWING. + // For example "turn the louvers to the desired position or "spread to the sides" / "concentrate in the center". + // But the ROVEX IR-remote does not provide this features. Therefore this features haven't been tested. + // May be suitable for other models of AUX-based ACs. + case climate::CLIMATE_SWING_OFF: + cmd.louver.louver_h = AC_LOUVERH_OFF; + cmd.louver.louver_v = AC_LOUVERV_OFF; + hasCommand = true; + this->swing_mode = swingmode; + break; + + case climate::CLIMATE_SWING_BOTH: + cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; + cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; + hasCommand = true; + this->swing_mode = swingmode; + break; + + case climate::CLIMATE_SWING_VERTICAL: + cmd.louver.louver_h = AC_LOUVERH_OFF; + cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; + hasCommand = true; + this->swing_mode = swingmode; + break; + + case climate::CLIMATE_SWING_HORIZONTAL: + cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; + cmd.louver.louver_v = AC_LOUVERV_OFF; + hasCommand = true; + this->swing_mode = swingmode; + break; + } + } + + if (call.get_target_temperature().has_value()) { + if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_FAN) || + (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_FAN)){ // блокировка ввода температуры в режиме FAN + this->target_temperature = _current_ac_state.temp_ambient; + this->publish_state(); + } else if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_AUTO) || + (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_AUTO)){ // блокировка ввода температуры в режиме АВТО + this->target_temperature = 25; + this->publish_state(); + } else { + // User requested target temperature change + float temp = *call.get_target_temperature(); + // Send target temp to climate + if (temp > Constants::AC_MAX_TEMPERATURE) temp = Constants::AC_MAX_TEMPERATURE; + if (temp < Constants::AC_MIN_TEMPERATURE) temp = Constants::AC_MIN_TEMPERATURE; + if(cmd.temp_target != temp){ + cmd.temp_target = temp; + hasCommand = true; + cmd.temp_target_matter = true; + } + } + } + + if (call.get_fan_mode().has_value()) { + ClimateFanMode fanmode = *call.get_fan_mode(); + // Send fan mode to hardware + switch (fanmode) { + case climate::CLIMATE_FAN_AUTO: + if(cmd.mode != AC_MODE_FAN){ // при вентиляции нет такого режима + hasCommand = true; + cmd.fanSpeed = AC_FANSPEED_AUTO; + // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners + cmd.fanTurbo = AC_FANTURBO_OFF; + cmd.fanMute = AC_FANMUTE_OFF; + this->fan_mode = fanmode; + } + break; + + case climate::CLIMATE_FAN_LOW: + hasCommand = true; + cmd.fanSpeed = AC_FANSPEED_LOW; + // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners + cmd.fanTurbo = AC_FANTURBO_OFF; + cmd.fanMute = AC_FANMUTE_OFF; + this->fan_mode = fanmode; + break; + + case climate::CLIMATE_FAN_MEDIUM: + hasCommand = true; + cmd.fanSpeed = AC_FANSPEED_MEDIUM; + // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners + cmd.fanTurbo = AC_FANTURBO_OFF; + cmd.fanMute = AC_FANMUTE_OFF; + this->fan_mode = fanmode; + break; + + case climate::CLIMATE_FAN_HIGH: + hasCommand = true; + cmd.fanSpeed = AC_FANSPEED_HIGH; + // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners + cmd.fanTurbo = AC_FANTURBO_OFF; + cmd.fanMute = AC_FANMUTE_OFF; + this->fan_mode = fanmode; + break; + + case climate::CLIMATE_FAN_ON: + case climate::CLIMATE_FAN_OFF: + case climate::CLIMATE_FAN_MIDDLE: + case climate::CLIMATE_FAN_FOCUS: + case climate::CLIMATE_FAN_DIFFUSE: + default: + break; + } + + } else if (call.get_custom_fan_mode().has_value()) { + std::string customfanmode = *call.get_custom_fan_mode(); + // Send fan mode to hardware + if (customfanmode == Constants::TURBO) { + // TURBO fan mode is suitable in COOL and HEAT modes for Rovex air conditioners. + // Other modes don't accept TURBO fan mode. + // May be other AUX-based air conditioners do the same. + if ( cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || + _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT) { + + hasCommand = true; + cmd.fanTurbo = AC_FANTURBO_ON; + cmd.fanMute = AC_FANMUTE_OFF; // зависимость от fanturbo + this->custom_fan_mode = customfanmode; + } else { + _debugMsg(F("TURBO fan mode is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + } + } else if (customfanmode == Constants::MUTE) { + // MUTE fan mode is suitable in FAN mode only for Rovex air conditioner. + // In COOL mode AC receives command without any changes. + // May be other AUX-based air conditioners do the same. + //if ( cmd.mode == AC_MODE_FAN || + // _current_ac_state.mode == AC_MODE_FAN) { + + hasCommand = true; + cmd.fanMute = AC_FANMUTE_ON; + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от fanmute + this->custom_fan_mode = customfanmode; + //} else { + // _debugMsg(F("MUTE fan mode is suitable in FAN mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + //} + } + } + + // Пользователь выбрал пресет + if (call.get_preset().has_value()) { + ClimatePreset preset = *call.get_preset(); + switch (preset) { + case climate::CLIMATE_PRESET_SLEEP: + // Ночной режим (SLEEP). Комбинируется только с режимами COOL, HEAT AUTO и DRY. Автоматически выключается через 7 часов. + // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. + // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. + // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. + if ( cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || cmd.mode == AC_MODE_DRY || cmd.mode == AC_MODE_AUTO || + _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT || + _current_ac_state.mode == AC_MODE_DRY || _current_ac_state.mode == AC_MODE_AUTO){ + + hasCommand = true; + cmd.sleep = AC_SLEEP_ON; + cmd.health = AC_HEALTH_OFF; // для логики пресетов + cmd.health_status = AC_HEALTH_STATUS_OFF; + this->preset = preset; + } else { + _debugMsg(F("SLEEP preset is suitable in COOL,HEAT and AUTO modes only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + } + break; + case climate::CLIMATE_PRESET_NONE: + // выбран пустой пресет, сбрасываем все настройки + hasCommand = true; + cmd.health = AC_HEALTH_OFF; // для логики пресетов + cmd.health_status = AC_HEALTH_STATUS_OFF; + cmd.sleep = AC_SLEEP_OFF; // для логики пресетов + this->preset = preset; + _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + break; + default: + // никакие другие встроенные пресеты не поддерживаются + break; + } + } else if (call.get_custom_preset().has_value()) { + std::string custom_preset = *call.get_custom_preset(); + if (custom_preset == Constants::HEALTH) { + hasCommand = true; + cmd.health = AC_HEALTH_ON; + cmd.health_status = AC_HEALTH_STATUS_ON; + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health + cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health + cmd.sleep = AC_SLEEP_OFF; // для логики пресетов + if(cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || cmd.mode == AC_MODE_AUTO || + _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT || + _current_ac_state.mode == AC_MODE_AUTO ){ + + cmd.fanSpeed = AC_FANSPEED_AUTO; // зависимость от health + } else if(cmd.mode == AC_MODE_FAN || _current_ac_state.mode == AC_MODE_FAN){ + cmd.fanSpeed = AC_FANSPEED_MEDIUM; // зависимость от health + } + this->custom_preset = custom_preset; + } else if (custom_preset == Constants::CLEAN) { + _debugMsg(F("CLEAN work only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + } else if (custom_preset == Constants::ANTIFUNGUS) { + _debugMsg(F("Anti-FUNGUS works only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + } + } + } else if(_current_ac_state.power == AC_POWER_OFF || cmd.power == AC_POWER_OFF){ // функции при выключеном питании + + if (call.get_target_temperature().has_value()) { // блокировка изменения температуры в выключеном состоянии + this->target_temperature = _current_ac_state.temp_ambient; + this->publish_state(); + } + + if (call.get_preset().has_value()) { // пользователь выбрал пустой пресет + ClimatePreset preset = *call.get_preset(); + switch (preset) { + case climate::CLIMATE_PRESET_SLEEP: + _debugMsg(F("SLEEP preset is suitable in POWER ON mode only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + break; + case climate::CLIMATE_PRESET_NONE: + // выбран пустой пресет, сбрасываем все настройки + hasCommand = true; + cmd.clean = AC_CLEAN_OFF; // для логики пресетов + cmd.mildew = AC_MILDEW_OFF; // для логики пресетов + this->preset = preset; + _debugMsg(F("Clear all 'Power OFF' presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + break; + default: + // никакие другие встроенные пресеты не поддерживаются + break; + } + } else { + std::string custom_preset = *call.get_custom_preset(); + if (call.get_custom_preset().has_value()) { + if (custom_preset == Constants::CLEAN) { + // режим очистки кондиционера при AC_POWER_OFF + hasCommand = true; + cmd.clean = AC_CLEAN_ON; + cmd.mildew = AC_MILDEW_OFF; // для логики пресетов + this->custom_preset = custom_preset; + } else if (custom_preset == Constants::ANTIFUNGUS) { + // Brokly: + // включение-выключение функции "Антиплесень". + // у меня пульт отправляет 5 посылок и на включение и на выключение, но реагирует на эту кнопку + // только в режиме POWER_OFF + + // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. + // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. + // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. + cmd.mildew = AC_MILDEW_ON; + cmd.clean = AC_CLEAN_OFF; // для логики пресетов + hasCommand = true; + this->custom_preset = custom_preset; + } else if (custom_preset == Constants::HEALTH) { + _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + } + } + } + } + + if (hasCommand) { + commandSequence(&cmd); + this->publish_state(); // Publish updated state + _new_command_set = _store_settings; // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка + } + } + + esphome::climate::ClimateTraits traits() override { + // The capabilities of the climate device + auto traits = climate::ClimateTraits(); + + traits.set_supports_current_temperature(true); + traits.set_supports_two_point_target_temperature(false); // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature + + // tells the frontend what range of temperatures the climate device should display (gauge min/max values) + traits.set_visual_min_temperature(Constants::AC_MIN_TEMPERATURE); + traits.set_visual_max_temperature(Constants::AC_MAX_TEMPERATURE); + // the step with which to increase/decrease target temperature. This also affects with how many decimal places the temperature is shown. + traits.set_visual_temperature_step(Constants::AC_TEMPERATURE_STEP); + + traits.set_supported_modes(this->_supported_modes); + traits.set_supported_swing_modes(this->_supported_swing_modes); + traits.set_supported_presets(this->_supported_presets); + traits.set_supported_custom_presets(this->_supported_custom_presets); + traits.set_supported_custom_fan_modes(this->_supported_custom_fan_modes); + + /* + MINIMAL SET */ + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF); + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH); + traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF); + //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL); + //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_BOTH); + traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); + //traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); + + /* *************** TODO: надо сделать информирование о текущем режиме, сплит поддерживает *************** + * смотри climate::ClimateAction + */ + // if the climate device supports reporting the active current action of the device with the action property. + traits.set_supports_action(this->_show_action); + + return traits; + } + + // запрос маленького пакета статуса кондиционера + bool getStatusSmall(){ + // нет смысла в последовательности, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("getStatusSmall: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + // есть ли место на запрос в последовательности команд? + if (_getFreeSequenceSpace() < 2) { + _debugMsg(F("getStatusSmall: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + + /*************************************** getSmallInfo request ***********************************************/ + if (!_addSequenceFuncStep(&AirCon::sq_requestSmallStatus)) { + _debugMsg(F("getStatusSmall: getSmallInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + /*************************************** getSmallInfo control ***********************************************/ + if (!_addSequenceFuncStep(&AirCon::sq_controlSmallStatus)) { + _debugMsg(F("getStatusSmall: getSmallInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + /**************************************************************************************/ + + _debugMsg(F("getStatusSmall: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + return true; + } + + // запрос большого пакета статуса кондиционера + bool getStatusBig(){ + // нет смысла в последовательности, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("getStatusBig: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + // есть ли место на запрос в последовательности команд? + if (_getFreeSequenceSpace() < 2) { + _debugMsg(F("getStatusBig: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + + /*************************************** getBigInfo request ***********************************************/ + if (!_addSequenceFuncStep(&AirCon::sq_requestBigStatus)) { + _debugMsg(F("getStatusBig: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + /*************************************** getBigInfo control ***********************************************/ + if (!_addSequenceFuncStep(&AirCon::sq_controlBigStatus)) { + _debugMsg(F("getStatusBig: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + /**************************************************************************************/ + + _debugMsg(F("getStatusBig: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + return true; + } + + // запрос большого и малого пакетов статуса последовательно + bool getStatusBigAndSmall(){ + // нет смысла в последовательности, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("getStatusBigAndSmall: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + + if (!getStatusSmall()) { + _debugMsg(F("getStatusBigAndSmall: error with small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + + if (!getStatusBig()) { + _debugMsg(F("getStatusBigAndSmall: error with big status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + + _debugMsg(F("getStatusBigAndSmall: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + return true; + } + + /** стартовая последовательность пакетов + * + * нужна, чтобы не ждать долго обновления статуса кондиционера + * запускаем сразу, как только удалось подключиться к кондиционеру и прошел первый пинг-пакет + * возвращаемое значение будет присвоено флагу выполнения последовательности + * то есть при возврате false последовательность считается не запущенной и будет вызоваться до тех пор, пока не вернет true + **/ + bool startupSequence(){ + // нет смысла в последовательности, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("startupSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + + // по сути на старте надо получить от кондиционера два статуса + if (!getStatusBigAndSmall()){ + _debugMsg(F("startupSequence: error with big&small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + }; + + _debugMsg(F("startupSequence: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + return true; + } + + /** загружает на выполнение команду + * + * стандартная последовательность - это запрос маленького статусного пакета, выполнение команды и повторный запрос + * такого же статуса для проверки, что всё включилось, ну и для обновления интерфейсов всяких связанных компонентов + **/ + bool commandSequence(ac_command_t * cmd){ + // нет смысла в последовательности, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("commandSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + + // добавление начального запроса маленького статусного пакета в последовательность команд + if (!getStatusSmall()) { + _debugMsg(F("commandSequence: error with first small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + + // есть ли место на запрос в последовательности команд? + if (_getFreeSequenceSpace() < 2) { + _debugMsg(F("commandSequence: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + + /*************************************** set params request ***********************************************/ + if (!_addSequenceFuncStep(&AirCon::sq_requestDoCommand, cmd)) { + _debugMsg(F("commandSequence: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + /*************************************** set params control ***********************************************/ + if (!_addSequenceFuncStep(&AirCon::sq_controlDoCommand)) { + _debugMsg(F("commandSequence: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + /**************************************************************************************/ + + // добавление финального запроса маленького статусного пакета в последовательность команд + if (!getStatusSmall()) { + _debugMsg(F("commandSequence: error with last small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + + _debugMsg(F("commandSequence: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + return true; + } + + // загружает на выполнение последовательность команд на включение/выключение + bool powerSequence(ac_power pwr = AC_POWER_ON){ + // нет смысла в последовательности, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("powerSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + if (pwr == AC_POWER_UNTOUCHED) return false; // выходим, чтобы не тратить время + + // формируем команду + ac_command_t cmd; + _clearCommand(&cmd); // не забываем очищать, а то будет мусор + cmd.power = pwr; + // добавляем команду в последовательность + if (!commandSequence(&cmd)) return false; + + _debugMsg(F("powerSequence: loaded (power = %02X)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, pwr); + return true; + } + + // выключает экран + bool displayOffSequence(){ + ac_display dsp = AC_DISPLAY_OFF; + if (this->get_display_inverted()) dsp = AC_DISPLAY_ON; + return _displaySequence(dsp); + } + + // включает экран + bool displayOnSequence(){ + ac_display dsp = AC_DISPLAY_ON; + if (this->get_display_inverted()) dsp = AC_DISPLAY_OFF; + return _displaySequence(dsp); + } + + // отправляет сплиту заданный набор байт + // Перед отправкой проверяет пакет на корректность структуры. CRC16 рассчитывает самостоятельно и перезаписывает. + bool sendTestPacket(const std::vector &data){ + //bool sendTestPacket(uint8_t *data = nullptr, uitn8_t data_length = 0){ + //if (data == nullptr) return false; + //if (data_length == 0) return false; + if (data.size() == 0) return false; + //if (data_length > AC_BUFFER_SIZE) return false; + if (data.size() > AC_BUFFER_SIZE) return false; + + // нет смысла в отправке, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("sendTestPacket: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + // очищаем пакет + _clearPacket(&_outTestPacket); + + // копируем данные в пакет + //memcpy(_outTestPacket.data, data, data_length); + uint8_t i = 0; + for (uint8_t n : data) { + _outTestPacket.data[i] = n; + i++; + } + + // на всякий случай указываем правильные некоторые байты + _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; + //_outTestPacket.header->wifi = AC_PACKET_ANSWER; + + _outTestPacket.msec = millis(); + _outTestPacket.body = &(_outTestPacket.data[AC_HEADER_SIZE]); + _outTestPacket.bytesLoaded = AC_HEADER_SIZE + _outTestPacket.header->body_length + 2; + + // рассчитываем и записываем в пакет CRC + _outTestPacket.crc = (packet_crc_t *) &(_outTestPacket.data[AC_HEADER_SIZE + _outTestPacket.header->body_length]); + _setCRC16(&_outTestPacket); + + _debugMsg(F("sendTestPacket: test packet loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugPrintPacket(&_outTestPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); + + // ниже блок добавления отправки пакета в последовательность команд + //***************************************************************** + // есть ли место на запрос в последовательности команд? + if (_getFreeSequenceSpace() < 1) { + _debugMsg(F("sendTestPacket: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + + /*************************************** sendTestPacket request ***********************************************/ + if (!_addSequenceFuncStep(&AirCon::sq_requestTestPacket)) { + _debugMsg(F("sendTestPacket: sendTestPacket request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + /**************************************************************************************/ + + _debugMsg(F("sendTestPacket: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + return true; + } + + void set_period(uint32_t ms) { this->_update_period = ms; } + uint32_t get_period() { return this->_update_period; } + + void set_show_action(bool show_action) { this->_show_action = show_action; } + bool get_show_action() { return this->_show_action; } + + void set_display_inverted(bool display_inverted) { this->_display_inverted = display_inverted; } + bool get_display_inverted() { return this->_display_inverted; } + + void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } + bool get_store_settings() { return this->_store_settings; } + + void set_supported_modes(const std::set &modes) { this->_supported_modes = modes; } + void set_supported_swing_modes(const std::set &modes) { this->_supported_swing_modes = modes; } + void set_supported_presets(const std::set &presets) { this->_supported_presets = presets; } + void set_custom_presets(const std::set &presets) { this->_supported_custom_presets = presets; } + void set_custom_fan_modes(const std::set &modes) { this->_supported_custom_fan_modes = modes; } + + uint8_t load_presets_result = 0xFF; + void setup() override { + #if defined(ESP32) + load_presets_result = storage.load(global_presets); // читаем все пресеты из флеша + _debugMsg(F("Preset base read from NVRAM, result %02d."), ESPHOME_LOG_LEVEL_WARN, __LINE__, load_presets_result); + #endif + }; + + void loop() override { + if (!get_hw_initialized()) return; + + // контролируем сохранение пресета + if(_new_command_set){ //нужно сохранить пресет + _new_command_set = false; + save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов + } + + // отрабатываем состояния конечного автомата + switch (_ac_state) { + case ACSM_RECEIVING_PACKET: + // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны + _doReceivingPacketState(); + break; + + case ACSM_PARSING_PACKET: + // разбираем полученный пакет + _doParsingPacket(); + break; + + case ACSM_SENDING_PACKET: + // отправляем пакет сплиту + _doSendingPacketState(); + break; + + case ACSM_IDLE: // ничего не делаем, ждем, на что бы среагировать + default: // если состояние какое-то посторонее, то считаем, что IDLE + _doIdleState(); + break; + } + + // раз в заданное количество миллисекунд запрашиваем обновление статуса кондиционера + if ((millis()-_dataMillis) > _update_period){ + _dataMillis = millis(); + + // обычный wifi-модуль запрашивает маленький пакет статуса + // но нам никто не мешает запрашивать и большой и маленький, чтобы чаще обновлять комнатную температуру + // делаем этот запросо только в случае, если есть коннект с кондиционером + if (get_has_connection()) getStatusBigAndSmall(); + } + + }; + }; +} // namespace aux_ac +} // namespace esphome \ No newline at end of file diff --git a/climate.py b/climate.py new file mode 100644 index 0000000..58a9ab2 --- /dev/null +++ b/climate.py @@ -0,0 +1,341 @@ +import logging +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.components import climate, uart, sensor, binary_sensor +from esphome import automation +from esphome.automation import maybe_simple_id +from esphome.const import ( + CONF_ID, + CONF_UART_ID, + CONF_PERIOD, + CONF_CUSTOM_FAN_MODES, + CONF_CUSTOM_PRESETS, + CONF_INTERNAL, + CONF_DATA, + CONF_SUPPORTED_MODES, + CONF_SUPPORTED_SWING_MODES, + CONF_SUPPORTED_PRESETS, + CONF_PRESSURE, + UNIT_CELSIUS, + UNIT_PERCENT, + UNIT_PASCAL, + ICON_POWER, + ICON_THERMOMETER, + ICON_GAS_CYLINDER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_PRESSURE, + STATE_CLASS_MEASUREMENT, +) +from esphome.components.climate import ( + ClimateMode, + ClimatePreset, + ClimateSwingMode, +) + +_LOGGER = logging.getLogger(__name__) + +CODEOWNERS = ["@GrKoR"] +DEPENDENCIES = ["climate", "uart"] +AUTO_LOAD = ["sensor", "binary_sensor"] + +CONF_SUPPORTED_MODES = 'supported_modes' +CONF_SUPPORTED_SWING_MODES = 'supported_swing_modes' +CONF_SUPPORTED_PRESETS = 'supported_presets' +CONF_SHOW_ACTION = 'show_action' +CONF_INDOOR_TEMPERATURE = 'indoor_temperature' +CONF_OUTDOOR_TEMPERATURE = 'outdoor_temperature' +ICON_OUTDOOR_TEMPERATURE = 'mdi:home-thermometer-outline' +CONF_INBOUND_TEMPERATURE = 'inbound_temperature' +ICON_INBOUND_TEMPERATURE = 'mdi:thermometer-plus' +CONF_OUTBOUND_TEMPERATURE = 'outbound_temperature' +ICON_OUTBOUND_TEMPERATURE = 'mdi:thermometer-minus' +CONF_STRANGE_TEMPERATURE = 'strange_temperature' +ICON_STRANGE_TEMPERATURE = 'mdi:thermometer-lines' +CONF_DISPLAY_STATE = 'display_state' +CONF_INVERTOR_POWER = 'invertor_power' +CONF_DEFROST_STATE = 'defrost_state' +ICON_DEFROST = "mdi:snowflake-melt" +CONF_DISPLAY_INVERTED = 'display_inverted' +ICON_DISPLAY = "mdi:clock-digital" +CONF_STORE_SETTINGS = 'store_settings' + + +aux_ac_ns = cg.esphome_ns.namespace("aux_ac") +AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) +Capabilities = aux_ac_ns.namespace("Constants") + +AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action) +AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action) +AirConSendTestPacketAction = aux_ac_ns.class_("AirConSendTestPacketAction", automation.Action) + +ALLOWED_CLIMATE_MODES = { + "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, + "COOL": ClimateMode.CLIMATE_MODE_COOL, + "HEAT": ClimateMode.CLIMATE_MODE_HEAT, + "DRY": ClimateMode.CLIMATE_MODE_DRY, + "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, +} +validate_modes = cv.enum(ALLOWED_CLIMATE_MODES, upper=True) + +ALLOWED_CLIMATE_PRESETS = { + "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, +} +validate_presets = cv.enum(ALLOWED_CLIMATE_PRESETS, upper=True) + +ALLOWED_CLIMATE_SWING_MODES = { + "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, + "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, + "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, +} +validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True) + +CUSTOM_FAN_MODES = { + "MUTE": Capabilities.MUTE, + "TURBO": Capabilities.TURBO, +} +validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) + +CUSTOM_PRESETS = { + "CLEAN": Capabilities.CLEAN, + "HEALTH": Capabilities.HEALTH, + "ANTIFUNGUS": Capabilities.ANTIFUNGUS, +} +validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True) + + +def validate_raw_data(value): + if isinstance(value, list): + return cv.Schema([cv.hex_uint8_t])(value) + raise cv.Invalid( + "data must be a list of bytes" + ) + + +def output_info(config): + """_LOGGER.info(config)""" + return config + +CONFIG_SCHEMA = cv.All( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(AirCon), + cv.Optional(CONF_PERIOD, default="7s"): cv.time_period, + cv.Optional(CONF_SHOW_ACTION, default="true"): cv.boolean, + cv.Optional(CONF_DISPLAY_INVERTED, default="false"): cv.boolean, + cv.Optional(CONF_STORE_SETTINGS, default="false"): cv.boolean, + cv.Optional(CONF_DEFROST_STATE, default="false"): cv.boolean, + cv.Optional(CONF_INVERTOR_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_POWER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, + } + ), + cv.Optional(CONF_INDOOR_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, + } + ), + cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_OUTDOOR_TEMPERATURE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, + } + ), + cv.Optional(CONF_INBOUND_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_INBOUND_TEMPERATURE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, + } + ), + cv.Optional(CONF_OUTBOUND_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_OUTBOUND_TEMPERATURE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, + } + ), + cv.Optional(CONF_STRANGE_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_STRANGE_TEMPERATURE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, + } + ), + + cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema( + icon=ICON_DISPLAY + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean + } + ), + + cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema( + icon=ICON_DEFROST + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean + } + ), + + + cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes), + cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(validate_swing_modes), + cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(validate_presets), + cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(validate_custom_presets), + cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list(validate_custom_fan_modes), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), + output_info +) + +async def to_code(config): + """_LOGGER.info("--------------")""" + """_LOGGER.info(config)""" + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await climate.register_climate(var, config) + + parent = await cg.get_variable(config[CONF_UART_ID]) + cg.add(var.initAC(parent)) + + if CONF_INDOOR_TEMPERATURE in config: + conf = config[CONF_INDOOR_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_indoor_temperature_sensor(sens)) + + if CONF_OUTDOOR_TEMPERATURE in config: + conf = config[CONF_OUTDOOR_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_outdoor_temperature_sensor(sens)) + + if CONF_OUTBOUND_TEMPERATURE in config: + conf = config[CONF_OUTBOUND_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_outbound_temperature_sensor(sens)) + + if CONF_INBOUND_TEMPERATURE in config: + conf = config[CONF_INBOUND_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_inbound_temperature_sensor(sens)) + + if CONF_STRANGE_TEMPERATURE in config: + conf = config[CONF_STRANGE_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_strange_temperature_sensor(sens)) + + if CONF_DISPLAY_STATE in config: + conf = config[CONF_DISPLAY_STATE] + sens = await binary_sensor.new_binary_sensor(conf) + cg.add(var.set_display_sensor(sens)) + + if CONF_DEFROST_STATE in config: + conf = config[CONF_DEFROST_STATE] + sens = await binary_sensor.new_binary_sensor(conf) + cg.add(var.set_defrost_state(sens)) + + if CONF_INVERTOR_POWER in config: + conf = config[CONF_INVERTOR_POWER] + sens = await sensor.new_sensor(conf) + cg.add(var.set_invertor_power_sensor(sens)) + + cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) + cg.add(var.set_show_action(config[CONF_SHOW_ACTION])) + cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED])) + cg.add(var.set_store_settings(config[CONF_STORE_SETTINGS])) + if CONF_SUPPORTED_MODES in config: + cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) + if CONF_SUPPORTED_SWING_MODES in config: + cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) + if CONF_SUPPORTED_PRESETS in config: + cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) + if CONF_CUSTOM_PRESETS in config: + cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS])) + if CONF_CUSTOM_FAN_MODES in config: + cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) + + + +DISPLAY_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(AirCon), + } +) + +@automation.register_action("aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA) +async def display_off_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + +@automation.register_action("aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA) +async def display_on_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(AirCon), + cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + } +) + + +# ********************************************************************************************************* +# ВАЖНО! Только для инженеров! +# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт +# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает. +# Вы действуете на свой страх и риск. +# ********************************************************************************************************* +@automation.register_action( + "aux_ac.send_packet", + AirConSendTestPacketAction, + SEND_TEST_PACKET_ACTION_SCHEMA +) +async def send_packet_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + + data = config[CONF_DATA] + if isinstance(data, bytes): + data = list(data) + + if cg.is_template(data): + templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) + cg.add(var.set_data_template(templ)) + else: + cg.add(var.set_data_static(data)) + + return var \ No newline at end of file From 2784338f3b45a9b2c87a316243346d4a5bc30f02 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 23:03:26 +0300 Subject: [PATCH 29/74] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20CL?= =?UTF-8?q?EEN?= 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 deletion(-) diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml index 7f3e8f7..6b7acce 100644 --- a/examples/advanced/ac-energolux-bern.yaml +++ b/examples/advanced/ac-energolux-bern.yaml @@ -118,7 +118,6 @@ climate: supported_presets: - SLEEP custom_presets: - - CLEAN - FEEL - HEALTH - ANTIFUNGUS From 522d1b5f359c986e24413ea8f118c5d50cfa64bc Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 23:04:47 +0300 Subject: [PATCH 30/74] Update ac-energolux-bern.yaml --- examples/advanced/ac-energolux-bern.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml index 7f3e8f7..6b7acce 100644 --- a/examples/advanced/ac-energolux-bern.yaml +++ b/examples/advanced/ac-energolux-bern.yaml @@ -118,7 +118,6 @@ climate: supported_presets: - SLEEP custom_presets: - - CLEAN - FEEL - HEALTH - ANTIFUNGUS From 0016b0cd3a9c1a520d3fe775a5762c43363d5c60 Mon Sep 17 00:00:00 2001 From: Brokly Date: Thu, 26 May 2022 23:17:40 +0300 Subject: [PATCH 31/74] Add files via upload --- components/aux_ac/aux_ac.h | 192 ++++++++++++----------------------- components/aux_ac/climate.py | 1 - 2 files changed, 66 insertions(+), 127 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 49768b4..e8efa3e 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -19,7 +19,7 @@ #warning "Saving presets does not work with ESP8266" #endif -#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны +//#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны namespace esphome { namespace aux_ac { @@ -40,7 +40,6 @@ public: static const std::string MUTE; static const std::string TURBO; static const std::string CLEAN; - static const std::string FEEL; static const std::string HEALTH; static const std::string ANTIFUNGUS; @@ -58,12 +57,11 @@ public: const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; const char *const Constants::TAG = "AirCon"; -const std::string Constants::MUTE = "Mute"; -const std::string Constants::TURBO = "Turbo"; -const std::string Constants::CLEAN = "Clean"; -const std::string Constants::FEEL = "Feel"; -const std::string Constants::HEALTH = "Health"; -const std::string Constants::ANTIFUNGUS = "Antifugnus"; +const std::string Constants::MUTE = "mute"; +const std::string Constants::TURBO = "turbo"; +const std::string Constants::CLEAN = "clean"; +const std::string Constants::HEALTH = "health"; +const std::string Constants::ANTIFUNGUS = "antifungus"; const float Constants::AC_MIN_TEMPERATURE = 16.0; const float Constants::AC_MAX_TEMPERATURE = 32.0; const float Constants::AC_TEMPERATURE_STEP = 0.5; @@ -546,7 +544,6 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_health health;\ ac_mode mode;\ ac_sleep sleep;\ - ac_ifeel iFeel;\ ac_louver louver;\ ac_fanspeed fanSpeed;\ ac_fanturbo fanTurbo;\ @@ -556,11 +553,11 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_timer timer;\ uint8_t timer_hours;\ uint8_t timer_minutes;\ - bool temp_target_matter\ + bool temp_target_matter // чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше // из-за такого приема нужно контролировать размер копируемых данных руками -#define AC_COMMAND_BASE_SIZE 21 +#define AC_COMMAND_BASE_SIZE 20 struct ac_command_t { /* @@ -569,7 +566,6 @@ struct ac_command_t { ac_health health; // включение ионизатора ac_mode mode; ac_sleep sleep; - ac_ifeel iFeel; ac_louver louver; ac_fanspeed fanSpeed; ac_fanturbo fanTurbo; @@ -593,6 +589,7 @@ struct ac_command_t { uint8_t invertor_power; // мощность инвертора uint8_t pressure; // предположительно давление bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) + ac_ifeel iFeel; }; // структура для сохранения данных @@ -1263,7 +1260,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _current_ac_state.sleep = (ac_sleep)stateByte; stateByte = small_info_body->mode & AC_IFEEL_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.iFeel != (ac_ifeel)stateByte); _current_ac_state.iFeel = (ac_ifeel)stateByte; stateByte = small_info_body->status & AC_POWER_MASK; @@ -2201,100 +2197,63 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ - if ( _current_ac_state.power == AC_POWER_ON){ // ЕСЛИ КОНДЕЙ ВКЛЮЧЕН - /*************************** iFEEL CUSTOM PRESET ***************************/ - // режим поддержки температуры в районе пульта, работает только при включенном конедее - switch (_current_ac_state.iFeel) { - case AC_IFEEL_ON: - //if(_current_ac_state.mode != AC_MODE_FAN){ // в режиме FAN такой опции нет - this->custom_preset = Constants::FEEL; - //} else { - //// if (this->custom_preset == Constants::FEEL) this->custom_preset = (std::string)""; - //} - break; - case AC_IFEEL_OFF: - default: - if (this->custom_preset == Constants::FEEL) { - this->custom_preset = (std::string)""; - } - break; - } - _debugMsg(F("Climate iFEEL preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.iFeel); - - /*************************** HEALTH CUSTOM PRESET ***************************/ - // режим работы ионизатора - if(_current_ac_state.health == AC_HEALTH_ON) { - this->custom_preset = Constants::HEALTH; - } else { - if (this->custom_preset == Constants::HEALTH) { - this->custom_preset = (std::string)""; - } - } - - _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); - - /*************************** SLEEP PRESET ***************************/ - // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - switch (_current_ac_state.sleep) { - case AC_SLEEP_ON: - this->preset = climate::CLIMATE_PRESET_SLEEP; - break; - case AC_SLEEP_OFF: - default: - if (this->preset == climate::CLIMATE_PRESET_SLEEP) this->preset = climate::CLIMATE_PRESET_NONE; - break; - } - - _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); - - } else if( _current_ac_state.power == AC_POWER_OFF){ // ЕСЛИ КОНДЕЙ ВЫКЛЮЧЕН - - if (this->custom_preset == Constants::FEEL) this->custom_preset = (std::string)""; - if (this->custom_preset == Constants::HEALTH) this->custom_preset = (std::string)""; - if (this->preset == climate::CLIMATE_PRESET_SLEEP) this->preset = climate::CLIMATE_PRESET_NONE; - - /*************************** CLEAN CUSTOM PRESET ***************************/ - // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF - switch (_current_ac_state.clean) { - case AC_CLEAN_ON: - this->custom_preset = Constants::CLEAN; - break; - case AC_CLEAN_OFF: - default: - if (this->custom_preset == Constants::CLEAN) this->custom_preset = (std::string)""; - break; - } - - _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); - - /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ - // пресет просушки кондиционера после выключения - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - // - // Brokly: - // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет - // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии - // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки - // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало - // и затыкается. - switch (_current_ac_state.mildew) { - case AC_MILDEW_ON: - this->custom_preset = Constants::ANTIFUNGUS; - break; - case AC_MILDEW_OFF: - default: - if (this->custom_preset == Constants::ANTIFUNGUS) this->custom_preset = (std::string)""; - break; - } - - _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); + /*************************** SLEEP PRESET ***************************/ + // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. + // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. + // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. + // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. + if(_current_ac_state.sleep == AC_SLEEP_ON && + _current_ac_state.power == AC_POWER_ON ) { + this->preset = climate::CLIMATE_PRESET_SLEEP; + } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { + this->preset = climate::CLIMATE_PRESET_NONE; } + + _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); + + /*************************** HEALTH CUSTOM PRESET ***************************/ + // режим работы ионизатора + if(_current_ac_state.health == AC_HEALTH_ON && + _current_ac_state.power == AC_POWER_ON ) { + this->custom_preset = Constants::HEALTH; + } else if (this->custom_preset == Constants::HEALTH) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); + + /*************************** CLEAN CUSTOM PRESET ***************************/ + // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF + if(_current_ac_state.clean == AC_CLEAN_ON && + _current_ac_state.power == AC_POWER_OFF ) { + this->custom_preset = Constants::CLEAN; + } else if (this->custom_preset == Constants::CLEAN) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); + + /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ + // пресет просушки кондиционера после выключения + // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. + // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. + // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. + // + // Brokly: + // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет + // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии + // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки + // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало + // и затыкается. + if(_current_ac_state.mildew == AC_MILDEW_ON && + _current_ac_state.power == AC_POWER_OFF ) { + this->custom_preset = Constants::ANTIFUNGUS; + } else if (this->custom_preset == Constants::ANTIFUNGUS) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); /*************************** LOUVERs ***************************/ @@ -2385,7 +2344,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { ESP_LOGCONFIG(Constants::TAG, " [x] Show action: %s", TRUEFALSE(this->get_show_action())); ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", TRUEFALSE(this->get_display_inverted())); ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); - ESP_LOGCONFIG(Constants::TAG, " [?] Detect invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "unread"); + ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); if ((this->sensor_indoor_temperature_) != nullptr) { ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); if (!(this->sensor_indoor_temperature_)->get_device_class().empty()) { @@ -2651,12 +2610,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_FAN) || (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_FAN)){ // блокировка ввода температуры в режиме FAN this->target_temperature = _current_ac_state.temp_ambient; - //this->mode = climate::CLIMATE_MODE_FAN_ONLY; this->publish_state(); } else if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_AUTO) || (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_AUTO)){ // блокировка ввода температуры в режиме АВТО this->target_temperature = 25; - //this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->publish_state(); } else { // User requested target temperature change @@ -2772,7 +2729,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.sleep = AC_SLEEP_ON; - cmd.iFeel = AC_IFEEL_OFF; // для логики пресетов cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; this->preset = preset; @@ -2786,7 +2742,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - cmd.iFeel = AC_IFEEL_OFF; // для логики пресетов this->preset = preset; _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); break; @@ -2796,22 +2751,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } else if (call.get_custom_preset().has_value()) { std::string custom_preset = *call.get_custom_preset(); - if (custom_preset == Constants::FEEL) { - // Пульт для работы этого режима не нужен. Как описывает производитель: - // температура выбирается не четкой логикой, анализируя действий - // пользователясвязанных с изменением температуры температуры. - // При этом этот режим можно установить только с пульта. Фигня какая то. - hasCommand = true; - cmd.iFeel = AC_IFEEL_ON; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::HEALTH) { + if (custom_preset == Constants::HEALTH) { hasCommand = true; cmd.health = AC_HEALTH_ON; cmd.health_status = AC_HEALTH_STATUS_ON; - cmd.iFeel = AC_IFEEL_ON; // зависимость от health cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health cmd.sleep = AC_SLEEP_OFF; // для логики пресетов @@ -2834,7 +2777,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (call.get_target_temperature().has_value()) { // блокировка изменения температуры в выключеном состоянии this->target_temperature = _current_ac_state.temp_ambient; - //this->mode = climate::CLIMATE_MODE_OFF; this->publish_state(); } @@ -2878,8 +2820,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.clean = AC_CLEAN_OFF; // для логики пресетов hasCommand = true; this->custom_preset = custom_preset; - } else if (custom_preset == Constants::FEEL) { - _debugMsg(F("iFEEL is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } else if (custom_preset == Constants::HEALTH) { _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 414d17a..58a9ab2 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -98,7 +98,6 @@ validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) CUSTOM_PRESETS = { "CLEAN": Capabilities.CLEAN, - "FEEL": Capabilities.FEEL, "HEALTH": Capabilities.HEALTH, "ANTIFUNGUS": Capabilities.ANTIFUNGUS, } From b777730d70ad38922cfec487c50d638fa45c9acc Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 07:46:22 +0300 Subject: [PATCH 32/74] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B2=D0=BD=D0=B5=D1=88=D0=BD=D0=B5?= =?UTF-8?q?=D0=B9=20=D1=82=D0=B5=D0=BC=D0=BF=D0=B5=D1=80=D0=B0=D1=82=D1=83?= =?UTF-8?q?=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/aux_ac.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index e8efa3e..1738976 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -1314,10 +1314,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // temp = big_info_body->outdoor_temperature - 0x20; // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах { - const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; - const float antkoef = 1.0 - koef; - static float temp = _current_ac_state.temp_outdoor; - temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); + const float koef = ((float)OUTDOOR_FILTER_PESCENT); + const float antkoef = 100 - koef; + static float temp = _current_ac_state.temp_outdoor*100; + temp = (temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20))/100; stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); _current_ac_state.temp_outdoor = temp; } From 88251bc8d652040bd7d2976896f3aa47ce8db98c Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 07:47:18 +0300 Subject: [PATCH 33/74] Add files via upload --- components/aux_ac/aux_ac.h | 200 +++++++++++++------------------------ 1 file changed, 70 insertions(+), 130 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 49768b4..1738976 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -19,7 +19,7 @@ #warning "Saving presets does not work with ESP8266" #endif -#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны +//#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны namespace esphome { namespace aux_ac { @@ -40,7 +40,6 @@ public: static const std::string MUTE; static const std::string TURBO; static const std::string CLEAN; - static const std::string FEEL; static const std::string HEALTH; static const std::string ANTIFUNGUS; @@ -58,12 +57,11 @@ public: const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; const char *const Constants::TAG = "AirCon"; -const std::string Constants::MUTE = "Mute"; -const std::string Constants::TURBO = "Turbo"; -const std::string Constants::CLEAN = "Clean"; -const std::string Constants::FEEL = "Feel"; -const std::string Constants::HEALTH = "Health"; -const std::string Constants::ANTIFUNGUS = "Antifugnus"; +const std::string Constants::MUTE = "mute"; +const std::string Constants::TURBO = "turbo"; +const std::string Constants::CLEAN = "clean"; +const std::string Constants::HEALTH = "health"; +const std::string Constants::ANTIFUNGUS = "antifungus"; const float Constants::AC_MIN_TEMPERATURE = 16.0; const float Constants::AC_MAX_TEMPERATURE = 32.0; const float Constants::AC_TEMPERATURE_STEP = 0.5; @@ -546,7 +544,6 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_health health;\ ac_mode mode;\ ac_sleep sleep;\ - ac_ifeel iFeel;\ ac_louver louver;\ ac_fanspeed fanSpeed;\ ac_fanturbo fanTurbo;\ @@ -556,11 +553,11 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_timer timer;\ uint8_t timer_hours;\ uint8_t timer_minutes;\ - bool temp_target_matter\ + bool temp_target_matter // чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше // из-за такого приема нужно контролировать размер копируемых данных руками -#define AC_COMMAND_BASE_SIZE 21 +#define AC_COMMAND_BASE_SIZE 20 struct ac_command_t { /* @@ -569,7 +566,6 @@ struct ac_command_t { ac_health health; // включение ионизатора ac_mode mode; ac_sleep sleep; - ac_ifeel iFeel; ac_louver louver; ac_fanspeed fanSpeed; ac_fanturbo fanTurbo; @@ -593,6 +589,7 @@ struct ac_command_t { uint8_t invertor_power; // мощность инвертора uint8_t pressure; // предположительно давление bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) + ac_ifeel iFeel; }; // структура для сохранения данных @@ -1263,7 +1260,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _current_ac_state.sleep = (ac_sleep)stateByte; stateByte = small_info_body->mode & AC_IFEEL_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.iFeel != (ac_ifeel)stateByte); _current_ac_state.iFeel = (ac_ifeel)stateByte; stateByte = small_info_body->status & AC_POWER_MASK; @@ -1318,10 +1314,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // temp = big_info_body->outdoor_temperature - 0x20; // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах { - const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; - const float antkoef = 1.0 - koef; - static float temp = _current_ac_state.temp_outdoor; - temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); + const float koef = ((float)OUTDOOR_FILTER_PESCENT); + const float antkoef = 100 - koef; + static float temp = _current_ac_state.temp_outdoor*100; + temp = (temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20))/100; stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); _current_ac_state.temp_outdoor = temp; } @@ -2201,100 +2197,63 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ - if ( _current_ac_state.power == AC_POWER_ON){ // ЕСЛИ КОНДЕЙ ВКЛЮЧЕН - /*************************** iFEEL CUSTOM PRESET ***************************/ - // режим поддержки температуры в районе пульта, работает только при включенном конедее - switch (_current_ac_state.iFeel) { - case AC_IFEEL_ON: - //if(_current_ac_state.mode != AC_MODE_FAN){ // в режиме FAN такой опции нет - this->custom_preset = Constants::FEEL; - //} else { - //// if (this->custom_preset == Constants::FEEL) this->custom_preset = (std::string)""; - //} - break; - case AC_IFEEL_OFF: - default: - if (this->custom_preset == Constants::FEEL) { - this->custom_preset = (std::string)""; - } - break; - } - _debugMsg(F("Climate iFEEL preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.iFeel); - - /*************************** HEALTH CUSTOM PRESET ***************************/ - // режим работы ионизатора - if(_current_ac_state.health == AC_HEALTH_ON) { - this->custom_preset = Constants::HEALTH; - } else { - if (this->custom_preset == Constants::HEALTH) { - this->custom_preset = (std::string)""; - } - } - - _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); - - /*************************** SLEEP PRESET ***************************/ - // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - switch (_current_ac_state.sleep) { - case AC_SLEEP_ON: - this->preset = climate::CLIMATE_PRESET_SLEEP; - break; - case AC_SLEEP_OFF: - default: - if (this->preset == climate::CLIMATE_PRESET_SLEEP) this->preset = climate::CLIMATE_PRESET_NONE; - break; - } - - _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); - - } else if( _current_ac_state.power == AC_POWER_OFF){ // ЕСЛИ КОНДЕЙ ВЫКЛЮЧЕН - - if (this->custom_preset == Constants::FEEL) this->custom_preset = (std::string)""; - if (this->custom_preset == Constants::HEALTH) this->custom_preset = (std::string)""; - if (this->preset == climate::CLIMATE_PRESET_SLEEP) this->preset = climate::CLIMATE_PRESET_NONE; - - /*************************** CLEAN CUSTOM PRESET ***************************/ - // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF - switch (_current_ac_state.clean) { - case AC_CLEAN_ON: - this->custom_preset = Constants::CLEAN; - break; - case AC_CLEAN_OFF: - default: - if (this->custom_preset == Constants::CLEAN) this->custom_preset = (std::string)""; - break; - } - - _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); - - /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ - // пресет просушки кондиционера после выключения - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - // - // Brokly: - // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет - // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии - // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки - // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало - // и затыкается. - switch (_current_ac_state.mildew) { - case AC_MILDEW_ON: - this->custom_preset = Constants::ANTIFUNGUS; - break; - case AC_MILDEW_OFF: - default: - if (this->custom_preset == Constants::ANTIFUNGUS) this->custom_preset = (std::string)""; - break; - } - - _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); + /*************************** SLEEP PRESET ***************************/ + // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. + // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. + // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. + // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. + if(_current_ac_state.sleep == AC_SLEEP_ON && + _current_ac_state.power == AC_POWER_ON ) { + this->preset = climate::CLIMATE_PRESET_SLEEP; + } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { + this->preset = climate::CLIMATE_PRESET_NONE; } + + _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); + + /*************************** HEALTH CUSTOM PRESET ***************************/ + // режим работы ионизатора + if(_current_ac_state.health == AC_HEALTH_ON && + _current_ac_state.power == AC_POWER_ON ) { + this->custom_preset = Constants::HEALTH; + } else if (this->custom_preset == Constants::HEALTH) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); + + /*************************** CLEAN CUSTOM PRESET ***************************/ + // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF + if(_current_ac_state.clean == AC_CLEAN_ON && + _current_ac_state.power == AC_POWER_OFF ) { + this->custom_preset = Constants::CLEAN; + } else if (this->custom_preset == Constants::CLEAN) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); + + /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ + // пресет просушки кондиционера после выключения + // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. + // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. + // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. + // + // Brokly: + // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет + // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии + // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки + // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало + // и затыкается. + if(_current_ac_state.mildew == AC_MILDEW_ON && + _current_ac_state.power == AC_POWER_OFF ) { + this->custom_preset = Constants::ANTIFUNGUS; + } else if (this->custom_preset == Constants::ANTIFUNGUS) { + this->custom_preset = (std::string)""; + } + + _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); /*************************** LOUVERs ***************************/ @@ -2385,7 +2344,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { ESP_LOGCONFIG(Constants::TAG, " [x] Show action: %s", TRUEFALSE(this->get_show_action())); ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", TRUEFALSE(this->get_display_inverted())); ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); - ESP_LOGCONFIG(Constants::TAG, " [?] Detect invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "unread"); + ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); if ((this->sensor_indoor_temperature_) != nullptr) { ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); if (!(this->sensor_indoor_temperature_)->get_device_class().empty()) { @@ -2651,12 +2610,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_FAN) || (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_FAN)){ // блокировка ввода температуры в режиме FAN this->target_temperature = _current_ac_state.temp_ambient; - //this->mode = climate::CLIMATE_MODE_FAN_ONLY; this->publish_state(); } else if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_AUTO) || (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_AUTO)){ // блокировка ввода температуры в режиме АВТО this->target_temperature = 25; - //this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->publish_state(); } else { // User requested target temperature change @@ -2772,7 +2729,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.sleep = AC_SLEEP_ON; - cmd.iFeel = AC_IFEEL_OFF; // для логики пресетов cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; this->preset = preset; @@ -2786,7 +2742,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - cmd.iFeel = AC_IFEEL_OFF; // для логики пресетов this->preset = preset; _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); break; @@ -2796,22 +2751,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } else if (call.get_custom_preset().has_value()) { std::string custom_preset = *call.get_custom_preset(); - if (custom_preset == Constants::FEEL) { - // Пульт для работы этого режима не нужен. Как описывает производитель: - // температура выбирается не четкой логикой, анализируя действий - // пользователясвязанных с изменением температуры температуры. - // При этом этот режим можно установить только с пульта. Фигня какая то. - hasCommand = true; - cmd.iFeel = AC_IFEEL_ON; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::HEALTH) { + if (custom_preset == Constants::HEALTH) { hasCommand = true; cmd.health = AC_HEALTH_ON; cmd.health_status = AC_HEALTH_STATUS_ON; - cmd.iFeel = AC_IFEEL_ON; // зависимость от health cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health cmd.sleep = AC_SLEEP_OFF; // для логики пресетов @@ -2834,7 +2777,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (call.get_target_temperature().has_value()) { // блокировка изменения температуры в выключеном состоянии this->target_temperature = _current_ac_state.temp_ambient; - //this->mode = climate::CLIMATE_MODE_OFF; this->publish_state(); } @@ -2878,8 +2820,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.clean = AC_CLEAN_OFF; // для логики пресетов hasCommand = true; this->custom_preset = custom_preset; - } else if (custom_preset == Constants::FEEL) { - _debugMsg(F("iFEEL is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } else if (custom_preset == Constants::HEALTH) { _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } From 94dbb27f03dee144c9cd6bc87d5242f737af689c Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 07:51:21 +0300 Subject: [PATCH 34/74] Add files via upload --- components/aux_ac/aux_ac.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 1738976..ca1f645 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -1317,9 +1317,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { const float koef = ((float)OUTDOOR_FILTER_PESCENT); const float antkoef = 100 - koef; static float temp = _current_ac_state.temp_outdoor*100; - temp = (temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20))/100; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); - _current_ac_state.temp_outdoor = temp; + temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp/100); + _current_ac_state.temp_outdoor = temp/100; } // температура входящей магистрали From 54bc9f65a25859c7f9834316550a10e7cc7404b2 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 07:53:53 +0300 Subject: [PATCH 35/74] Add files via upload --- components/aux_ac/aux_ac.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 1738976..ca1f645 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -1317,9 +1317,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { const float koef = ((float)OUTDOOR_FILTER_PESCENT); const float antkoef = 100 - koef; static float temp = _current_ac_state.temp_outdoor*100; - temp = (temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20))/100; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); - _current_ac_state.temp_outdoor = temp; + temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp/100); + _current_ac_state.temp_outdoor = temp/100; } // температура входящей магистрали From 99193935100e2b2c1723cedbda431ab77699f8ea Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:05:31 +0300 Subject: [PATCH 36/74] Add files via upload --- components/aux_ac/aux_ac.h | 12 ++++++------ components/aux_ac/climate.py | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index ca1f645..9c6a569 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -529,7 +529,7 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // настройка усреднения фильтра температуры. Это значение - взнос нового измерения // в усредненные показания в процентах -#define OUTDOOR_FILTER_PESCENT 0.2 +#define OUTDOOR_FILTER_PESCENT 0.5 /** команда для кондиционера * @@ -1314,12 +1314,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // temp = big_info_body->outdoor_temperature - 0x20; // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах { - const float koef = ((float)OUTDOOR_FILTER_PESCENT); - const float antkoef = 100 - koef; - static float temp = _current_ac_state.temp_outdoor*100; + const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; + const float antkoef = 1.0 - koef; + static float temp = _current_ac_state.temp_outdoor; temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp/100); - _current_ac_state.temp_outdoor = temp/100; + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); + _current_ac_state.temp_outdoor = temp; } // температура входящей магистрали diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 414d17a..58a9ab2 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -98,7 +98,6 @@ validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) CUSTOM_PRESETS = { "CLEAN": Capabilities.CLEAN, - "FEEL": Capabilities.FEEL, "HEALTH": Capabilities.HEALTH, "ANTIFUNGUS": Capabilities.ANTIFUNGUS, } From d2efbd33dca1722e6c638f4f8cf134fd53d82101 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:16:01 +0300 Subject: [PATCH 37/74] Delete automation.h --- automation.h | 65 ---------------------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 automation.h diff --git a/automation.h b/automation.h deleted file mode 100644 index a62fb27..0000000 --- a/automation.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/core/automation.h" -#include "aux_ac.h" - -namespace esphome { -namespace aux_ac { - - template - class AirConDisplayOffAction : public Action - { - public: - explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {} - - void play(Ts... x) override { this->ac_->displayOffSequence(); } - - protected: - AirCon *ac_; - }; - - template - class AirConDisplayOnAction : public Action - { - public: - explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {} - - void play(Ts... x) override { this->ac_->displayOnSequence(); } - - protected: - AirCon *ac_; - }; - - template - class AirConSendTestPacketAction : public Action - { - public: - explicit AirConSendTestPacketAction(AirCon *ac) : ac_(ac) {} - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - this->static_ = false; - } - void set_data_static(const std::vector &data) { - this->data_static_ = data; - this->static_ = true; - } - - void play(Ts... x) override { - if (this->static_) { - this->ac_->sendTestPacket(this->data_static_); - } else { - auto val = this->data_func_(x...); - this->ac_->sendTestPacket(val); - } - } - - protected: - AirCon *ac_; - bool static_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; - }; - -} // namespace aux_ac -} // namespace esphome \ No newline at end of file From d3ca62bff848e797d952f662c9746db8a89456ee Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:16:13 +0300 Subject: [PATCH 38/74] Delete aux_ac.h --- aux_ac.h | 3194 ------------------------------------------------------ 1 file changed, 3194 deletions(-) delete mode 100644 aux_ac.h diff --git a/aux_ac.h b/aux_ac.h deleted file mode 100644 index e8efa3e..0000000 --- a/aux_ac.h +++ /dev/null @@ -1,3194 +0,0 @@ -// Custom ESPHome component for AUX-based air conditioners -// Need some soldering skills -// Source code and detailed instructions are available on github: https://github.com/GrKoR/esphome_aux_ac_component -/// немного переработанная версия старого компонента -#pragma once - -#include -#include "esphome.h" -#include -#include "esphome/core/component.h" -#include "esphome/components/climate/climate.h" -#include "esphome/components/uart/uart.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/core/helpers.h" -#if defined(ESP32) - #include "esphome/core/preferences.h" -#else - #warning "Saving presets does not work with ESP8266" -#endif - -//#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны - -namespace esphome { -namespace aux_ac { - - -using climate::ClimatePreset; -using climate::ClimateTraits; -using climate::ClimateMode; -using climate::ClimateSwingMode; -using climate::ClimateFanMode; - - -class Constants { -public: - static const std::string AC_FIRMWARE_VERSION; - - static const char *const TAG; - static const std::string MUTE; - static const std::string TURBO; - static const std::string CLEAN; - static const std::string HEALTH; - static const std::string ANTIFUNGUS; - - /// минимальная и максимальная температура в градусах Цельсия, ограничения самого кондиционера - static const float AC_MIN_TEMPERATURE; - static const float AC_MAX_TEMPERATURE; - /// шаг изменения целевой температуры, градусы Цельсия - static const float AC_TEMPERATURE_STEP; - - // периодичность опроса кондиционера на предмет изменения состояния - // изменение параметров с пульта не сообщается в UART, поэтому надо запрашивать состояние, чтобы быть в курсе - // значение в миллисекундах - static const uint32_t AC_STATES_REQUEST_INTERVAL; -}; - -const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; -const char *const Constants::TAG = "AirCon"; -const std::string Constants::MUTE = "mute"; -const std::string Constants::TURBO = "turbo"; -const std::string Constants::CLEAN = "clean"; -const std::string Constants::HEALTH = "health"; -const std::string Constants::ANTIFUNGUS = "antifungus"; -const float Constants::AC_MIN_TEMPERATURE = 16.0; -const float Constants::AC_MAX_TEMPERATURE = 32.0; -const float Constants::AC_TEMPERATURE_STEP = 0.5; -const uint32_t Constants::AC_STATES_REQUEST_INTERVAL = 7000; - -class AirCon; - -// состояния конечного автомата компонента -enum acsm_state : uint8_t { - ACSM_IDLE = 0, // ничего не делаем, ждем, на что бы среагировать - ACSM_RECEIVING_PACKET, // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны - ACSM_PARSING_PACKET, // разбираем полученный пакет - ACSM_SENDING_PACKET, // отправляем пакет сплиту -}; - -/** - * Кондиционер отправляет пакеты следующей структуры: - * HEADER: 8 bytes - * BODY: 0..24 bytes - * CRC: 2 bytes - * Весь пакет максимум 34 байта - * По крайней мере все встреченные мной пакеты имели такой размер и структуру. - **/ -#define AC_HEADER_SIZE 8 -#define AC_MAX_BODY_SIZE 24 -#define AC_BUFFER_SIZE 35 - -/** - * таймаут загрузки пакета - * - * через такое количиство миллисекунд конечный автомат перейдет из состояния ACSM_RECEIVING_PACKET в ACSM_IDLE, если пакет не будет загружен - * расчетное время передачи 1 бита при скорости 4800 примерно 0,208 миллисекунд; - * 1 байт передается 11 битами (1 стартовый, 8 бит данных, 1 бит четности и 1 стоповый бит) или 2,30 мс. - * максимальный размер пакета AC_BUFFER_SIZE = 34 байта => 78,2 мсек. Плюс накладные расходы. - * Скорее всего на получение пакета должно хватать 100 мсек. - * - * По факту проверка показала: - * - если отрабатывать по 1 символу из UART на один вызов loop, то на 10 байт пинг-пакета требуется 166 мсек. - * То есть примерно по 16,6 мсек на байт. Примем 17 мсек. - * Значит на максимальный пакет потребуется 17*34 = 578 мсек. Примем 600 мсек. - * - если отрабатывать пакет целиком или хотя бы имеющимися в буфере UART кусками, то на 10 байт пинг-пакета требуется 27 мсек. - * То есть примерно по 2,7 мсек. на байт. Что близко к расчетным значениям. Примем 3 мсек. - * Значит на максимальный пакет потребуется 3*34 = 102 мсек. Примем 150 мсек. - * Опыт показал, что 150 мсек вполне хватает на большие пакеты - **/ -#define AC_PACKET_TIMEOUT 150 // 150 мсек - отработка буфера UART за раз, 600 мсек - отработка буфера UART по 1 байту за вызов loop - -// типы пакетов -#define AC_PTYPE_PING 0x01 // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает -#define AC_PTYPE_CMD 0x06 // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита -#define AC_PTYPE_INFO 0x07 // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля -#define AC_PTYPE_INIT 0x09 // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. -#define AC_PTYPE_UNKN 0x0b // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует - -// типы команд -#define AC_CMD_STATUS_BIG 0x21 // большой пакет статуса кондиционера -#define AC_CMD_STATUS_SMALL 0x11 // маленький пакет статуса кондиционера -#define AC_CMD_STATUS_PERIODIC 0x2C // иногда встречается, сплит её рассылает по своему разумению; (вроде бы может быть и другой код! надо больше данных) -#define AC_CMD_SET_PARAMS 0x01 // команда установки параметров кондиционера - -// значения байтов в пакетах -#define AC_PACKET_START_BYTE 0xBB // Стартовый байт любого пакета 0xBB, других не встречал -#define AC_PACKET_ANSWER 0x80 // признак ответа wifi-модуля - -// заголовок пакета -struct packet_header_t { - uint8_t start_byte; // стартовый бит пакета, всегда 0xBB - uint8_t _unknown1; // не расшифрован - uint8_t packet_type; // тип пакета: - // 0x01 - пинг - // 0x06 - команда кондиционеру - // 0x07 - информационный пакет со статусом кондиционера - // 0x09 - (не разбирался) инициирование коннекта wifi-модуля с приложением на телефоне, с ESP работает и без этого - // 0x0b - (не разбирался) wifi-модуль так сигналит, когда не получает пинги от кондиционера и в каких-то еще случаях - uint8_t wifi; // признак пакета от wifi-модуля - // 0x80 - для всех сообщений, посылаемых модулем - // 0x00 - для всех сообщений, посылаемых кондиционером - uint8_t ping_answer_01; // не расшифрован, почти всегда 0x00, только в ответе на ping этот байт равен 0x01 - uint8_t _unknown2; // не расшифрован - uint8_t body_length; // длина тела пакета в байтах - uint8_t _unknown3; // не расшифрован -}; - -// CRC пакета -union packet_crc_t { - uint16_t crc16; - uint8_t crc[2]; -}; - -struct packet_t { - uint32_t msec; // значение millis в момент определения корректности пакета - packet_header_t * header; - packet_crc_t * crc; - uint8_t * body; // указатель на первый байт тела; можно приведением типов указателей обращаться к отдельным битам как к полям соответсвующей структуры - uint8_t bytesLoaded; //количество загруженных в пакет байт, включая CRC - uint8_t data[AC_BUFFER_SIZE]; -}; - -// тело ответа на пинг -struct packet_ping_answer_body_t { - uint8_t byte_1C = 0x1C; // первый байт всегда 0x1C - uint8_t byte_27 = 0x27; // второй байт тела пинг-ответа всегда 0x27 - uint8_t zero1 = 0; // всегда 0x00 - uint8_t zero2 = 0; // всегда 0x00 - uint8_t zero3 = 0; // всегда 0x00 - uint8_t zero4 = 0; // всегда 0x00 - uint8_t zero5 = 0; // всегда 0x00 - uint8_t zero6 = 0; // всегда 0x00 -}; - -// тело большого информационного пакета -struct packet_big_info_body_t { - uint8_t byte_01; // всегда 0x01 - uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x21); - // пакет может рассылаться и в дежурном режиме (без запроса со стороны wifi-модуля) - // в этом случае тут могут быть значения, отличные от 0x21 - // БАЙТ2 - uint8_t reserv20 :5; // не расшифрован, всегда 0xC0 11000000 - bool is_invertor :1; // флаг инвертора - uint8_t reserv21 :2; // для RoyalClima18HNI: всегда 0xE0 11100000 - // Brokly: для Energolux Bern: 0xE0; иногда, с равными промежутками во времени проскакивает )xE4 - // предполагаю, что это байт конфига кондиционера (5 бит - инвертер), (2 бит - периодический мпульсный сигнал,период пимерно 500 сек) - // БАЙТ 3 - bool power:1; - bool sleep:1; - bool louver_V:1; - uint8_t louver_H:2; // у шторок лево-право, почему то два бита - uint8_t mode:3; // enum { AC_BIG_MODE_AUTO = 0, - // AC_BIG_MODE_COOL = 1, - // AC_BIG_MODE_DRY = 2, - // AC_BIG_MODE_HEAT = 4, - // AC_BIG_MODE_FAN = 6} - // - // - // Встречались такие значения: - // 0x04 100 - сплит выключен, до этого работал (статус держится 1 час после выкл.) - // 0x05 101 - режим AUTO - // 0x24 100100 - режим OFF - // 0x25 100101 - режим COOL - // 0x39 111001 - ?? - // 0x45 1000101 - режим DRY - // 0x85 10000101 - режим HEAT - // 0xC4 11000100 - режим OFF, выключен давно, зима - // 0xC5 11000101 - режим FAN - // Brokly: - // Встречались такие значения : - // 0x00 00000000 - OFF - // 0x01 00000001 - AUTO // режим авто, нет отдельного бита - // 0x41 1000001 - DRY - // 0x21 100001 - COOL - // 0x81 10000001 - HEAT - // 0x85 10000101 - HEAT+шторки верх-низ - // 0x99 10011001 - HEAT+шторки влево вправо - // 0xC1 11000001 - FAN // 7 и 6 бит связаны - // 0x80 10000000 - продувка после переключения из HEAT в OFF - // 0xC5 11000101 - FAN+шторки верх-низ - // 0xDD 11011101 - FAN+шторки лево-право/верх-низ - // 0xD9 11011001 - FAN+шторки лево-право - // 0xD8 11011000 - из FAN+шторки лево-право в OFF - // 0x39 111001 - COOL+шторки лево-право - // Очевидно битовые, но связные, поля, предположительные зависимости - // ВНИМАНИЕ : режимы номинальны, например в режиме АВТО нагрев или охлаждение не отображаются - // 7+6+5 4+3 2 1 0 - // MODE Louv_L Louv_H SLEEP ON/OFF - // - // ФУНКЦМЯ CLEEN, HEALTH, ANTIFUNGUS на данный байт не влияют - // - // #define AC_BIG_MASK_MODE b11100000 - // enum { AC_BIG_MODE_DRY = 0x40, - // AC_BIG_MODE_COOL = 0x20, - // AC_BIG_MODE_HEAT = 0x80, - // AC_BIG_MODE_FAN = 0xC0} - // #define AC_BIG_MASK_MODE b00011100 - // enum { AC_BIG_LOUVERS_H = 0x04, - // AC_BIG_LOUVERS_L = 0x18, - // AC_BIG_LOUVERS_BOTH = 0x1C} - // #define AC_BIG_MASK_POWER b00000001 - // #define AC_BIG_MASK_SLEEP b00000010 - // #define AC_BIG_MASK_COOL b00100000 - // - // БАЙТ 4 - uint8_t reserv40:4; // 8 - bool needDefrost:1; // 5 бит начало разморозки(накопление тепла) - bool defrostMode:1; // 6 бит режим разморозки внешнего блока (прогрев испарителя) - bool reserv41:1; // для RoyalClima18HNI: режим разморозки внешнего блока - 0x20, в других случаях 0x00 - bool cleen:1; // 8 бит CLEAN - - // Для кондея старт-стоп - // x xx - // C5 11000101 - // C4 11000100 - // 85 10000101 - // 84 10000100 - // 3D 00111101 - // 3C 00111100 - // 25 00100101 - // 24 00100100 - // 5 00000101 - // 4 00000100 - - - // БАЙТ5 - uint8_t realFanSpeed:3; // Brokly: Energolux Bern - подтверждаю, ВАЖНО !!!! Это реальная скорость фена - uint8_t reserv30:5; // та которая в данный момент. Например может быть установлен нагрев со скоростью - // вентилятора HI, но кондей еще не произвел достаточно тепла, и крутит на LOW, - // Тут будет отображаться LOW - // fanSpeed: OFF=0x00, MUTE=0x01, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07 - // в дежурных пакетах тут похоже что-то другое - // БАЙТ6 - bool reserv60:1; - uint8_t fanPWM:7; // скорость шима вентилятора - // 126...128 - turbo - // 100...113 - hi - // 84...85 - mid - // 59...62 - low - // 0 - off - // - // БАЙТ7 - uint8_t ambient_temperature_int; // Brokly: ПОДТВЕРЖДАЮ это точно показания датчика под крышкой внутреннего блока, - // физически доступен для пользователя - // целая часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // перевод по формуле T = Тin - 0x20 + Tid/10 - // где - // Tin - целая часть температуры - // Tid - десятичная часть температуры - - // В ВЫКЛЮЧЕНОМ СОСТОНИИ занчение 7 8 9 10 равны !!!!!! - // А значит это термодатчики внутри внутреннего блока !!!!!! - - // БАЙТ8 - uint8_t zero3; // Brokly: полностью повторяет значение 9 байта - - // БАЙТ9 - uint8_t in_temperature_int; // Brokly: скорее всего это действительно какая то температура или дельта температур - // СКОРЕЕ ВСЕГО ЭТО ТЕМПЕРАТУРА ПОДАЧИ !!!!!! При охлаждении - холодная, при нагреве теплая - - // холоднее или горячее температуры в команте. В выключеном состоянии стремится к комнатной темп. - // у меня на трех инверторных кондиционерах Energolux серии Bern - // в выключеном состоянии значение этого байта находится на уровне 57-68 (мощность 0%) - // зависит от мощности работы компрессора (измерения при 12гр на улице) - // в режиме охлаждения уменьшается и при мощности 47% = 40 / 73% = 38 - // в режиме нагрева увеличивается и при мощности 47% = 70 / 73% = 75 / 84% = 84 - // изменение этого значения более вялое, с западыванием относительно изменения мощности - // видимо является реакцией (следствием работы) на изменение мощности инвертора - // учитывая стиль записи температур имеет смысл рассматривать это значение как увеличенное на 0x20 - - - - // этот байт как-то связан с температурой во внешнем блоке. Требуются дополнительные исследования. - // При выключенном сплите характер изменения значения примерно соответствует изменению температуры на улице. - // При включенном сплите значение может очень сильно скакать. - // По схеме wiring diagram сплит-системы, во внешнем блоке есть термодатчик, отслеживающий температуру испарителя. - // Возможно, этот байт как раз и отражает изменение температуры на испарителе. - // Но я не смог разобраться, как именно перевести эти значения в градусы. - // Кроме того, зимой даже в минусовую температуру этот байт не уходит ниже 0x33 по крайней мере - // для температур в диапазоне -5..-10 градусов Цельсия. - // БАЙТ10 - uint8_t zero4; // Brokly: полностью повторяет значение 9 байта - - // БАЙТ11 - uint8_t zero5; // всегда = 100 (0x64) - - // БАЙТ12 - uint8_t outdoor_temperature; // Brokly Energolux Bern: Внешняя температура формула T=БАЙТ12 - 0x20 - // Датчик на радиаторе внешнего блока, доступен для пользователя, без разборки блока - // температура внешнего теплообменника влияет на это значение (при работе на обогрев - понижает, при охлаждении или при разморозке - повышает) - // для RoyalClima18HNI: похоже на какую-то температуру, точно неизвестно - - // БАЙТ13 - uint8_t out_temperature_int; // всегда 0x00 - // для RoyalClima18HNI: 0x20 - // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ13 - 0x20 - // При охлаждении растет, при нагреве падает, можно делать вывод о режиме COOL или HEAT - // ПОХОЖЕ НА ТЕМПЕРАТУРУ ОБРАТКИ !!! - - // БАЙТ14 - uint8_t strange_temperature_int;// всегда 0x00 - // для RoyalClima18HNI: 0x20 - // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ14 - 0x20 - // показания РАСТЕТ ПРИ ВКЛЮЧЕНИИ ИНВЕРТОРА, при выключении падают до комнатной - // от режима охлаждения или нагрева не зависит !!! - - - // БАЙТ15 - uint8_t zero9; // всегда 0x00, Brokly: Energolux Bern, всегда 0x39 111001 - // БАЙТ16 - uint8_t invertor_power; // МОщность инвертера (Brokly: подтверждаю) - // для RoyalClima18HNI: мощность инвертора (от 0 до 100) в % - // например, разморозка внешнего блока происходит при 80% - // БАЙТ17 - uint8_t zero11; // всегда 0x00 - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 22, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика достаточно плавна - - // БАЙТ18 - uint8_t zero12; // - // Brokly: Energolux Bern : наложение на показания инвертора (от 144 до 174, когда инвертор отключен - // показания немного скачут в районе 149...154, при включении инвертора быстро растет, при выключении - // моментально падает до 149...154, бывают опускания ниже этих значений до 144, чаще в момент первоначального - // включения инвертора, а потом вверх, не всегда. При включении уходит в 0 на одну посылку - - // БАЙТ19 - uint8_t zero13; // Brokly: Energolux Bern : включение 144 -> 124 -> 110 далее все время держим 110 - - // БАЙТ20 - uint8_t zero14; // - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 45, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика дрожащая нестабильная - // колебания в районе +-2...4 единицы - // БАЙТ21 - uint8_t zero15; // всегда 0x00 Brokly: подтверждаю - - // БАЙТ22 - uint8_t zero16; // всегда 0x00 Brokly: подтверждаю - - // БАЙТ23 - uint8_t ambient_temperature_frac:4; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // подробнее смотреть ambient_temperature_int - // для RoyalClima18HNI: старшие 4 бита - 0x2 - uint8_t reserv023:4; -}; - -// тело малого информационного пакета -struct packet_small_info_body_t { - uint8_t byte_01; // не расшифрован, всегда 0x01 - uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x11); - // в пакетах сплита другие варианты не встречаются - // в отправляемых wifi-модулем пакетах тут может быть 0x01, если требуется установить режим работы - uint8_t target_temp_int_and_v_louver; // целая часть целевой температуры и положение вертикальных жалюзи - // три младших бита - положение вертикальных жалюзи - // если они все = 0, то вертикальный SWING включен - // если они все = 1, то выключен вертикальный SWING - // протокол универсильный, другие комбинации битов могут задавать какие-то положения - // вертикальных жалюзи, но у меня на пульте таких возможностей нет, надо экспериментировать. - // пять старших бит - целая часть целевой температуры - // температура определяется по формуле: - // 8 + (target_temp_int_and_v_louver >> 3) + (0.5 * (target_temp_frac >> 7)) - uint8_t h_louver; // старшие 3 бита - положение горизонтальных жалюзи, остальное не изучено и всегда было 0 - // если все 3 бита = 0, то горизонтальный SWING включен - // если все 3 бита = 1, то горизонтальный SWING отключен - // надо изучить другие комбинации - uint8_t target_temp_frac; // старший бит - дробная часть целевой температуры - // остальные биты до конца не изучены: - // бит 6 был всегда 0 - // биты 0..5 растут на 1 каждую минуту, возможно внутренний таймер для включения/выключения по времени - uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны - // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 - uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны - // БФЙТ 7 - uint8_t mode; // режим работы сплита: - // AUTO : bits[7, 6, 5] = [0, 0, 0] - // COOL : bits[7, 6, 5] = [0, 0, 1] - // DRY : bits[7, 6, 5] = [0, 1, 0] - // HEAT : bits[7, 6, 5] = [1, 0, 1] - // FAN : bits[7, 6, 5] = [1, 1, 1] - // Sleep function : bit 2 = 1 - // iFeel function : bit 3 = 1 - uint8_t zero1; // всегда 0x00 - uint8_t zero2; // всегда 0x00 - uint8_t status; // бит 5 = 1: включен, обычный режим работы (когда можно включить нагрев, охлаждение и т.п.) - // бит 2 = 1: режим самоочистки, должен запускаться только при бит 5 = 0 - // бит 0 и бит 1: активация режима ионизатора воздуха (не проверен, у меня его нет) - uint8_t zero3; // всегда 0x00 - uint8_t display_and_mildew; // бит4 = 1, чтобы погасить дисплей на внутреннем блоке сплита - // бит3 = 1, чтобы включить функцию "антиплесень" (после отключения как-то прогревает или просушивает теплообменник, чтобы на нем не росла плесень) - uint8_t zero4; // всегда 0x00 - uint8_t target_temp_frac2; // дробная часть целевой температуры, может быть только 0x00 и 0x05 - // при установке температуры тут 0x00, а заданная температура передается в target_temp_int_and_v_louver и target_temp_frac - // после установки сплит в информационных пакетах тут начинает показывать дробную часть - // не очень понятно, зачем так сделано -}; - -//**************************************************************************************************************************************************** -//*************************************************** ПАРАМЕТРЫ РАБОТЫ КОНДИЦИОНЕРА ****************************************************************** -//**************************************************************************************************************************************************** - -// для показаний о реальной скорости фена из большого пакета -enum ac_realFan : uint8_t { AC_REAL_FAN_OFF = 0x00, AC_REAL_FAN_MUTE = 0x01, AC_REAL_FAN_LOW = 0x02, AC_REAL_FAN_MID = 0x04, - AC_REAL_FAN_HIGH = 0x06, AC_REAL_FAN_TURBO = 0x07, AC_REAL_FAN_UNTOUCHED = 0xFF }; - -// для всех параметров ниже вариант X_UNTOUCHED = 0xFF означает, что этот параметр команды должен остаться тот, который уже установлен -// питание кондиционера -#define AC_POWER_MASK 0b00100000 -enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTOUCHED = 0xFF }; - -// режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF -#define AC_CLEAN_MASK 0b00000100 -enum ac_clean : uint8_t { AC_CLEAN_OFF = 0x00, AC_CLEAN_ON = 0x04, AC_CLEAN_UNTOUCHED = 0xFF }; - -// для включения ионизатора нужно установить второй бит в байте -// по результату этот бит останется установленным, но кондиционер еще и установит первый бит -#define AC_HEALTH_MASK 0b00000010 -enum ac_health : uint8_t { AC_HEALTH_OFF = 0x00, AC_HEALTH_ON = 0x02, AC_HEALTH_UNTOUCHED = 0xFF }; - -// Статус ионизатора. Если бит поднят, то обнаружена ошибка ключения ионизатора -#define AC_HEALTH_STATUS_MASK 0b00000001 -enum ac_health_status : uint8_t { AC_HEALTH_STATUS_OFF = 0x00, AC_HEALTH_STATUS_ON = 0x01, AC_HEALTH_STATUS_UNTOUCHED = 0xFF }; - -// целевая температура -#define AC_TEMP_TARGET_INT_PART_MASK 0b11111000 -#define AC_TEMP_TARGET_FRAC_PART_MASK 0b10000000 - -// задержка отключения кондиционера -#define AC_TIMER_MINUTES_MASK 0b00111111 -#define AC_TIMER_HOURS_MASK 0b00011111 - -// включение таймера сна -#define AC_TIMER_MASK 0b01000000 -enum ac_timer : uint8_t {AC_TIMER_OFF = 0x00, AC_TIMER_ON = 0x40, AC_TIMER_UNTOUCHED = 0xFF}; - -// основные режимы работы кондиционера -#define AC_MODE_MASK 0b11100000 -enum ac_mode : uint8_t { AC_MODE_AUTO = 0x00, AC_MODE_COOL = 0x20, AC_MODE_DRY = 0x40, AC_MODE_HEAT = 0x80, AC_MODE_FAN = 0xC0, AC_MODE_UNTOUCHED = 0xFF }; - -// Ночной режим (SLEEP). Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. -// COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. -// HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. -// Восстанавливается ли температура через 7 часов при отключении режима - не понятно. -#define AC_SLEEP_MASK 0b00000100 -enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTOUCHED = 0xFF }; - -// функция iFeel - поддерживате температуру по датчику в пульте ДУ, а не во внутреннем блоке кондиционера -#define AC_IFEEL_MASK 0b00001000 -enum ac_ifeel : uint8_t { AC_IFEEL_OFF = 0x00, AC_IFEEL_ON = 0x08, AC_IFEEL_UNTOUCHED = 0xFF }; - -// Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V -#define AC_LOUVERV_MASK 0b00000111 -enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_UPDOWN = 0x00, AC_LOUVERV_OFF = 0x07, AC_LOUVERV_UNTOUCHED = 0xFF }; - -// Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H -#define AC_LOUVERH_MASK 0b11100000 -enum ac_louver_H : uint8_t { AC_LOUVERH_SWING_LEFTRIGHT = 0x00, AC_LOUVERH_OFF = 0xE0, AC_LOUVERH_UNTOUCHED = 0xFF }; - -struct ac_louver { - ac_louver_H louver_h; - ac_louver_V louver_v; -}; - -// скорость вентилятора -#define AC_FANSPEED_MASK 0b11100000 -enum ac_fanspeed : uint8_t { AC_FANSPEED_HIGH = 0x20, AC_FANSPEED_MEDIUM = 0x40, AC_FANSPEED_LOW = 0x60, AC_FANSPEED_AUTO = 0xA0, AC_FANSPEED_UNTOUCHED = 0xFF }; - -// TURBO работает только в режимах COOL и HEAT -#define AC_FANTURBO_MASK 0b01000000 -enum ac_fanturbo : uint8_t { AC_FANTURBO_OFF = 0x00, AC_FANTURBO_ON = 0x40, AC_FANTURBO_UNTOUCHED = 0xFF }; - -// MUTE работает только в режиме FAN. В режиме COOL кондей команду принимает, но MUTE не устанавливается -#define AC_FANMUTE_MASK 0b10000000 -enum ac_fanmute : uint8_t { AC_FANMUTE_OFF = 0x00, AC_FANMUTE_ON = 0x80, AC_FANMUTE_UNTOUCHED = 0xFF }; - -// включение-выключение дисплея на корпусе внутреннего блока -#define AC_DISPLAY_MASK 0b00010000 -enum ac_display : uint8_t { AC_DISPLAY_OFF = 0x00, AC_DISPLAY_ON = 0x10, AC_DISPLAY_UNTOUCHED = 0xFF }; - -// включение-выключение функции "Антиплесень". -// По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. Уличный блок при этом гудит и тарахтит. -// Возможно, прогревается теплообменник для высыхания. Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. -#define AC_MILDEW_MASK 0b00001000 -enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_UNTOUCHED = 0xFF }; - -// маска счетчика минут прошедших с последней команды -#define AC_MIN_COUTER 0b00111111 // - -// настройка усреднения фильтра температуры. Это значение - взнос нового измерения -// в усредненные показания в процентах -#define OUTDOOR_FILTER_PESCENT 0.2 - -/** команда для кондиционера - * - * ВАЖНО! В коде используется копирование команд простым присваиванием. - * Если в структуру будут введены указатели, то копирование надо будет изменить! -*/ - -// данные структур содержат настройку, специально вынес в макрос -#define AC_COMMAND_BASE float temp_target;\ - ac_power power;\ - ac_clean clean;\ - ac_health health;\ - ac_mode mode;\ - ac_sleep sleep;\ - ac_louver louver;\ - ac_fanspeed fanSpeed;\ - ac_fanturbo fanTurbo;\ - ac_fanmute fanMute;\ - ac_display display;\ - ac_mildew mildew;\ - ac_timer timer;\ - uint8_t timer_hours;\ - uint8_t timer_minutes;\ - bool temp_target_matter - -// чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше -// из-за такого приема нужно контролировать размер копируемых данных руками -#define AC_COMMAND_BASE_SIZE 20 - -struct ac_command_t { -/* - ac_power power; - ac_clean clean; - ac_health health; // включение ионизатора - ac_mode mode; - ac_sleep sleep; - ac_louver louver; - ac_fanspeed fanSpeed; - ac_fanturbo fanTurbo; - ac_fanmute fanMute; - ac_display display; - ac_mildew mildew; - ac_timer timer; - uint8_t timer_hours; - uint8_t timer_minutes; - float temp_target; - bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную -*/ - AC_COMMAND_BASE; - ac_health_status health_status; - float temp_ambient; // внутренняя температура - int8_t temp_outdoor; // внешняя температура - int8_t temp_inbound; // температура входящая - int8_t temp_outbound; // температура исходящая - int8_t temp_strange; // непонятная температура, понаблюдаем - ac_realFan realFanSpeed; // текущая скорость вентилятора - uint8_t invertor_power; // мощность инвертора - uint8_t pressure; // предположительно давление - bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) - ac_ifeel iFeel; -}; - -// структура для сохранения данных -struct ac_save_command_t { - AC_COMMAND_BASE; -}; - -// номера сохранений пресетов -enum store_pos : uint8_t { - POS_MODE_AUTO = 0, - POS_MODE_COOL, - POS_MODE_DRY, - POS_MODE_HEAT, - POS_MODE_FAN, - POS_MODE_OFF}; - -typedef ac_command_t ac_state_t; // текущее состояние параметров кондея можно хранить в таком же формате, как и комманды - -//**************************************************************************************************************************************************** -//************************************************ КОНЕЦ ПАРАМЕТРОВ РАБОТЫ КОНДИЦИОНЕРА ************************************************************** -//**************************************************************************************************************************************************** - - -/***************************************************************************************************************************************************** - * структуры и типы для последовательности команд - ***************************************************************************************************************************************************** - * - * Последовательность команд позволяет выполнить несколько последовательных команд с контролем получаемых в ответ пакетов. - * Если требуется, в получаемых в ответ пакетах можно контролировать значение любых байт. - * Для входящего пакета байт, значение которого не проверяется, должен быть установлен в AC_SEQUENCE_ANY_BYTE. - * Контроль возможен только для входящих пакетов, исходящие отправляются "как есть". - * - * Для исходящих пакетов значения CRC могут не рассчитываться, контрольная сумма будет рассчитана автоматически. - * Для входящих пакетов значение CRC также можно не рассчитывать, установив байты CRC в AC_SEQUENCE_ANY_BYTE, - * так как контроль CRC для получаемых пакетов выполняется автоматически при получении. - * - * Для входящих пакетов в последовательности можно указать таймаут. Если таймаут равен 0, то используется значение AC_SEQUENCE_DEFAULT_TIMEOUT. - * Если в течение указанного времени подходящий пакет не будет получен, то последовательность прерывается с ошибкой. - * Пинг-пакеты в последовательности игнорируются. - * - * Пауза в последовательности задается значением timeout элемента AC_DELAY. Никакие другие параметры такого элемента можно не заполнять. - * - **/ -// максимальная длина последовательности; больше вроде бы не требовалось -#define AC_SEQUENCE_MAX_LEN 0x0F - -// в пакетах никогда не встречалось значение 0xFF (только в CRC), поэтому решено его использовать как признак не важного значение байта -//#define AC_SEQUENCE_ANY_BYTE 0xFF - -// дефолтный таймаут входящего пакета в миллисекундах -// если для входящего пакета в последовательности указан таймаут 0, то используется значение по-умолчанию -// если нужный пакет не поступил в течение указанного времени, то последовательность прерывается с ошибкой -// Brokly: пришлось увеличить -#define AC_SEQUENCE_DEFAULT_TIMEOUT 580 - -enum sequence_item_type_t : uint8_t { - AC_SIT_NONE = 0x00, // пустой элемент последовательности - AC_SIT_DELAY = 0x01, // пауза в последовательности на нужное количество миллисекунд - AC_SIT_FUNC = 0x02 // рабочий элемент последовательности -}; - -// тип пакета в массиве последовательности -// информирует о том, что за пакет лежит в поле packet элемента последовательности -enum sequence_packet_type_t : uint8_t { - AC_SPT_CLEAR = 0x00, // пустой пакет - AC_SPT_RECEIVED_PACKET = 0x01, // полученный пакет - AC_SPT_SENT_PACKET = 0x02 // отправленный пакет -}; - -/** элемент последовательности - * Поля item_type, func, timeout и cmd устанавливаются ручками и задают параметры выполнения шага последовательности. - * Поля msec, packet_type и packet заполняются движком при обработке последовательности. - **/ -struct sequence_item_t { - sequence_item_type_t item_type; // тип элемента последовательности - bool (AirCon::*func)(); // указатель на функцию, отрабатывающую шаг последовательности - uint16_t timeout; // допустимый таймаут в ожидании пакета (применим только для входящих пакетов) - ac_command_t cmd; // новое состояние сплита, нужно для передачи кондиционеру команд - //******* поля ниже заполняются функциями обработки последовательности *********** - uint32_t msec; // время старта текущего шага последовательности (для входящего пакета и паузы) - sequence_packet_type_t packet_type; // тип пакета (входящий, исходящий или вовсе не пакет) - packet_t packet; // данные пакета -}; -/*****************************************************************************************************************************************************/ - - -class AirCon : public esphome::Component, public esphome::climate::Climate { - private: - - // массив для сохранения данных глобальных персетов - ac_save_command_t global_presets[POS_MODE_OFF+1]; - #if defined(ESP32) - // тут будем хранить данные глобальных пресетов во флеше - // ВНИМАНИЕ на данный момент 22.05.22 ESPHOME 20022.5.0 имеет ошибку - // траблтикет: https://github.com/esphome/issues/issues/3298 - // из-за этого сохранение в энергонезависимую память не работает !!! - ESPPreferenceObject storage = global_preferences->make_preference(this->get_object_id_hash(), true); - #endif - // настройка-ключ, для включения сохранения - восстановления настроек каждого - // режима работы в отдельности, то есть каждый режим работы имеет свои настройки - // температуры, шторок, скорости вентилятора, пресетов - bool _store_settings = false; - // флаги для сохранения пресетов - bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено - - // время последнего запроса статуса у кондея - uint32_t _dataMillis; - // периодичность обновления статуса кондея, по дефолту AC_STATES_REQUEST_INTERVAL - uint32_t _update_period = Constants::AC_STATES_REQUEST_INTERVAL; - - // надо ли отображать текущий режим работы внешнего блока - // в режиме нагрева, например, кондиционер может как греть воздух, так и работать в режиме вентилятора, если целевая темпреатура достигнута - // по дефолту показываем - bool _show_action = true; - - // как отрабатывается включание-выключение дисплея. - // если тут false, то 1 в соответствующем бите включает дисплей, а 0 выключает. - // если тут true, то 1 потушит дисплей, а 0 включит. - bool _display_inverted = false; - - // флаг типа кондиционера инвертор - true, ON/OFF - false, начальная установка false - // в таком режиме точность и скорость определения реального состояния системы для инвертора, - // будет работать, но будет ниже, переменная устанавливается при первом получении большого пакета; - // если эта переменная установлена, то режим работы не инверторного кондиционера будет распознаваться - // как "в простое" (IDLE) - bool _is_invertor = false; - - // поддерживаемые кондиционером опции - std::set _supported_modes{}; - std::set _supported_swing_modes{}; - std::set _supported_presets{}; - std::set _supported_custom_presets{}; - std::set _supported_custom_fan_modes{}; - - // состояние конечного автомата - acsm_state _ac_state = ACSM_IDLE; - - // текущее состояние задаваемых пользователем параметров системы - ac_state_t _current_ac_state; - - // флаг подключения к UART - bool _hw_initialized = false; - // указатель на UART, по которому общаемся с кондиционером - esphome::uart::UARTComponent *_ac_serial; - - // UART wrappers: peek - int peek() { - uint8_t data; - if (!_ac_serial->peek_byte(&data)) return -1; - return data; - } - - // UART wrappers: read - int read() { - uint8_t data; - if (!_ac_serial->read_byte(&data)) return -1; - return data; - } - - // флаг обмена пакетами с кондиционером (если проходят пинги, значит есть коннект) - bool _has_connection = false; - - // входящий и исходящий пакеты - packet_t _inPacket; - packet_t _outPacket; - - // пакет для тестирования всякой фигни - packet_t _outTestPacket; - - // последовательность пакетов текущий шаг в последовательности - sequence_item_t _sequence[AC_SEQUENCE_MAX_LEN]; - uint8_t _sequence_current_step; - - // флаг успешного выполнения стартовой последовательности команд - bool _startupSequenceComlete = false; - - // очистка последовательности команд - void _clearSequence(){ - for (uint8_t i = 0; i < AC_SEQUENCE_MAX_LEN; i++) { - _sequence[i].item_type = AC_SIT_NONE; - _sequence[i].func = nullptr; - _sequence[i].timeout = 0; - _sequence[i].msec = 0; - _sequence[i].packet_type = AC_SPT_CLEAR; - _clearPacket(&_sequence[i].packet); - _clearCommand(&_sequence[i].cmd); - } - _sequence_current_step = 0; - } - - // проверяет, есть ли свободные шаги в последовательности команд - bool _hasFreeSequenceStep(){ - return (_getNextFreeSequenceStep() < AC_SEQUENCE_MAX_LEN); - } - - // возвращает индекс первого пустого шага последовательности команд - uint8_t _getNextFreeSequenceStep(){ - for (size_t i = 0; i < AC_SEQUENCE_MAX_LEN; i++) { - if (_sequence[i].item_type == AC_SIT_NONE){ - return i; - } - } - // если свободных слотов нет, то возвращаем значение за пределом диапазона - return AC_SEQUENCE_MAX_LEN; - } - - // возвращает количество свободных шагов в последовательности - uint8_t _getFreeSequenceSpace() { - return (AC_SEQUENCE_MAX_LEN - _getNextFreeSequenceStep()); - } - - // добавляет шаг в последовательность команд - // возвращает false, если не нашлось места для шага - bool _addSequenceStep(const sequence_item_type_t item_type, bool (AirCon::*func)() = nullptr, ac_command_t *cmd = nullptr, uint16_t timeout = AC_SEQUENCE_DEFAULT_TIMEOUT){ - if (!_hasFreeSequenceStep()) return false; // если места нет, то уходим - if (item_type == AC_SIT_NONE) return false; // глупость какая-то, уходим - if ((item_type == AC_SIT_FUNC) && (func == nullptr)) return false; // должна быть передана функция для такого типа шага - if ((item_type != AC_SIT_DELAY) && (item_type != AC_SIT_FUNC)){ - // какой-то неизвестный тип - _debugMsg(F("_addSequenceStep: unknown sequence item type = %u"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__, item_type); - return false; - } - - uint8_t step = _getNextFreeSequenceStep(); - - _sequence[step].item_type = item_type; - - // если задержка нулевая, то присваиваем дефолтную задержку - if (timeout == 0) timeout = AC_SEQUENCE_DEFAULT_TIMEOUT; - _sequence[step].timeout = timeout; - - _sequence[step].func = func; - if (cmd != nullptr) _sequence[step].cmd = *cmd; // так как в структуре команды только простые типы, то можно вот так присваивать - - return true; - } - - // добавляет в последовательность шаг с задержкой - bool _addSequenceDelayStep(uint16_t timeout){ - return this->_addSequenceStep(AC_SIT_DELAY, nullptr, nullptr, timeout); - } - - // добавляет в последовательность функциональный шаг - bool _addSequenceFuncStep(bool (AirCon::*func)(), ac_command_t *cmd = nullptr, uint16_t timeout = AC_SEQUENCE_DEFAULT_TIMEOUT){ - return this->_addSequenceStep(AC_SIT_FUNC, func, cmd, timeout); - } - - // выполняет всю логику очередного шага последовательности команд - void _doSequence(){ - if (!hasSequence()) return; - - // если шаг уже максимальный из возможных - if (_sequence_current_step >= AC_SEQUENCE_MAX_LEN) { - // значит последовательность закончилась, надо её очистить - // при очистке последовательности будет и _sequence_current_step обнулён - _debugMsg(F("Sequence [step %u]: maximum step reached"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _clearSequence(); - return; - } - - // смотрим тип текущего элемента в последовательности - switch (_sequence[_sequence_current_step].item_type) { - case AC_SIT_FUNC: { - // если указатель на функцию пустой, то прерываем последовательность - if (_sequence[_sequence_current_step].func == nullptr) { - _debugMsg(F("Sequence [step %u]: function pointer is NULL, sequence broken"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _clearSequence(); - return; - } - - // сохраняем время начала паузы - if (_sequence[_sequence_current_step].msec == 0) { - _sequence[_sequence_current_step].msec = millis(); - _debugMsg(F("Sequence [step %u]: step started"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - } - - // если таймаут не указан, берем значение по-умолчанию - if (_sequence[_sequence_current_step].timeout == 0 ) _sequence[_sequence_current_step].timeout = AC_SEQUENCE_DEFAULT_TIMEOUT; - - // если время вышло, то отчитываемся в лог и очищаем последовательность - if (millis() - _sequence[_sequence_current_step].msec >= _sequence[_sequence_current_step].timeout) { - _debugMsg(F("Sequence [step %u]: step timed out (it took %u ms instead of %u ms)"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step, millis() - _sequence[_sequence_current_step].msec, _sequence[_sequence_current_step].timeout); - _clearSequence(); - return; - } - - // можно вызывать функцию - // она самомтоятельно загружает отправляемые/полученные пакеты в packet последовательности - // а также самостоятельно увеличивает счетчик шагов последовательности _sequence_current_step - // единственное исключение - таймауты - if (!(this->*_sequence[_sequence_current_step].func)()) { - _debugMsg(F("Sequence [step %u]: error was occur in step function"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step, millis() - _sequence[_sequence_current_step].msec); - _clearSequence(); - return; - } - break; - } - - case AC_SIT_DELAY: { // это пауза в последовательности - // пауза задается параметром timeout элемента последовательности - // начало паузы сохраняется в параметре msec - - // сохраняем время начала паузы - if (_sequence[_sequence_current_step].msec == 0) { - _sequence[_sequence_current_step].msec = millis(); - _debugMsg(F("Sequence [step %u]: begin delay (%u ms)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step, _sequence[_sequence_current_step].timeout); - } - - // если время вышло, то переходим на следующий шаг - if (millis() - _sequence[_sequence_current_step].msec >= _sequence[_sequence_current_step].timeout) { - _debugMsg(F("Sequence [step %u]: delay culminated (plan = %u ms, fact = %u ms)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step, _sequence[_sequence_current_step].timeout, millis() - _sequence[_sequence_current_step].msec); - _sequence_current_step++; - } - break; - } - - case AC_SIT_NONE: // шаги закончились - default: // или какой-то мусор в последовательности - // надо очистить последовательность и уходить - _debugMsg(F("Sequence [step %u]: sequence complete"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _clearSequence(); - break; - } - } - - // заполняет структуру команды нейтральными значениями - void _clearCommand(ac_command_t * cmd){ - cmd->clean = AC_CLEAN_UNTOUCHED; - cmd->display = AC_DISPLAY_UNTOUCHED; - cmd->fanMute = AC_FANMUTE_UNTOUCHED; - cmd->fanSpeed = AC_FANSPEED_UNTOUCHED; - cmd->fanTurbo = AC_FANTURBO_UNTOUCHED; - cmd->health = AC_HEALTH_UNTOUCHED; - cmd->health_status = AC_HEALTH_STATUS_UNTOUCHED; - cmd->iFeel = AC_IFEEL_UNTOUCHED; - cmd->louver.louver_h = AC_LOUVERH_UNTOUCHED; - cmd->louver.louver_v = AC_LOUVERV_UNTOUCHED; - cmd->mildew = AC_MILDEW_UNTOUCHED; - cmd->mode = AC_MODE_UNTOUCHED; - cmd->power = AC_POWER_UNTOUCHED; - cmd->sleep = AC_SLEEP_UNTOUCHED; - cmd->timer = AC_TIMER_UNTOUCHED; - cmd->timer_hours = 0; - cmd->timer_minutes = 0; - cmd->temp_target = 0; - cmd->temp_target_matter = false; - cmd->temp_ambient = 0; - cmd->temp_outdoor = 0; - cmd->temp_inbound = 0; - cmd->temp_outbound = 0; - cmd->temp_strange = 0; - cmd->realFanSpeed = AC_REAL_FAN_UNTOUCHED; - }; - - // очистка буфера размером AC_BUFFER_SIZE - void _clearBuffer(uint8_t * buf){ - memset(buf, 0, AC_BUFFER_SIZE); - } - - // очистка структуры пакета по указателю - void _clearPacket(packet_t * pckt){ - if (pckt == nullptr) { - _debugMsg(F("Clear packet error: pointer is NULL!"), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return; - } - pckt->crc = nullptr; - pckt->header = (packet_header_t *)(pckt->data); // заголовок же всегда стартует с начала пакета - pckt->msec = 0; - pckt->bytesLoaded = 0; - pckt->body = nullptr; - _clearBuffer(pckt->data); - } - - // очистка входящего пакета - void _clearInPacket(){ - _clearPacket(&_inPacket); - } - - // очистка исходящего пакета - void _clearOutPacket(){ - _clearPacket(&_outPacket); - _outPacket.header->start_byte = AC_PACKET_START_BYTE; // для исходящего сразу ставим стартовый байт - _outPacket.header->wifi = AC_PACKET_ANSWER; // для исходящего пакета сразу ставим признак ответа - } - - // копирует пакет из одной структуры в другую с корректным переносом указателей на заголовки и т.п. - bool _copyPacket(packet_t *dest, packet_t *src){ - if (dest == nullptr) return false; - if (src == nullptr) return false; - - dest->msec = src->msec; - dest->bytesLoaded = src->bytesLoaded; - memcpy(dest->data, src->data, AC_BUFFER_SIZE); - dest->header = (packet_header_t *)&dest->data; - if (dest->header->body_length > 0) dest->body = &dest->data[AC_HEADER_SIZE]; - dest->crc = (packet_crc_t *)&dest->data[AC_HEADER_SIZE + dest->header->body_length]; - - return true; - } - - // устанавливает состояние конечного автомата - // можно и напрямую устанавливать переменную, но для целей отладки лучше так - void _setStateMachineState(acsm_state state = ACSM_IDLE){ - if (_ac_state == state) return; // состояние не меняется - - _ac_state = state; - - switch (state) { - case ACSM_IDLE: - _debugMsg(F("State changed to ACSM_IDLE."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - case ACSM_RECEIVING_PACKET: - _debugMsg(F("State changed to ACSM_RECEIVING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - case ACSM_PARSING_PACKET: - _debugMsg(F("State changed to ACSM_PARSING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - case ACSM_SENDING_PACKET: - _debugMsg(F("State changed to ACSM_SENDING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - default: - _debugMsg(F("State changed to ACSM_IDLE by default. Given state is %02X."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, state); - _ac_state = ACSM_IDLE; - break; - } - } - - // состояние конечного автомата: IDLE - void _doIdleState(){ - // вначале нужно выполнить очередной шаг последовательности команд - _doSequence(); - - // Если нет входящих данных, значит можно отправить исходящий пакет, если он есть - if (_ac_serial->available() == 0) { - // если есть пакет на отправку, то надо отправлять - // вначале думал, что сейчас отправка пакетов тут не нужна, т.к. состояние ACSM_SENDING_PACKET устанавливается сразу в парсере пакетов - // но потом понял, что у нас пакеты уходят не только когда надо отвечать, но и мы можем быть инициаторами - // поэтому вызов отправки тут пригодится - if (_outPacket.msec > 0) _setStateMachineState(ACSM_SENDING_PACKET); - // иначе просто выходим - return; - }; - - if (this->peek() == AC_PACKET_START_BYTE) { - // если во входящий пакет что-то уже загружено, значит это какие-то ошибочные данные или неизвестные пакеты - // надо эту инфу вывалить в лог - if (_inPacket.bytesLoaded > 0){ - _debugMsg(F("Start byte received but there are some unparsed bytes in the buffer:"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - } - _clearInPacket(); - _inPacket.msec = millis(); - _setStateMachineState(ACSM_RECEIVING_PACKET); - //******************************************** экспериментальная секция ************************************************************* - // пробуем сократить время ответа с помощью прямых вызовов обработчиков, а не через состояние IDLE - //_doReceivingPacketState(); - // получилось всё те же 123 мсек. Только изредка падает до 109 мсек. Странно. - // логический анализатор показал примерно то же время от начала запроса до окончания ответа. - // запрос имеет длительность 18 мсек (лог.анализатор говорит 22,5 мсек). - // ответ имеет длительность 41 мсек по лог.анализатору. - // длительность паузы между запросом и ответом порядка 60 мсек. - // Скорее всего за один вызов _doReceivingPacketState не удается загрузить весь пакет (на момент вызова не все байы поступили в буфер UART) - // и поэтому программа отдает управление ESPHome для выполнения своих задач - // Стоит ли переделать код наоборот для непрерывного выполнения всё время, пока ожидается посылка - не знаю. Может быть такой риалтайм и не нужен. - //*********************************************************************************************************************************** - - } else { - while (_ac_serial->available() > 0) - { - // если наткнулись на старт пакета, то выходим из while - // если какие-то данные были загружены в буфер, то они будут выгружены в лог при загрузке нового пакета - if (this->peek() == AC_PACKET_START_BYTE) break; - - // читаем байт в буфер входящего пакета - _inPacket.data[_inPacket.bytesLoaded] = this->read(); - _inPacket.bytesLoaded++; - - // если буфер уже полон, надо его вывалить в лог и очистить - if (_inPacket.bytesLoaded >= AC_BUFFER_SIZE){ - _debugMsg(F("Some unparsed data on the bus:"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _clearInPacket(); - } - } - } - }; - - // состояние конечного автомата: ACSM_RECEIVING_PACKET - void _doReceivingPacketState(){ - while (_ac_serial-> available() > 0) { - // если в буфере пакета данных уже под завязку, то надо сообщить о проблеме и выйти - if (_inPacket.bytesLoaded >= AC_BUFFER_SIZE) { - _debugMsg(F("Receiver: packet buffer overflow!"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - return; - } - - _inPacket.data[_inPacket.bytesLoaded] = this->read(); - _inPacket.bytesLoaded++; - - // данных достаточно для заголовка - if (_inPacket.bytesLoaded == AC_HEADER_SIZE) { - // указатель заголовка установлен еще при обнулении пакета, его можно не трогать - //_inPacket.header = (packet_header_t *)(_inPacket.data); - - // уже знаем размер пакета и можем установить указатели на тело пакета и CRC - _inPacket.crc = (packet_crc_t *)&(_inPacket.data[AC_HEADER_SIZE + _inPacket.header->body_length]); - if (_inPacket.header->body_length > 0) _inPacket.body = &(_inPacket.data[AC_HEADER_SIZE]); - - _debugMsg(F("Header loaded: timestamp = %010u, start byte = %02X, packet type = %02X, body size = %02X"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.msec, _inPacket.header->start_byte, _inPacket.header->packet_type, _inPacket.header->body_length); - } - - // если все байты пакета загружены, надо его распарсить - // максимальный по размеру пакет будет упираться в размер буфера. если такой пакет здесь не уйдет на парсинг, - // то на следующей итерации будет ошибка о переполнении буфера, которая в начале цикла while - if (_inPacket.bytesLoaded == AC_HEADER_SIZE + _inPacket.header->body_length + 2) { - _debugMsg(F("Packet loaded: timestamp = %010u, start byte = %02X, packet type = %02X, body size = %02X, crc = [%02X, %02X]."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.msec, _inPacket.header->start_byte, _inPacket.header->packet_type, _inPacket.header->body_length, _inPacket.crc->crc[0], _inPacket.crc->crc[1]); - _debugMsg(F("Loaded %02u bytes for a %u ms."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.bytesLoaded, (millis() - _inPacket.msec)); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - _setStateMachineState(ACSM_PARSING_PACKET); - return; - } - } - - // если пакет не загружен, а время вышло, то надо вернуться в IDLE - if (millis() - _inPacket.msec >= AC_PACKET_TIMEOUT) { - _debugMsg(F("Receiver: packet timed out!"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - return; - } - }; - - // состояние конечного автомата: ACSM_PARSING_PACKET - void _doParsingPacket(){ - if (!_checkCRC(&_inPacket)) { - _debugMsg(F("Parser: packet CRC fail!"), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_ERROR, __LINE__); - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - return; - } - - bool stateChangedFlag = false; // флаг, показывающий, изменилось ли состояние кондиционера - uint8_t stateByte = 0; // переменная для временного сохранения текущих параметров сплита для проверки их изменения - float stateFloat = 0.0; // переменная для временного сохранения текущих параметров сплита для проверки их изменения - - // вначале выводим полученный пакет в лог, чтобы он шел до информации об ответах и т.п. - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - - // разбираем тип пакета - switch (_inPacket.header->packet_type) { - case AC_PTYPE_PING: { // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает - - if (_inPacket.header->body_length != 0 ) { // у входящего ping-пакета тело должно отсутствовать - // если тело есть, то жалуемся в лог - _debugMsg(F("Parser: ping packet should not to have body. Received one has body length %02X."), ESPHOME_LOG_LEVEL_WARN, __LINE__, _inPacket.header->body_length); - // очищаем пакет - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - break; - } - - _debugMsg(F("Parser: ping packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - // поднимаем флаг, что есть коннект с кондиционером - _has_connection = true; - - // надо отправлять ответ на пинг - _clearOutPacket(); - _outPacket.msec = millis(); - _outPacket.header->packet_type = AC_PTYPE_PING; - _outPacket.header->ping_answer_01 = 0x01; // только в ответе на пинг этот байт равен 0x01; что означает не ясно - _outPacket.header->body_length = 8; // в ответе на пинг у нас тело 8 байт - _outPacket.body = &(_outPacket.data[AC_HEADER_SIZE]); - - // заполняем тело пакета - packet_ping_answer_body_t * ping_body; - ping_body = (packet_ping_answer_body_t *) (_outPacket.body); - ping_body->byte_1C = 0x1C; - ping_body->byte_27 = 0x27; - - // расчет контрольной суммы и прописывание её в пакет - _outPacket.crc = (packet_crc_t *)&(_outPacket.data[AC_HEADER_SIZE + _outPacket.header->body_length]); - _setCRC16(&_outPacket); - _outPacket.bytesLoaded = AC_HEADER_SIZE + _outPacket.header->body_length + 2; - - _debugMsg(F("Parser: generated ping answer. Waiting for sending."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // до отправки пинг-ответа проверяем, не выполнялась ли стартовая последовательность команд - // по задумке она выполняется после подключения к кондиционеру после ответа на первый пинг - // нужна для максимально быстрого определния текущих параметров кондиционера - if (!_startupSequenceComlete){ - _startupSequenceComlete = startupSequence(); - } - - // изначально предполагал, что передачу пакета на отправку выполнит обработчик IDLE, но показалось, что слишком долго - // логика отправки через IDLE в том, что получение запросов может быть важнее отправки ответов и IDLE позволяет реализовать такой приоритет - // но потом решил всё же напрямую отправлять в отправку - // в этом случае пинг-ответ заканчивает отправку спустя 144 мсек после стартового байта пинг-запроса - //_setStateMachineState(ACSM_IDLE); - _setStateMachineState(ACSM_SENDING_PACKET); - // решил провести эксперимент - // получилось от начала запроса до отправки ответа порядка 165 мсек., если отправка идет не сразу, а через состояние IDLE - // Если сразу отсюда отправляться в обработчик отправки, то время сокращается до 131 мсек. Основные потери идут до входа в парсер пакетов - break; - } - - case AC_PTYPE_CMD: { // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита - // сплит такие команды отправлять не должен, поэтому жалуемся в лог - _debugMsg(F("Parser: packet type=0x06 received from HVAC. This isn't expected."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - // очищаем пакет - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - break; - } - - case AC_PTYPE_INFO: { // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля - // смотрим тип поступившего пакета по второму байту тела - _debugMsg(F("Parser: status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - switch (_inPacket.body[1]) { - case AC_CMD_STATUS_SMALL: { // маленький пакет статуса кондиционера - _debugMsg(F("Parser: status packet type = small"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - stateChangedFlag = false; - - // будем обращаться к телу пакета через указатель на структуру - packet_small_info_body_t * small_info_body; - small_info_body = (packet_small_info_body_t *) (_inPacket.body); - - // в малом пакете передается большое количество установленных пользователем параметров работы - stateFloat = 8 + (small_info_body->target_temp_int_and_v_louver >> 3) + 0.5*(float)(small_info_body->target_temp_frac >> 7); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_target != stateFloat); - _current_ac_state.temp_target = stateFloat; - _current_ac_state.temp_target_matter = true; - - stateByte = small_info_body->target_temp_int_and_v_louver & AC_LOUVERV_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.louver.louver_v != (ac_louver_V)stateByte); - _current_ac_state.louver.louver_v = (ac_louver_V)stateByte; - - stateByte = small_info_body->h_louver & AC_LOUVERH_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.louver.louver_h != (ac_louver_H)stateByte); - _current_ac_state.louver.louver_h = (ac_louver_H)stateByte; - - stateByte = small_info_body->fan_speed & AC_FANSPEED_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.fanSpeed != (ac_fanspeed)stateByte); - _current_ac_state.fanSpeed = (ac_fanspeed)stateByte; - - stateByte = small_info_body->fan_turbo_and_mute & AC_FANTURBO_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.fanTurbo != (ac_fanturbo)stateByte); - _current_ac_state.fanTurbo = (ac_fanturbo)stateByte; - - stateByte = small_info_body->fan_turbo_and_mute & AC_FANMUTE_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.fanMute != (ac_fanmute)stateByte); - _current_ac_state.fanMute = (ac_fanmute)stateByte; - - stateByte = small_info_body->mode & AC_MODE_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.mode != (ac_mode)stateByte); - _current_ac_state.mode = (ac_mode)stateByte; - - stateByte = small_info_body->mode & AC_SLEEP_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.sleep != (ac_sleep)stateByte); - _current_ac_state.sleep = (ac_sleep)stateByte; - - stateByte = small_info_body->mode & AC_IFEEL_MASK; - _current_ac_state.iFeel = (ac_ifeel)stateByte; - - stateByte = small_info_body->status & AC_POWER_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.power != (ac_power)stateByte); - _current_ac_state.power = (ac_power)stateByte; - - stateByte = small_info_body->status & AC_HEALTH_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.health != (ac_health)stateByte); - _current_ac_state.health = (ac_health)stateByte; - - stateByte = small_info_body->status & AC_HEALTH_STATUS_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.health_status != (ac_health_status)stateByte); - _current_ac_state.health_status = (ac_health_status)stateByte; - - stateByte = small_info_body->status & AC_CLEAN_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.clean != (ac_clean)stateByte); - _current_ac_state.clean = (ac_clean)stateByte; - - stateByte = small_info_body->display_and_mildew & AC_DISPLAY_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.display != (ac_display)stateByte); - _current_ac_state.display = (ac_display)stateByte; - - stateByte = small_info_body->display_and_mildew & AC_MILDEW_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.mildew != (ac_mildew)stateByte); - _current_ac_state.mildew = (ac_mildew)stateByte; - - // уведомляем об изменении статуса сплита - if (stateChangedFlag) stateChanged(); - - break; - } - - case AC_CMD_STATUS_BIG: // большой пакет статуса кондиционера - case AC_CMD_STATUS_PERIODIC: { // раз в 10 минут рассылается сплитом, структура аналогична большому пакету статуса - // TODO: вроде как AC_CMD_STATUS_PERIODIC могут быть и с другими кодами; пока что другие будут игнорироваться; если это будет критично, надо будет поправить - _debugMsg(F("Parser: status packet type = big or periodic"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - stateChangedFlag = false; - - // будем обращаться к телу пакета через указатель на структуру - packet_big_info_body_t * big_info_body; - big_info_body = (packet_big_info_body_t *) (_inPacket.body); - - // тип кондея (инвертор или старт стоп) - _is_invertor = big_info_body->is_invertor; - - // температура воздуха в помещении по версии сплит-системы - stateFloat = big_info_body->ambient_temperature_int - 0x20 + (float)(big_info_body->ambient_temperature_frac & 0x0f) / 10.0; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_ambient != stateFloat); - _current_ac_state.temp_ambient = stateFloat; - - // некая температура из наружного блока, скорее всего температура испарителя - // temp = big_info_body->outdoor_temperature - 0x20; - // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах - { - const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; - const float antkoef = 1.0 - koef; - static float temp = _current_ac_state.temp_outdoor; - temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); - _current_ac_state.temp_outdoor = temp; - } - - // температура входящей магистрали - stateFloat = big_info_body->in_temperature_int - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_inbound != stateFloat); - _current_ac_state.temp_inbound = stateFloat; - - // температура исходящей магистрали - stateFloat = big_info_body->out_temperature_int - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outbound != stateFloat); - _current_ac_state.temp_outbound = stateFloat; - - // температура непонятная температура - stateFloat = big_info_body->strange_temperature_int - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_strange != stateFloat); - _current_ac_state.temp_strange = stateFloat; - - // реальная скорость проперлера - stateFloat = big_info_body->realFanSpeed; - stateChangedFlag = stateChangedFlag || (_current_ac_state.realFanSpeed != (ac_realFan)stateFloat); - _current_ac_state.realFanSpeed = (ac_realFan)stateFloat; - - // мощность инвертора - stateFloat = big_info_body->invertor_power; - stateChangedFlag = stateChangedFlag || (_current_ac_state.invertor_power != stateFloat); - _current_ac_state.invertor_power = stateFloat; - - // режим разморозки - bool temp = (big_info_body->needDefrost && big_info_body->defrostMode); - stateChangedFlag = stateChangedFlag || (_current_ac_state.defrost != temp); - _current_ac_state.defrost = temp; - - // уведомляем об изменении статуса сплита - if (stateChangedFlag) { - stateChanged(); - } - break; - } - - case AC_CMD_SET_PARAMS: { // такой статусный пакет присылается кондиционером в ответ на команду установки параметров - // в теле пакета нет ничего примечательного - // в байтах 2 и 3 тела похоже передается CRC пакета поступившей команды, на которую сплит отвечает - // но я решил этот момент тут не проверять и не контролировать. - // корректную установку параметров можно определить, запросив статус кондиционера сразу после получения этой команды кондея - // в настоящий момент проверка сделана в механизме sequences - // TODO: если доводить до идеала, то проверку байтов 2 и 3 можно сделать и тут - break; - } - - default: - _debugMsg(F("Parser: status packet type = unknown (%02X)"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _inPacket.body[1]); - break; - } - _setStateMachineState(ACSM_IDLE); - break; - } - - case AC_PTYPE_INIT: // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. - case AC_PTYPE_UNKN: // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует - default: - // игнорируем. Для нашего случая эти пакеты не важны - _setStateMachineState(ACSM_IDLE); - break; - } - - // если есть последовательность команд, то надо отработать проверку последовательности - if (hasSequence()) _doSequence(); - - // после разбора входящего пакета его надо очистить - _clearInPacket(); - } - - // состояние конечного автомата: ACSM_SENDING_PACKET - void _doSendingPacketState(){ - // если нет исходящего пакета, то выходим - if ((_outPacket.msec == 0) || (_outPacket.crc == nullptr) || (_outPacket.bytesLoaded == 0)) { - _debugMsg(F("Sender: no packet to send."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - _setStateMachineState(ACSM_IDLE); - return; - } - - _debugMsg(F("Sender: sending packet."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - _ac_serial->write_array(_outPacket.data, _outPacket.bytesLoaded); - _ac_serial->flush(); - - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _debugMsg(F("Sender: %u bytes sent (%u ms)."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _outPacket.bytesLoaded, millis()-_outPacket.msec); - _clearOutPacket(); - - _setStateMachineState(ACSM_IDLE); - }; - - /** вывод отладочной информации в лог - * - * dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе. - * msg - сообщение, выводимое в лог - * line - строка, на которой произошел вызов (удобно при отладке) - */ - void _debugMsg(const String &msg, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0, ... ){ - if (dbgLevel < ESPHOME_LOG_LEVEL_NONE) dbgLevel = ESPHOME_LOG_LEVEL_NONE; - if (dbgLevel > ESPHOME_LOG_LEVEL_VERY_VERBOSE) dbgLevel = ESPHOME_LOG_LEVEL_VERY_VERBOSE; - - if (line == 0) line = __LINE__; // если строка не передана, берем текущую строку - - va_list vl; - va_start(vl, line); - esp_log_vprintf_(dbgLevel, Constants::TAG, line, msg.c_str(), vl); // так тоже вроде через Ж*, только ее не видно :) - va_end(vl); - } - - /** выводим данные пакета в лог для отладки - * - * dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе. - * packet - указатель на пакет для вывода; - * если указатель на crc равен nullptr или первый байт в буфере не AC_PACKET_START_BYTE, то считаем, что передан битый пакет - * или не пакет вовсе. Для такого выводим только массив байт. - * Для нормального пакета данные выводятся с форматированием. - * line - строка, на которой произошел вызов (удобно при отладке) - **/ - void _debugPrintPacket(packet_t * packet, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0){ - // определяем, полноценный ли пакет нам передан - bool notAPacket = false; - // указатель заголовка всегда установден на начало буфера - notAPacket = notAPacket || (packet->crc == nullptr); - notAPacket = notAPacket || (packet->data[0] != AC_PACKET_START_BYTE); - - String st = ""; - char textBuf[11]; - - // заполняем время получения пакета - memset(textBuf, 0, 11); - sprintf(textBuf, "%010u", packet->msec); - st = st + textBuf + ": "; - - // формируем преамбулы - if (packet == &_inPacket) { - st += "[<=] "; // преамбула входящего пакета - } else if (packet == &_outPacket) { - st += "[=>] "; // преамбула исходящего пакета - } else { - st += "[--] "; // преамбула для "непакета" - } - - // формируем данные - #ifdef HOLMS - dbgLevel = ESPHOME_LOG_LEVEL_ERROR; - if(packet->header->body_length > HOLMS){ - for (int i=0; ibytesLoaded; i++){ - sprintf(textBuf, "%03d;", packet->data[i]); - st += textBuf; - } - if (line == 0) line = __LINE__; - _debugMsg(st, dbgLevel, line); - } - #else - for (int i=0; ibytesLoaded; i++){ - // для нормальных пакетов надо заключить заголовок в [] - if ((!notAPacket) && (i == 0)) st += "["; - // для нормальных пакетов надо заключить CRC в [] - if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE)) st += "["; - - //memset(textBuf, 0, 11); - sprintf(textBuf, "%02X", packet->data[i]); - st += textBuf; - - // для нормальных пакетов надо заключить заголовок в [] - if ((!notAPacket) && (i == AC_HEADER_SIZE-1)) st += "]"; - // для нормальных пакетов надо заключить CRC в [] - if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE+2-1)) st += "]"; - st += " "; - } - if (line == 0) line = __LINE__; - _debugMsg(st, dbgLevel, line); - #endif - - } - - /** расчет CRC16 для блока данных data длиной len - * data - данные для расчета CRC16, указатель на массив байт - * len - длина блока данных для расчета, в байтах - * - * возвращаем uint16_t CRC16 - **/ - uint16_t _CRC16(uint8_t *data, uint8_t len){ - uint32_t crc = 0; - - // выделяем буфер для расчета CRC и копируем в него переданные данные - // это нужно для того, чтобы в случае нечетной длины данных можно было дополнить тело пакета - // одним нулевым байтом и не попортить загруженный пакет (ведь в загруженном сразу за телом идёт CRC) - uint8_t _crcBuffer[AC_BUFFER_SIZE]; - memset(_crcBuffer, 0, AC_BUFFER_SIZE); - memcpy(_crcBuffer, data, len); - - // если длина данных нечетная, то надо сделать четной, дополнив данные в конце нулевым байтом - // но так как выше буфер заполняли нулями, то отдельно тут присваивать 0x00 нет смысла - if ((len%2) == 1) len++; - - // рассчитываем CRC16 - uint32_t word = 0; - for (uint8_t i=0; i < len; i+=2){ - word = (_crcBuffer[i] << 8) + _crcBuffer[i+1]; - crc += word; - } - crc = (crc >> 16) + (crc & 0xFFFF); - crc = ~ crc; - - return crc & 0xFFFF; - } - - // расчитываем CRC16 и заполняем эти данные в структуре пакета - void _setCRC16(packet_t* pack = nullptr){ - // если пакет не указан, то устанавливаем CRC для исходящего пакета - if (pack == nullptr) pack = &_outPacket; - - packet_crc_t crc; - crc.crc16 = _CRC16(pack->data, AC_HEADER_SIZE + pack->header->body_length); - - // если забыли указатель на crc установить, то устанавливаем - if (pack->crc == nullptr) pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - - pack->crc->crc[0] = crc.crc[1]; - pack->crc->crc[1] = crc.crc[0]; - return; - } - - // проверяет CRC пакета по указателю - bool _checkCRC(packet_t* pack = nullptr){ - // если пакет не указан, то проверяем входящий - if (pack == nullptr) pack = &_inPacket; - if (pack->bytesLoaded < AC_HEADER_SIZE) { - _debugMsg(F("CRC check: incoming packet size error."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - // если забыли указатель на crc установить, то устанавливаем - if (pack->crc == nullptr) pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - - packet_crc_t crc; - crc.crc16 = _CRC16(pack->data, AC_HEADER_SIZE + pack->header->body_length); - - return ((pack->crc->crc[0] == crc.crc[1]) && (pack->crc->crc[1] == crc.crc[0])); - } - - // заполняет пакет по ссылке командой запроса маленького пакета статуса - void _fillStatusSmall(packet_t * pack = nullptr){ - // по умолчанию заполняем исходящий пакет - if (pack == nullptr) pack = &_outPacket; - - // присваиваем параметры пакета - pack->msec = millis(); - pack->header->start_byte = AC_PACKET_START_BYTE; - pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа - pack->header->packet_type = AC_PTYPE_CMD; - pack->header->body_length = 2; // тело команды 2 байта - pack->body = &(pack->data[AC_HEADER_SIZE]); - pack->body[0] = AC_CMD_STATUS_SMALL; - pack->body[1] = 0x01; // он всегда 0x01 - pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; - - // рассчитываем и записываем в пакет CRC - pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - _setCRC16(pack); - } - - // заполняет пакет по ссылке командой запроса большого пакета статуса - void _fillStatusBig(packet_t * pack = nullptr){ - // по умолчанию заполняем исходящий пакет - if (pack == nullptr) pack = &_outPacket; - - // присваиваем параметры пакета - pack->msec = millis(); - pack->header->start_byte = AC_PACKET_START_BYTE; - pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа - pack->header->packet_type = AC_PTYPE_CMD; - pack->header->body_length = 2; // тело команды 2 байта - pack->body = &(pack->data[AC_HEADER_SIZE]); - pack->body[0] = AC_CMD_STATUS_BIG; - pack->body[1] = 0x01; // он всегда 0x01 - pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; - - // рассчитываем и записываем в пакет CRC - pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - _setCRC16(pack); - } - - /** заполняет пакет по ссылке командой установки параметров - * - * указатель на пакет может отсутствовать, тогда заполняется _outPacket - * указатель на команду также может отсутствовать, тогда используется текущее состояние из _current_ac_state - * все *__UNTOUCHED параметры заполняются из _current_ac_state - **/ - void _fillSetCommand(bool clrPacket = false, packet_t * pack = nullptr, ac_state_t *cmd = nullptr){ - // по умолчанию заполняем исходящий пакет - if (pack == nullptr) pack = &_outPacket; - - // очищаем пакет, если это указано - if (clrPacket) _clearPacket(pack); - - // заполняем его параметрами из _current_ac_state - if (cmd != &_current_ac_state) { - _fillSetCommand(false, pack, &_current_ac_state); - } - - // если команда не указана, значит выходим - if (cmd == nullptr) return; - - // команда указана, дополнительно внесем в пакет те параметры, которые установлены в команде - - // присваиваем параметры пакета - pack->msec = millis(); - pack->header->start_byte = AC_PACKET_START_BYTE; - pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа - pack->header->packet_type = AC_PTYPE_CMD; - pack->header->body_length = 15; // тело команды 15 байт, как у Small status - pack->body = &(pack->data[AC_HEADER_SIZE]); - pack->body[0] = AC_CMD_SET_PARAMS; // устанавливаем параметры - pack->body[1] = 0x01; // он всегда 0x01 - pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; - - // целевая температура кондиционера - if (cmd->temp_target_matter){ - // устраняем выход за границы диапазона (это ограничение самого кондиционера) - if (cmd->temp_target < Constants::AC_MIN_TEMPERATURE) cmd->temp_target = Constants::AC_MIN_TEMPERATURE; - if (cmd->temp_target > Constants::AC_MAX_TEMPERATURE) cmd->temp_target = Constants::AC_MAX_TEMPERATURE; - - // целая часть температуры - pack->body[2] = (pack->body[2] & ~AC_TEMP_TARGET_INT_PART_MASK) | (((uint8_t)(cmd->temp_target) - 8) << 3); - - // дробная часть температуры - if (cmd->temp_target - (uint8_t)(cmd->temp_target) > 0) { - pack->body[4] = (pack->body[4] & ~AC_TEMP_TARGET_FRAC_PART_MASK) | 1; - } else { - pack->body[4] = (pack->body[4] & ~AC_TEMP_TARGET_FRAC_PART_MASK) | 0; - } - } - - // обнулить счетчик минут с последней команды - pack->body[4] &= ~ AC_MIN_COUTER ; - - // вертикальные жалюзи - if (cmd->louver.louver_v != AC_LOUVERV_UNTOUCHED){ - pack->body[2] = (pack->body[2] & ~AC_LOUVERV_MASK) | cmd->louver.louver_v; - } - - // горизонтальные жалюзи - if (cmd->louver.louver_h != AC_LOUVERH_UNTOUCHED){ - pack->body[3] = (pack->body[3] & ~AC_LOUVERH_MASK) | cmd->louver.louver_h; - } - - // скорость вентилятора - if (cmd->fanSpeed != AC_FANSPEED_UNTOUCHED){ - pack->body[5] = (pack->body[5] & ~AC_FANSPEED_MASK) | cmd->fanSpeed; - } - - // спец.режимы вентилятора: TURBO - if (cmd->fanTurbo != AC_FANTURBO_UNTOUCHED){ - pack->body[6] = (pack->body[6] & ~AC_FANTURBO_MASK) | cmd->fanTurbo; - } - - // спец.режимы вентилятора: MUTE - if (cmd->fanMute != AC_FANMUTE_UNTOUCHED){ - pack->body[6] = (pack->body[6] & ~AC_FANMUTE_MASK) | cmd->fanMute; - } - - // режим кондея - if (cmd->mode != AC_MODE_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_MODE_MASK) | cmd->mode; - } - if (cmd->sleep != AC_SLEEP_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_SLEEP_MASK) | cmd->sleep; - } - if (cmd->iFeel != AC_IFEEL_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_IFEEL_MASK) | cmd->iFeel; - } - - // питание вкл/выкл - if (cmd->power != AC_POWER_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_POWER_MASK) | cmd->power; - } - if (cmd->clean != AC_CLEAN_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_CLEAN_MASK) | cmd->clean; - } - // ионизатор - if (cmd->health != AC_HEALTH_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_HEALTH_MASK) | cmd->health; - } - - // какой то флаг ионизатора - if (cmd->health_status != AC_HEALTH_STATUS_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_HEALTH_STATUS_MASK) | cmd->health_status; - } - - // дисплей - if (cmd->display != AC_DISPLAY_UNTOUCHED){ - pack->body[12] = (pack->body[12] & ~AC_DISPLAY_MASK) | cmd->display; - } - - // антиплесень - if (cmd->mildew != AC_MILDEW_UNTOUCHED){ - pack->body[12] = (pack->body[12] & ~AC_MILDEW_MASK) | cmd->mildew; - } - - - - // рассчитываем и записываем в пакет CRC - pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - _setCRC16(pack); - } - - // отправка запроса на маленький статусный пакет - bool sq_requestSmallStatus(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _fillStatusSmall(&_outPacket); - _fillStatusSmall(&_sequence[_sequence_current_step].packet); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: small status request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // проверка ответа на запрос маленького статусного пакета - bool sq_controlSmallStatus(){ - // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим - if (_inPacket.bytesLoaded == 0) return true; - - // Пинги игнорируем - if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; - - // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать - _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; - - // проверяем ответ - bool relevant = true; - relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x0F)); - relevant = (relevant && (_inPacket.body[0] == 0x01)); - relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_SMALL)); - - // если пакет подходит, значит можно переходить к следующему шагу - if (relevant) { - _debugMsg(F("Sequence [step %u]: correct small status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _sequence_current_step++; - } else { - // если пакет не подходящий, то отчитываемся в лог... - _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_SMALL); - // ...и прерываем последовательность, так как вернем false - } - return relevant; - } - - // отправка запроса на большой статусный пакет - bool sq_requestBigStatus(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _fillStatusBig(&_outPacket); - _fillStatusBig(&_sequence[_sequence_current_step].packet); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: big status request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // проверка ответа на запрос большого статусного пакета - bool sq_controlBigStatus(){ - // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим - if (_inPacket.bytesLoaded == 0) return true; - - // Пинги игнорируем - if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; - - // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать - _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; - - // проверяем ответ - bool relevant = true; - relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x18 || _inPacket.header->body_length == 0x19)); // канальник Royal Clima отвечает пакетом длиной 0x19 - relevant = (relevant && (_inPacket.body[0] == 0x01)); - relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_BIG)); - - // если пакет подходит, значит можно переходить к следующему шагу - if (relevant) { - _debugMsg(F("Sequence [step %u]: correct big status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _sequence_current_step++; - } else { - // если пакет не подходящий, то отчитываемся в лог... - _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_BIG); - // ...и прерываем последовательность - } - return relevant; - } - - // отправка запроса на выполнение команды - bool sq_requestDoCommand(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _fillSetCommand(true, &_outPacket, &_sequence[_sequence_current_step].cmd); - _fillSetCommand(true, &_sequence[_sequence_current_step].packet, &_sequence[_sequence_current_step].cmd); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: doCommand request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // проверка ответа на выполнение команды - bool sq_controlDoCommand(){ - // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим - if (_inPacket.bytesLoaded == 0) return true; - - // Пинги игнорируем - if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; - - // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать - _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; - - // проверяем ответ - bool relevant = true; - relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x04)); - relevant = (relevant && (_inPacket.body[0] == 0x01)); - relevant = (relevant && (_inPacket.body[1] == AC_CMD_SET_PARAMS)); - // байты 2 и 3 обычно равны CRC отправленного пакета с командой - relevant = (relevant && (_inPacket.body[2] == _sequence[_sequence_current_step-1].packet.crc->crc[0])); - relevant = (relevant && (_inPacket.body[3] == _sequence[_sequence_current_step-1].packet.crc->crc[1])); - - // если пакет подходит, значит можно переходить к следующему шагу - if (relevant) { - _debugMsg(F("Sequence [step %u]: correct doCommand packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _sequence_current_step++; - } else { - // если пакет не подходящий, то отчитываемся в лог... - _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_BIG); - // ...и прерываем последовательность - } - return relevant; - } - - // отправка запроса с тестовым пакетом - bool sq_requestTestPacket(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _copyPacket(&_outPacket, &_outTestPacket); - _copyPacket(&_sequence[_sequence_current_step].packet, &_outTestPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: Test Packet request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // сенсоры, отображающие параметры сплита - esphome::sensor::Sensor *sensor_indoor_temperature_ = nullptr; - esphome::sensor::Sensor *sensor_outdoor_temperature_ = nullptr; - esphome::sensor::Sensor *sensor_inbound_temperature_ =nullptr; - esphome::sensor::Sensor *sensor_outbound_temperature_ =nullptr; - esphome::sensor::Sensor *sensor_strange_temperature_ =nullptr; - // текущая мощность компрессора - esphome::sensor::Sensor *sensor_invertor_power_ = nullptr; - // бинарный сенсор, отображающий состояние дисплея - esphome::binary_sensor::BinarySensor *sensor_display_ = nullptr; - // бинарный сенсор состония разморозки - esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; - - // загружает на выполнение последовательность команд на включение/выключение табло с температурой - bool _displaySequence(ac_display dsp = AC_DISPLAY_ON){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("displaySequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - if (dsp == AC_DISPLAY_UNTOUCHED) return false; // выходим, чтобы не тратить время - - // формируем команду - ac_command_t cmd; - _clearCommand(&cmd); // не забываем очищать, а то будет мусор - cmd.display = dsp; - // добавляем команду в последовательность - if (!commandSequence(&cmd)) return false; - - _debugMsg(F("displaySequence: loaded (display = %02X)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, dsp); - return true; - } - - // номер глобального пресета от режима работы - uint8_t get_num_preset(ac_command_t* cmd){ - if(cmd->power == AC_POWER_OFF){ - return POS_MODE_OFF; - } else if(cmd->mode == AC_MODE_AUTO){ - return POS_MODE_AUTO; - } else if(cmd->mode == AC_MODE_COOL){ - return POS_MODE_COOL; - } else if(cmd->mode == AC_MODE_DRY){ - return POS_MODE_DRY; - } else if(cmd->mode == AC_MODE_FAN){ - return POS_MODE_FAN; - } else if(cmd->mode == AC_MODE_HEAT){ - return POS_MODE_HEAT; - } - cmd->power = AC_POWER_OFF; - return POS_MODE_OFF; - } - - // восстановление данных из пресета - void load_preset(ac_command_t* cmd, uint8_t num_preset){ - if(num_preset < sizeof(global_presets)/sizeof(global_presets[0])){ // проверка выхода за пределы массива - if(cmd->power == global_presets[num_preset].power && cmd->mode == global_presets[num_preset].mode){ //контроль инициализации - memcpy(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE); // просто копируем из массива - _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } else { - _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } - } - } - - // запись данных в массив персетов - void save_preset(ac_command_t* cmd){ - uint8_t num_preset = get_num_preset(cmd); - if(memcmp(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE) != 0){ // содержимое пресетов разное - memcpy(&(global_presets[num_preset]), cmd, AC_COMMAND_BASE_SIZE); // копируем пресет в массив - #if defined(ESP32) - _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - if(storage.save(global_presets)){ - if(!global_preferences->sync()) // сохраняем все пресеты - _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } else { - _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } - #endif - } else { - _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } - } - - public: - // инициализация объекта - void initAC(esphome::uart::UARTComponent *parent = nullptr){ - _dataMillis = millis(); - _clearInPacket(); - _clearOutPacket(); - - _clearPacket(&_outTestPacket); - _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - _outTestPacket.header->wifi = AC_PACKET_ANSWER; - - _setStateMachineState(ACSM_IDLE); - _ac_serial = parent; - _hw_initialized = (_ac_serial != nullptr); - _has_connection = false; - - // заполняем структуру состояния начальными значениями - _clearCommand((ac_command_t *)&_current_ac_state); - - // очищаем последовательность пакетов - _clearSequence(); - - // выполнена ли уже стартовая последовательность команд (сбор информации о статусе кондея) - _startupSequenceComlete = false; - }; - - float get_setup_priority() const override { return esphome::setup_priority::DATA; } - - void set_indoor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_indoor_temperature_ = temperature_sensor; } - void set_outdoor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_outdoor_temperature_ = temperature_sensor; } - void set_inbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_inbound_temperature_ = temperature_sensor; } - void set_outbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_outbound_temperature_ = temperature_sensor; } - void set_strange_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_strange_temperature_ = temperature_sensor; } - void set_defrost_state(binary_sensor::BinarySensor *defrost_state) { sensor_defrost_ = defrost_state; } - void set_display_sensor(binary_sensor::BinarySensor *display_sensor) { sensor_display_ = display_sensor; } - void set_invertor_power_sensor(sensor::Sensor *invertor_power_sensor) { sensor_invertor_power_ = invertor_power_sensor; } - bool get_hw_initialized(){ return _hw_initialized; }; - bool get_has_connection(){ return _has_connection; }; - - // возвращает, есть ли елементы в последовательности команд - bool hasSequence(){ - return (_sequence[0].item_type != AC_SIT_NONE); - } - - // вызывается для обновления отображения состояния кондиционера, ДЛЯ ПУБЛИКАЦИИ - void stateChanged(){ - _debugMsg(F("State changed, let's publish it."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - if(_is_invertor){ // анализ режима для инвертора, точнее потому что использует показания мощности инвертора - static uint32_t timerInv = 0; - if(_current_ac_state.invertor_power == 0){ // инвертор выключен - timerInv = millis(); - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && - _current_ac_state.power == AC_POWER_OFF ){ // внутренний кулер остановлен, кондей выключен - this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает - } else { - int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; - if (delta_temp > 0 && delta_temp < 2 && - (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else if (_current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_OFF ){ // кулер чуть вертится - this->action = climate::CLIMATE_ACTION_IDLE; // кондей в простое - } else { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция - } - } - } else if(millis()-timerInv > 2000){ // инвертор включен, но нужно дождаться реакции на его включение - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE ){ //медленное вращение - if(_current_ac_state.temp_ambient - _current_ac_state.temp_inbound > 0){ //холодный радиатор - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else { // теплый радиатор, видимо переходный режим - this->action = climate::CLIMATE_ACTION_IDLE; - } - } else { - int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; - if(delta_temp < -2){ // входящая температура выше комнатной, быстрый фен - ОБОГРЕВ - this->action = climate::CLIMATE_ACTION_HEATING; - } else if(delta_temp > 2){ // ниже, быстрый фен - ОХЛАЖДЕНИЕ - this->action = climate::CLIMATE_ACTION_COOLING; - } else { // просто вентиляция - this->action = climate::CLIMATE_ACTION_IDLE; - } - } - } else { - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE){ - this->action = climate::CLIMATE_ACTION_IDLE; - } else { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция - } - } - } else { - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && - _current_ac_state.power == AC_POWER_OFF){ - this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает - } else { - int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; // разность температуры между комнатной и входящей - if (delta_temp > 0 && delta_temp < 2 && - (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else if(_current_ac_state.realFanSpeed != AC_REAL_FAN_OFF && - _current_ac_state.realFanSpeed != AC_REAL_FAN_MUTE){ - if(delta_temp > 2){ - this->action = climate::CLIMATE_ACTION_COOLING; - } else if(delta_temp < -2){ - this->action = climate::CLIMATE_ACTION_HEATING; - } else { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция - } - } else { - this->action = climate::CLIMATE_ACTION_IDLE; - } - } - } - _debugMsg(F("Action mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->action); - /*************************** POWER & MODE ***************************/ - if (_current_ac_state.power == AC_POWER_ON){ - switch (_current_ac_state.mode) { - case AC_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_HEAT_COOL; // по факту режим, названный в AUX как AUTO, является режимом HEAT_COOL - break; - - case AC_MODE_COOL: - this->mode = climate::CLIMATE_MODE_COOL; - break; - - case AC_MODE_DRY: - this->mode = climate::CLIMATE_MODE_DRY; - break; - - case AC_MODE_HEAT: - this->mode = climate::CLIMATE_MODE_HEAT; - break; - - case AC_MODE_FAN: - this->mode = climate::CLIMATE_MODE_FAN_ONLY; - break; - - default: - _debugMsg(F("Warning: unknown air conditioner mode."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - break; - } - } else { - this->mode = climate::CLIMATE_MODE_OFF; - } - - _debugMsg(F("Climate mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->mode); - - /*************************** FAN SPEED ***************************/ - if(_current_ac_state.power == AC_POWER_ON){ - this->fan_mode = climate::CLIMATE_FAN_OFF; - switch (_current_ac_state.fanSpeed) { - case AC_FANSPEED_HIGH: - this->fan_mode = climate::CLIMATE_FAN_HIGH; - this->custom_fan_mode = (std::string)""; - break; - - case AC_FANSPEED_MEDIUM: - this->fan_mode = climate::CLIMATE_FAN_MEDIUM; - this->custom_fan_mode = (std::string)""; - break; - - case AC_FANSPEED_LOW: - this->fan_mode = climate::CLIMATE_FAN_LOW; - break; - - case AC_FANSPEED_AUTO: - this->fan_mode = climate::CLIMATE_FAN_AUTO; - break; - - default: - break; - } - /*************************** TURBO FAN MODE ***************************/ - switch (_current_ac_state.fanTurbo) { - case AC_FANTURBO_ON: - this->custom_fan_mode = Constants::TURBO; - break; - case AC_FANTURBO_OFF: - default: - if (this->custom_fan_mode == Constants::TURBO) this->custom_fan_mode = (std::string)""; - break; - } - /*************************** MUTE FAN MODE ***************************/ - switch (_current_ac_state.fanMute) { - case AC_FANMUTE_ON: - this->custom_fan_mode = Constants::MUTE; - break; - case AC_FANMUTE_OFF: - default: - if (this->custom_fan_mode == Constants::MUTE) this->custom_fan_mode = (std::string)""; - break; - } - } else { // при выключеном питании публикуем фальшивый статус - //this->fan_mode = climate::CLIMATE_FAN_LOW ; - this->custom_fan_mode = Constants::MUTE; - } - - _debugMsg(F("Climate fan mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->fan_mode); - _debugMsg(F("Climate fan TURBO mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanTurbo); - _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); - - //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ - - /*************************** SLEEP PRESET ***************************/ - // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - if(_current_ac_state.sleep == AC_SLEEP_ON && - _current_ac_state.power == AC_POWER_ON ) { - this->preset = climate::CLIMATE_PRESET_SLEEP; - } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { - this->preset = climate::CLIMATE_PRESET_NONE; - } - - _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); - - /*************************** HEALTH CUSTOM PRESET ***************************/ - // режим работы ионизатора - if(_current_ac_state.health == AC_HEALTH_ON && - _current_ac_state.power == AC_POWER_ON ) { - this->custom_preset = Constants::HEALTH; - } else if (this->custom_preset == Constants::HEALTH) { - this->custom_preset = (std::string)""; - } - - _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); - - /*************************** CLEAN CUSTOM PRESET ***************************/ - // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF - if(_current_ac_state.clean == AC_CLEAN_ON && - _current_ac_state.power == AC_POWER_OFF ) { - this->custom_preset = Constants::CLEAN; - } else if (this->custom_preset == Constants::CLEAN) { - this->custom_preset = (std::string)""; - } - - _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); - - /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ - // пресет просушки кондиционера после выключения - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - // - // Brokly: - // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет - // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии - // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки - // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало - // и затыкается. - if(_current_ac_state.mildew == AC_MILDEW_ON && - _current_ac_state.power == AC_POWER_OFF ) { - this->custom_preset = Constants::ANTIFUNGUS; - } else if (this->custom_preset == Constants::ANTIFUNGUS) { - this->custom_preset = (std::string)""; - } - - _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); - - /*************************** LOUVERs ***************************/ - - if( _current_ac_state.power == AC_POWER_OFF){ //ЕСЛИ КОНДЕЙ ВЫКЛЮЧЕН - this->swing_mode = climate::CLIMATE_SWING_OFF; - } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN && - _current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ - this->swing_mode = climate::CLIMATE_SWING_BOTH; - } else if (_current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN){ - this->swing_mode = climate::CLIMATE_SWING_VERTICAL; - } else { - this->swing_mode = climate::CLIMATE_SWING_OFF; - } - - _debugMsg(F("Climate swing mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->swing_mode); - - /*************************** TEMPERATURE ***************************/ - - if(_current_ac_state.mode == AC_MODE_FAN || _current_ac_state.power == AC_POWER_OFF){ - this->target_temperature = _current_ac_state.temp_ambient; - } else if (_current_ac_state.mode == AC_MODE_AUTO ){ - this->target_temperature = 25; - } else { - this->target_temperature = _current_ac_state.temp_target; - } - _debugMsg(F("Target temperature: %f"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->target_temperature); - this->current_temperature = _current_ac_state.temp_ambient; - _debugMsg(F("Room temperature: %f"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->current_temperature); - - /*********************************************************************/ - /*************************** PUBLISH STATE ***************************/ - /*********************************************************************/ - this->publish_state(); - // температура в комнате - if (sensor_indoor_temperature_ != nullptr) - sensor_indoor_temperature_->publish_state(_current_ac_state.temp_ambient); - // температура уличного блока - if (sensor_outdoor_temperature_ != nullptr) - sensor_outdoor_temperature_->publish_state(_current_ac_state.temp_outdoor); - // температура подводящей магистрали - if (sensor_inbound_temperature_ != nullptr) - sensor_inbound_temperature_->publish_state(_current_ac_state.temp_inbound); - // температура отводящей магистрали - if (sensor_outbound_temperature_ != nullptr) - sensor_outbound_temperature_->publish_state(_current_ac_state.temp_outbound); - // температура странного датчика - if (sensor_strange_temperature_ != nullptr) - sensor_strange_temperature_->publish_state(_current_ac_state.temp_strange); - // мощность инвертора - if (sensor_invertor_power_ != nullptr) - sensor_invertor_power_->publish_state(_current_ac_state.invertor_power); - // флаг режима разморозки - if (sensor_defrost_ != nullptr) - sensor_defrost_->publish_state(_current_ac_state.defrost); - // состояние дисплея - if (sensor_display_ != nullptr) { - switch (_current_ac_state.display) { - case AC_DISPLAY_ON: - if (this->get_display_inverted()) { - sensor_display_->publish_state(false); - } else { - sensor_display_->publish_state(true); - } - break; - - case AC_DISPLAY_OFF: - if (this->get_display_inverted()) { - sensor_display_->publish_state(true); - } else { - sensor_display_->publish_state(false); - } - break; - - default: - // могут быть и другие состояния, поэтому так - break; - } - } - } - - // вывод в дебаг текущей конфигурации компонента - void dump_config() { - ESP_LOGCONFIG(Constants::TAG, "AUX HVAC:"); - ESP_LOGCONFIG(Constants::TAG, " [x] Firmware version: %s", Constants::AC_FIRMWARE_VERSION.c_str()); - ESP_LOGCONFIG(Constants::TAG, " [x] Period: %dms", this->get_period()); - ESP_LOGCONFIG(Constants::TAG, " [x] Show action: %s", TRUEFALSE(this->get_show_action())); - ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", TRUEFALSE(this->get_display_inverted())); - ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); - ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); - if ((this->sensor_indoor_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); - if (!(this->sensor_indoor_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_indoor_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_indoor_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_indoor_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_indoor_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_indoor_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_indoor_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_indoor_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_indoor_temperature_)->unique_id().c_str()); - } - if ((this->sensor_indoor_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_outdoor_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Outdoor Temperature"), (this->sensor_outdoor_temperature_)->get_name().c_str()); - if (!(this->sensor_outdoor_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_outdoor_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_outdoor_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_outdoor_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_outdoor_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_outdoor_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_outdoor_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_outdoor_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_outdoor_temperature_)->unique_id().c_str()); - } - if ((this->sensor_outdoor_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_inbound_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Inbound Temperature"), (this->sensor_inbound_temperature_)->get_name().c_str()); - if (!(this->sensor_inbound_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_inbound_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_inbound_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_inbound_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_inbound_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_inbound_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_inbound_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_inbound_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_inbound_temperature_)->unique_id().c_str()); - } - if ((this->sensor_inbound_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_outbound_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Outbound Temperature"), (this->sensor_outbound_temperature_)->get_name().c_str()); - if (!(this->sensor_outbound_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_outbound_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_outbound_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_outbound_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_outbound_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_outbound_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_outbound_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_outbound_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_outbound_temperature_)->unique_id().c_str()); - } - if ((this->sensor_outbound_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_strange_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Strange Temperature"), (this->sensor_strange_temperature_)->get_name().c_str()); - if (!(this->sensor_strange_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_strange_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_strange_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_strange_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_strange_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_strange_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_strange_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_strange_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_strange_temperature_)->unique_id().c_str()); - } - if ((this->sensor_strange_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_invertor_power_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Inverter Power"), (this->sensor_invertor_power_)->get_name().c_str()); - if (!(this->sensor_invertor_power_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_invertor_power_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_invertor_power_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_invertor_power_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_invertor_power_)->get_accuracy_decimals()); - if (!(this->sensor_invertor_power_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_invertor_power_)->get_icon().c_str()); - } - if (!(this->sensor_invertor_power_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_invertor_power_)->unique_id().c_str()); - } - if ((this->sensor_invertor_power_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_defrost_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Defrost status"), (this->sensor_defrost_)->get_name().c_str()); - if (!(this->sensor_defrost_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_defrost_)->get_device_class().c_str()); - } - if (!(this->sensor_defrost_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_defrost_)->get_icon().c_str()); - } - if (!(this->sensor_defrost_)->get_object_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Object ID: '%s'", " ", (this->sensor_defrost_)->get_object_id().c_str()); - } - } - - if ((this->sensor_display_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Display"), (this->sensor_display_)->get_name().c_str()); - if (!(this->sensor_display_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_display_)->get_device_class().c_str()); - } - if (!(this->sensor_display_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_display_)->get_icon().c_str()); - } - if (!(this->sensor_display_)->get_object_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Object ID: '%s'", " ", (this->sensor_display_)->get_object_id().c_str()); - } - } - this->dump_traits_(Constants::TAG); - } - - // вызывается пользователем из интерфейса ESPHome или Home Assistant - void control(const esphome::climate::ClimateCall &call) override { - bool hasCommand = false; - ac_command_t cmd; - - _clearCommand(&cmd); // не забываем очищать, а то будет мусор - - // User requested mode change - if (call.get_mode().has_value()) { - ClimateMode mode = *call.get_mode(); - // Send mode to hardware - switch (mode) { - case climate::CLIMATE_MODE_OFF: - hasCommand = true; - cmd.power = AC_POWER_OFF; - load_preset(&cmd, POS_MODE_OFF); - cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать - this->mode = mode; - break; - - case climate::CLIMATE_MODE_COOL: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_COOL; - load_preset(&cmd, POS_MODE_COOL); - this->mode = mode; - break; - - case climate::CLIMATE_MODE_HEAT: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_HEAT; - load_preset(&cmd, POS_MODE_HEAT); - this->mode = mode; - break; - - case climate::CLIMATE_MODE_HEAT_COOL: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_AUTO; - load_preset(&cmd, POS_MODE_AUTO); - cmd.temp_target = 25; // зависимость от режима HEAT_COOL - cmd.temp_target_matter = true; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL - this->mode = mode; - break; - - case climate::CLIMATE_MODE_FAN_ONLY: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_FAN; - load_preset(&cmd, POS_MODE_FAN); - cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN - cmd.temp_target_matter = true; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN - cmd.sleep = AC_SLEEP_OFF; - if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ - cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN - } - this->mode = mode; - break; - - case climate::CLIMATE_MODE_DRY: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_DRY; - load_preset(&cmd, POS_MODE_DRY); - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY - cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY - this->mode = mode; - break; - - case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) - default: - break; - } - } - - // User requested fan_mode change - if(_current_ac_state.power == AC_POWER_ON || cmd.power == AC_POWER_ON) { // пресеты для включенного кондея - // User requested swing_mode change - if (call.get_swing_mode().has_value()) { - ClimateSwingMode swingmode = *call.get_swing_mode(); - // Send fan mode to hardware - switch (swingmode) { - // The protocol allows other combinations for SWING. - // For example "turn the louvers to the desired position or "spread to the sides" / "concentrate in the center". - // But the ROVEX IR-remote does not provide this features. Therefore this features haven't been tested. - // May be suitable for other models of AUX-based ACs. - case climate::CLIMATE_SWING_OFF: - cmd.louver.louver_h = AC_LOUVERH_OFF; - cmd.louver.louver_v = AC_LOUVERV_OFF; - hasCommand = true; - this->swing_mode = swingmode; - break; - - case climate::CLIMATE_SWING_BOTH: - cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; - cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; - hasCommand = true; - this->swing_mode = swingmode; - break; - - case climate::CLIMATE_SWING_VERTICAL: - cmd.louver.louver_h = AC_LOUVERH_OFF; - cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; - hasCommand = true; - this->swing_mode = swingmode; - break; - - case climate::CLIMATE_SWING_HORIZONTAL: - cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; - cmd.louver.louver_v = AC_LOUVERV_OFF; - hasCommand = true; - this->swing_mode = swingmode; - break; - } - } - - if (call.get_target_temperature().has_value()) { - if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_FAN) || - (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_FAN)){ // блокировка ввода температуры в режиме FAN - this->target_temperature = _current_ac_state.temp_ambient; - this->publish_state(); - } else if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_AUTO) || - (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_AUTO)){ // блокировка ввода температуры в режиме АВТО - this->target_temperature = 25; - this->publish_state(); - } else { - // User requested target temperature change - float temp = *call.get_target_temperature(); - // Send target temp to climate - if (temp > Constants::AC_MAX_TEMPERATURE) temp = Constants::AC_MAX_TEMPERATURE; - if (temp < Constants::AC_MIN_TEMPERATURE) temp = Constants::AC_MIN_TEMPERATURE; - if(cmd.temp_target != temp){ - cmd.temp_target = temp; - hasCommand = true; - cmd.temp_target_matter = true; - } - } - } - - if (call.get_fan_mode().has_value()) { - ClimateFanMode fanmode = *call.get_fan_mode(); - // Send fan mode to hardware - switch (fanmode) { - case climate::CLIMATE_FAN_AUTO: - if(cmd.mode != AC_MODE_FAN){ // при вентиляции нет такого режима - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_AUTO; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - } - break; - - case climate::CLIMATE_FAN_LOW: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_LOW; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_MEDIUM: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_MEDIUM; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_HIGH: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_HIGH; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_ON: - case climate::CLIMATE_FAN_OFF: - case climate::CLIMATE_FAN_MIDDLE: - case climate::CLIMATE_FAN_FOCUS: - case climate::CLIMATE_FAN_DIFFUSE: - default: - break; - } - - } else if (call.get_custom_fan_mode().has_value()) { - std::string customfanmode = *call.get_custom_fan_mode(); - // Send fan mode to hardware - if (customfanmode == Constants::TURBO) { - // TURBO fan mode is suitable in COOL and HEAT modes for Rovex air conditioners. - // Other modes don't accept TURBO fan mode. - // May be other AUX-based air conditioners do the same. - if ( cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT) { - - hasCommand = true; - cmd.fanTurbo = AC_FANTURBO_ON; - cmd.fanMute = AC_FANMUTE_OFF; // зависимость от fanturbo - this->custom_fan_mode = customfanmode; - } else { - _debugMsg(F("TURBO fan mode is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - } - } else if (customfanmode == Constants::MUTE) { - // MUTE fan mode is suitable in FAN mode only for Rovex air conditioner. - // In COOL mode AC receives command without any changes. - // May be other AUX-based air conditioners do the same. - //if ( cmd.mode == AC_MODE_FAN || - // _current_ac_state.mode == AC_MODE_FAN) { - - hasCommand = true; - cmd.fanMute = AC_FANMUTE_ON; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от fanmute - this->custom_fan_mode = customfanmode; - //} else { - // _debugMsg(F("MUTE fan mode is suitable in FAN mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - //} - } - } - - // Пользователь выбрал пресет - if (call.get_preset().has_value()) { - ClimatePreset preset = *call.get_preset(); - switch (preset) { - case climate::CLIMATE_PRESET_SLEEP: - // Ночной режим (SLEEP). Комбинируется только с режимами COOL, HEAT AUTO и DRY. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - if ( cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || cmd.mode == AC_MODE_DRY || cmd.mode == AC_MODE_AUTO || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_DRY || _current_ac_state.mode == AC_MODE_AUTO){ - - hasCommand = true; - cmd.sleep = AC_SLEEP_ON; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - this->preset = preset; - } else { - _debugMsg(F("SLEEP preset is suitable in COOL,HEAT and AUTO modes only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - break; - case climate::CLIMATE_PRESET_NONE: - // выбран пустой пресет, сбрасываем все настройки - hasCommand = true; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - this->preset = preset; - _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - default: - // никакие другие встроенные пресеты не поддерживаются - break; - } - } else if (call.get_custom_preset().has_value()) { - std::string custom_preset = *call.get_custom_preset(); - if (custom_preset == Constants::HEALTH) { - hasCommand = true; - cmd.health = AC_HEALTH_ON; - cmd.health_status = AC_HEALTH_STATUS_ON; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health - cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - if(cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || cmd.mode == AC_MODE_AUTO || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_AUTO ){ - - cmd.fanSpeed = AC_FANSPEED_AUTO; // зависимость от health - } else if(cmd.mode == AC_MODE_FAN || _current_ac_state.mode == AC_MODE_FAN){ - cmd.fanSpeed = AC_FANSPEED_MEDIUM; // зависимость от health - } - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::CLEAN) { - _debugMsg(F("CLEAN work only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } else if (custom_preset == Constants::ANTIFUNGUS) { - _debugMsg(F("Anti-FUNGUS works only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - } - } else if(_current_ac_state.power == AC_POWER_OFF || cmd.power == AC_POWER_OFF){ // функции при выключеном питании - - if (call.get_target_temperature().has_value()) { // блокировка изменения температуры в выключеном состоянии - this->target_temperature = _current_ac_state.temp_ambient; - this->publish_state(); - } - - if (call.get_preset().has_value()) { // пользователь выбрал пустой пресет - ClimatePreset preset = *call.get_preset(); - switch (preset) { - case climate::CLIMATE_PRESET_SLEEP: - _debugMsg(F("SLEEP preset is suitable in POWER ON mode only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - case climate::CLIMATE_PRESET_NONE: - // выбран пустой пресет, сбрасываем все настройки - hasCommand = true; - cmd.clean = AC_CLEAN_OFF; // для логики пресетов - cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->preset = preset; - _debugMsg(F("Clear all 'Power OFF' presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - default: - // никакие другие встроенные пресеты не поддерживаются - break; - } - } else { - std::string custom_preset = *call.get_custom_preset(); - if (call.get_custom_preset().has_value()) { - if (custom_preset == Constants::CLEAN) { - // режим очистки кондиционера при AC_POWER_OFF - hasCommand = true; - cmd.clean = AC_CLEAN_ON; - cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::ANTIFUNGUS) { - // Brokly: - // включение-выключение функции "Антиплесень". - // у меня пульт отправляет 5 посылок и на включение и на выключение, но реагирует на эту кнопку - // только в режиме POWER_OFF - - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - cmd.mildew = AC_MILDEW_ON; - cmd.clean = AC_CLEAN_OFF; // для логики пресетов - hasCommand = true; - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::HEALTH) { - _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - } - } - } - - if (hasCommand) { - commandSequence(&cmd); - this->publish_state(); // Publish updated state - _new_command_set = _store_settings; // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка - } - } - - esphome::climate::ClimateTraits traits() override { - // The capabilities of the climate device - auto traits = climate::ClimateTraits(); - - traits.set_supports_current_temperature(true); - traits.set_supports_two_point_target_temperature(false); // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature - - // tells the frontend what range of temperatures the climate device should display (gauge min/max values) - traits.set_visual_min_temperature(Constants::AC_MIN_TEMPERATURE); - traits.set_visual_max_temperature(Constants::AC_MAX_TEMPERATURE); - // the step with which to increase/decrease target temperature. This also affects with how many decimal places the temperature is shown. - traits.set_visual_temperature_step(Constants::AC_TEMPERATURE_STEP); - - traits.set_supported_modes(this->_supported_modes); - traits.set_supported_swing_modes(this->_supported_swing_modes); - traits.set_supported_presets(this->_supported_presets); - traits.set_supported_custom_presets(this->_supported_custom_presets); - traits.set_supported_custom_fan_modes(this->_supported_custom_fan_modes); - - /* + MINIMAL SET */ - traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF); - traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH); - traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF); - //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL); - //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_BOTH); - traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); - //traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); - - /* *************** TODO: надо сделать информирование о текущем режиме, сплит поддерживает *************** - * смотри climate::ClimateAction - */ - // if the climate device supports reporting the active current action of the device with the action property. - traits.set_supports_action(this->_show_action); - - return traits; - } - - // запрос маленького пакета статуса кондиционера - bool getStatusSmall(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("getStatusSmall: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 2) { - _debugMsg(F("getStatusSmall: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** getSmallInfo request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestSmallStatus)) { - _debugMsg(F("getStatusSmall: getSmallInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /*************************************** getSmallInfo control ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_controlSmallStatus)) { - _debugMsg(F("getStatusSmall: getSmallInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - _debugMsg(F("getStatusSmall: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - // запрос большого пакета статуса кондиционера - bool getStatusBig(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("getStatusBig: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 2) { - _debugMsg(F("getStatusBig: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** getBigInfo request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestBigStatus)) { - _debugMsg(F("getStatusBig: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /*************************************** getBigInfo control ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_controlBigStatus)) { - _debugMsg(F("getStatusBig: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - _debugMsg(F("getStatusBig: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - // запрос большого и малого пакетов статуса последовательно - bool getStatusBigAndSmall(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("getStatusBigAndSmall: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - - if (!getStatusSmall()) { - _debugMsg(F("getStatusBigAndSmall: error with small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - if (!getStatusBig()) { - _debugMsg(F("getStatusBigAndSmall: error with big status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - _debugMsg(F("getStatusBigAndSmall: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - /** стартовая последовательность пакетов - * - * нужна, чтобы не ждать долго обновления статуса кондиционера - * запускаем сразу, как только удалось подключиться к кондиционеру и прошел первый пинг-пакет - * возвращаемое значение будет присвоено флагу выполнения последовательности - * то есть при возврате false последовательность считается не запущенной и будет вызоваться до тех пор, пока не вернет true - **/ - bool startupSequence(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("startupSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - - // по сути на старте надо получить от кондиционера два статуса - if (!getStatusBigAndSmall()){ - _debugMsg(F("startupSequence: error with big&small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - }; - - _debugMsg(F("startupSequence: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - /** загружает на выполнение команду - * - * стандартная последовательность - это запрос маленького статусного пакета, выполнение команды и повторный запрос - * такого же статуса для проверки, что всё включилось, ну и для обновления интерфейсов всяких связанных компонентов - **/ - bool commandSequence(ac_command_t * cmd){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("commandSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - - // добавление начального запроса маленького статусного пакета в последовательность команд - if (!getStatusSmall()) { - _debugMsg(F("commandSequence: error with first small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 2) { - _debugMsg(F("commandSequence: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** set params request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestDoCommand, cmd)) { - _debugMsg(F("commandSequence: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /*************************************** set params control ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_controlDoCommand)) { - _debugMsg(F("commandSequence: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - // добавление финального запроса маленького статусного пакета в последовательность команд - if (!getStatusSmall()) { - _debugMsg(F("commandSequence: error with last small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - _debugMsg(F("commandSequence: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - // загружает на выполнение последовательность команд на включение/выключение - bool powerSequence(ac_power pwr = AC_POWER_ON){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("powerSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - if (pwr == AC_POWER_UNTOUCHED) return false; // выходим, чтобы не тратить время - - // формируем команду - ac_command_t cmd; - _clearCommand(&cmd); // не забываем очищать, а то будет мусор - cmd.power = pwr; - // добавляем команду в последовательность - if (!commandSequence(&cmd)) return false; - - _debugMsg(F("powerSequence: loaded (power = %02X)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, pwr); - return true; - } - - // выключает экран - bool displayOffSequence(){ - ac_display dsp = AC_DISPLAY_OFF; - if (this->get_display_inverted()) dsp = AC_DISPLAY_ON; - return _displaySequence(dsp); - } - - // включает экран - bool displayOnSequence(){ - ac_display dsp = AC_DISPLAY_ON; - if (this->get_display_inverted()) dsp = AC_DISPLAY_OFF; - return _displaySequence(dsp); - } - - // отправляет сплиту заданный набор байт - // Перед отправкой проверяет пакет на корректность структуры. CRC16 рассчитывает самостоятельно и перезаписывает. - bool sendTestPacket(const std::vector &data){ - //bool sendTestPacket(uint8_t *data = nullptr, uitn8_t data_length = 0){ - //if (data == nullptr) return false; - //if (data_length == 0) return false; - if (data.size() == 0) return false; - //if (data_length > AC_BUFFER_SIZE) return false; - if (data.size() > AC_BUFFER_SIZE) return false; - - // нет смысла в отправке, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("sendTestPacket: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - // очищаем пакет - _clearPacket(&_outTestPacket); - - // копируем данные в пакет - //memcpy(_outTestPacket.data, data, data_length); - uint8_t i = 0; - for (uint8_t n : data) { - _outTestPacket.data[i] = n; - i++; - } - - // на всякий случай указываем правильные некоторые байты - _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - //_outTestPacket.header->wifi = AC_PACKET_ANSWER; - - _outTestPacket.msec = millis(); - _outTestPacket.body = &(_outTestPacket.data[AC_HEADER_SIZE]); - _outTestPacket.bytesLoaded = AC_HEADER_SIZE + _outTestPacket.header->body_length + 2; - - // рассчитываем и записываем в пакет CRC - _outTestPacket.crc = (packet_crc_t *) &(_outTestPacket.data[AC_HEADER_SIZE + _outTestPacket.header->body_length]); - _setCRC16(&_outTestPacket); - - _debugMsg(F("sendTestPacket: test packet loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_outTestPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - - // ниже блок добавления отправки пакета в последовательность команд - //***************************************************************** - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 1) { - _debugMsg(F("sendTestPacket: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** sendTestPacket request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestTestPacket)) { - _debugMsg(F("sendTestPacket: sendTestPacket request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - _debugMsg(F("sendTestPacket: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - return true; - } - - void set_period(uint32_t ms) { this->_update_period = ms; } - uint32_t get_period() { return this->_update_period; } - - void set_show_action(bool show_action) { this->_show_action = show_action; } - bool get_show_action() { return this->_show_action; } - - void set_display_inverted(bool display_inverted) { this->_display_inverted = display_inverted; } - bool get_display_inverted() { return this->_display_inverted; } - - void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } - bool get_store_settings() { return this->_store_settings; } - - void set_supported_modes(const std::set &modes) { this->_supported_modes = modes; } - void set_supported_swing_modes(const std::set &modes) { this->_supported_swing_modes = modes; } - void set_supported_presets(const std::set &presets) { this->_supported_presets = presets; } - void set_custom_presets(const std::set &presets) { this->_supported_custom_presets = presets; } - void set_custom_fan_modes(const std::set &modes) { this->_supported_custom_fan_modes = modes; } - - uint8_t load_presets_result = 0xFF; - void setup() override { - #if defined(ESP32) - load_presets_result = storage.load(global_presets); // читаем все пресеты из флеша - _debugMsg(F("Preset base read from NVRAM, result %02d."), ESPHOME_LOG_LEVEL_WARN, __LINE__, load_presets_result); - #endif - }; - - void loop() override { - if (!get_hw_initialized()) return; - - // контролируем сохранение пресета - if(_new_command_set){ //нужно сохранить пресет - _new_command_set = false; - save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов - } - - // отрабатываем состояния конечного автомата - switch (_ac_state) { - case ACSM_RECEIVING_PACKET: - // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны - _doReceivingPacketState(); - break; - - case ACSM_PARSING_PACKET: - // разбираем полученный пакет - _doParsingPacket(); - break; - - case ACSM_SENDING_PACKET: - // отправляем пакет сплиту - _doSendingPacketState(); - break; - - case ACSM_IDLE: // ничего не делаем, ждем, на что бы среагировать - default: // если состояние какое-то посторонее, то считаем, что IDLE - _doIdleState(); - break; - } - - // раз в заданное количество миллисекунд запрашиваем обновление статуса кондиционера - if ((millis()-_dataMillis) > _update_period){ - _dataMillis = millis(); - - // обычный wifi-модуль запрашивает маленький пакет статуса - // но нам никто не мешает запрашивать и большой и маленький, чтобы чаще обновлять комнатную температуру - // делаем этот запросо только в случае, если есть коннект с кондиционером - if (get_has_connection()) getStatusBigAndSmall(); - } - - }; - }; -} // namespace aux_ac -} // namespace esphome \ No newline at end of file From 947bd4c2d9da72f2d9b75d3d99455c0d77d1314a Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:16:21 +0300 Subject: [PATCH 39/74] Delete climate.py --- climate.py | 341 ----------------------------------------------------- 1 file changed, 341 deletions(-) delete mode 100644 climate.py diff --git a/climate.py b/climate.py deleted file mode 100644 index 58a9ab2..0000000 --- a/climate.py +++ /dev/null @@ -1,341 +0,0 @@ -import logging -import esphome.config_validation as cv -import esphome.codegen as cg -from esphome.components import climate, uart, sensor, binary_sensor -from esphome import automation -from esphome.automation import maybe_simple_id -from esphome.const import ( - CONF_ID, - CONF_UART_ID, - CONF_PERIOD, - CONF_CUSTOM_FAN_MODES, - CONF_CUSTOM_PRESETS, - CONF_INTERNAL, - CONF_DATA, - CONF_SUPPORTED_MODES, - CONF_SUPPORTED_SWING_MODES, - CONF_SUPPORTED_PRESETS, - CONF_PRESSURE, - UNIT_CELSIUS, - UNIT_PERCENT, - UNIT_PASCAL, - ICON_POWER, - ICON_THERMOMETER, - ICON_GAS_CYLINDER, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_POWER_FACTOR, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, -) -from esphome.components.climate import ( - ClimateMode, - ClimatePreset, - ClimateSwingMode, -) - -_LOGGER = logging.getLogger(__name__) - -CODEOWNERS = ["@GrKoR"] -DEPENDENCIES = ["climate", "uart"] -AUTO_LOAD = ["sensor", "binary_sensor"] - -CONF_SUPPORTED_MODES = 'supported_modes' -CONF_SUPPORTED_SWING_MODES = 'supported_swing_modes' -CONF_SUPPORTED_PRESETS = 'supported_presets' -CONF_SHOW_ACTION = 'show_action' -CONF_INDOOR_TEMPERATURE = 'indoor_temperature' -CONF_OUTDOOR_TEMPERATURE = 'outdoor_temperature' -ICON_OUTDOOR_TEMPERATURE = 'mdi:home-thermometer-outline' -CONF_INBOUND_TEMPERATURE = 'inbound_temperature' -ICON_INBOUND_TEMPERATURE = 'mdi:thermometer-plus' -CONF_OUTBOUND_TEMPERATURE = 'outbound_temperature' -ICON_OUTBOUND_TEMPERATURE = 'mdi:thermometer-minus' -CONF_STRANGE_TEMPERATURE = 'strange_temperature' -ICON_STRANGE_TEMPERATURE = 'mdi:thermometer-lines' -CONF_DISPLAY_STATE = 'display_state' -CONF_INVERTOR_POWER = 'invertor_power' -CONF_DEFROST_STATE = 'defrost_state' -ICON_DEFROST = "mdi:snowflake-melt" -CONF_DISPLAY_INVERTED = 'display_inverted' -ICON_DISPLAY = "mdi:clock-digital" -CONF_STORE_SETTINGS = 'store_settings' - - -aux_ac_ns = cg.esphome_ns.namespace("aux_ac") -AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) -Capabilities = aux_ac_ns.namespace("Constants") - -AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action) -AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action) -AirConSendTestPacketAction = aux_ac_ns.class_("AirConSendTestPacketAction", automation.Action) - -ALLOWED_CLIMATE_MODES = { - "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, - "COOL": ClimateMode.CLIMATE_MODE_COOL, - "HEAT": ClimateMode.CLIMATE_MODE_HEAT, - "DRY": ClimateMode.CLIMATE_MODE_DRY, - "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, -} -validate_modes = cv.enum(ALLOWED_CLIMATE_MODES, upper=True) - -ALLOWED_CLIMATE_PRESETS = { - "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, -} -validate_presets = cv.enum(ALLOWED_CLIMATE_PRESETS, upper=True) - -ALLOWED_CLIMATE_SWING_MODES = { - "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, - "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, - "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, -} -validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True) - -CUSTOM_FAN_MODES = { - "MUTE": Capabilities.MUTE, - "TURBO": Capabilities.TURBO, -} -validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) - -CUSTOM_PRESETS = { - "CLEAN": Capabilities.CLEAN, - "HEALTH": Capabilities.HEALTH, - "ANTIFUNGUS": Capabilities.ANTIFUNGUS, -} -validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True) - - -def validate_raw_data(value): - if isinstance(value, list): - return cv.Schema([cv.hex_uint8_t])(value) - raise cv.Invalid( - "data must be a list of bytes" - ) - - -def output_info(config): - """_LOGGER.info(config)""" - return config - -CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(AirCon), - cv.Optional(CONF_PERIOD, default="7s"): cv.time_period, - cv.Optional(CONF_SHOW_ACTION, default="true"): cv.boolean, - cv.Optional(CONF_DISPLAY_INVERTED, default="false"): cv.boolean, - cv.Optional(CONF_STORE_SETTINGS, default="false"): cv.boolean, - cv.Optional(CONF_DEFROST_STATE, default="false"): cv.boolean, - cv.Optional(CONF_INVERTOR_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_PERCENT, - icon=ICON_POWER, - accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER_FACTOR, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_INDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_THERMOMETER, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_OUTDOOR_TEMPERATURE, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_INBOUND_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_INBOUND_TEMPERATURE, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_OUTBOUND_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_OUTBOUND_TEMPERATURE, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_STRANGE_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_STRANGE_TEMPERATURE, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - - cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema( - icon=ICON_DISPLAY - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean - } - ), - - cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema( - icon=ICON_DEFROST - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean - } - ), - - - cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes), - cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(validate_swing_modes), - cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(validate_presets), - cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(validate_custom_presets), - cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list(validate_custom_fan_modes), - } - ) - .extend(uart.UART_DEVICE_SCHEMA) - .extend(cv.COMPONENT_SCHEMA), - output_info -) - -async def to_code(config): - """_LOGGER.info("--------------")""" - """_LOGGER.info(config)""" - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await climate.register_climate(var, config) - - parent = await cg.get_variable(config[CONF_UART_ID]) - cg.add(var.initAC(parent)) - - if CONF_INDOOR_TEMPERATURE in config: - conf = config[CONF_INDOOR_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_indoor_temperature_sensor(sens)) - - if CONF_OUTDOOR_TEMPERATURE in config: - conf = config[CONF_OUTDOOR_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_outdoor_temperature_sensor(sens)) - - if CONF_OUTBOUND_TEMPERATURE in config: - conf = config[CONF_OUTBOUND_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_outbound_temperature_sensor(sens)) - - if CONF_INBOUND_TEMPERATURE in config: - conf = config[CONF_INBOUND_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_inbound_temperature_sensor(sens)) - - if CONF_STRANGE_TEMPERATURE in config: - conf = config[CONF_STRANGE_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_strange_temperature_sensor(sens)) - - if CONF_DISPLAY_STATE in config: - conf = config[CONF_DISPLAY_STATE] - sens = await binary_sensor.new_binary_sensor(conf) - cg.add(var.set_display_sensor(sens)) - - if CONF_DEFROST_STATE in config: - conf = config[CONF_DEFROST_STATE] - sens = await binary_sensor.new_binary_sensor(conf) - cg.add(var.set_defrost_state(sens)) - - if CONF_INVERTOR_POWER in config: - conf = config[CONF_INVERTOR_POWER] - sens = await sensor.new_sensor(conf) - cg.add(var.set_invertor_power_sensor(sens)) - - cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) - cg.add(var.set_show_action(config[CONF_SHOW_ACTION])) - cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED])) - cg.add(var.set_store_settings(config[CONF_STORE_SETTINGS])) - if CONF_SUPPORTED_MODES in config: - cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) - if CONF_SUPPORTED_SWING_MODES in config: - cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) - if CONF_SUPPORTED_PRESETS in config: - cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) - if CONF_CUSTOM_PRESETS in config: - cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS])) - if CONF_CUSTOM_FAN_MODES in config: - cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) - - - -DISPLAY_ACTION_SCHEMA = maybe_simple_id( - { - cv.Required(CONF_ID): cv.use_id(AirCon), - } -) - -@automation.register_action("aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA) -async def display_off_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - return cg.new_Pvariable(action_id, template_arg, paren) - -@automation.register_action("aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA) -async def display_on_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - return cg.new_Pvariable(action_id, template_arg, paren) - - -SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id( - { - cv.Required(CONF_ID): cv.use_id(AirCon), - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), - } -) - - -# ********************************************************************************************************* -# ВАЖНО! Только для инженеров! -# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт -# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает. -# Вы действуете на свой страх и риск. -# ********************************************************************************************************* -@automation.register_action( - "aux_ac.send_packet", - AirConSendTestPacketAction, - SEND_TEST_PACKET_ACTION_SCHEMA -) -async def send_packet_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, paren) - - data = config[CONF_DATA] - if isinstance(data, bytes): - data = list(data) - - if cg.is_template(data): - templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) - cg.add(var.set_data_template(templ)) - else: - cg.add(var.set_data_static(data)) - - return var \ No newline at end of file From f67bc7df33e4f3fc0c54f4e7c35c4747200e65e9 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:16:53 +0300 Subject: [PATCH 40/74] Add files via upload --- components/aux_ac/aux_ac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 9c6a569..c91e397 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -1316,7 +1316,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { { const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; const float antkoef = 1.0 - koef; - static float temp = _current_ac_state.temp_outdoor; + static float temp = big_info_body->outdoor_temperature - 0x20; temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); _current_ac_state.temp_outdoor = temp; From 94f27a6ec8b5be76c5160429160f861c33f1f3a5 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:17:08 +0300 Subject: [PATCH 41/74] Delete automation.h --- automation.h | 65 ---------------------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 automation.h diff --git a/automation.h b/automation.h deleted file mode 100644 index a62fb27..0000000 --- a/automation.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/core/automation.h" -#include "aux_ac.h" - -namespace esphome { -namespace aux_ac { - - template - class AirConDisplayOffAction : public Action - { - public: - explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {} - - void play(Ts... x) override { this->ac_->displayOffSequence(); } - - protected: - AirCon *ac_; - }; - - template - class AirConDisplayOnAction : public Action - { - public: - explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {} - - void play(Ts... x) override { this->ac_->displayOnSequence(); } - - protected: - AirCon *ac_; - }; - - template - class AirConSendTestPacketAction : public Action - { - public: - explicit AirConSendTestPacketAction(AirCon *ac) : ac_(ac) {} - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - this->static_ = false; - } - void set_data_static(const std::vector &data) { - this->data_static_ = data; - this->static_ = true; - } - - void play(Ts... x) override { - if (this->static_) { - this->ac_->sendTestPacket(this->data_static_); - } else { - auto val = this->data_func_(x...); - this->ac_->sendTestPacket(val); - } - } - - protected: - AirCon *ac_; - bool static_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; - }; - -} // namespace aux_ac -} // namespace esphome \ No newline at end of file From fc213f9694f63346eb28d3d58f8286b6e2a751fb Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:17:18 +0300 Subject: [PATCH 42/74] Delete aux_ac.h --- aux_ac.h | 3194 ------------------------------------------------------ 1 file changed, 3194 deletions(-) delete mode 100644 aux_ac.h diff --git a/aux_ac.h b/aux_ac.h deleted file mode 100644 index e8efa3e..0000000 --- a/aux_ac.h +++ /dev/null @@ -1,3194 +0,0 @@ -// Custom ESPHome component for AUX-based air conditioners -// Need some soldering skills -// Source code and detailed instructions are available on github: https://github.com/GrKoR/esphome_aux_ac_component -/// немного переработанная версия старого компонента -#pragma once - -#include -#include "esphome.h" -#include -#include "esphome/core/component.h" -#include "esphome/components/climate/climate.h" -#include "esphome/components/uart/uart.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/core/helpers.h" -#if defined(ESP32) - #include "esphome/core/preferences.h" -#else - #warning "Saving presets does not work with ESP8266" -#endif - -//#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны - -namespace esphome { -namespace aux_ac { - - -using climate::ClimatePreset; -using climate::ClimateTraits; -using climate::ClimateMode; -using climate::ClimateSwingMode; -using climate::ClimateFanMode; - - -class Constants { -public: - static const std::string AC_FIRMWARE_VERSION; - - static const char *const TAG; - static const std::string MUTE; - static const std::string TURBO; - static const std::string CLEAN; - static const std::string HEALTH; - static const std::string ANTIFUNGUS; - - /// минимальная и максимальная температура в градусах Цельсия, ограничения самого кондиционера - static const float AC_MIN_TEMPERATURE; - static const float AC_MAX_TEMPERATURE; - /// шаг изменения целевой температуры, градусы Цельсия - static const float AC_TEMPERATURE_STEP; - - // периодичность опроса кондиционера на предмет изменения состояния - // изменение параметров с пульта не сообщается в UART, поэтому надо запрашивать состояние, чтобы быть в курсе - // значение в миллисекундах - static const uint32_t AC_STATES_REQUEST_INTERVAL; -}; - -const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; -const char *const Constants::TAG = "AirCon"; -const std::string Constants::MUTE = "mute"; -const std::string Constants::TURBO = "turbo"; -const std::string Constants::CLEAN = "clean"; -const std::string Constants::HEALTH = "health"; -const std::string Constants::ANTIFUNGUS = "antifungus"; -const float Constants::AC_MIN_TEMPERATURE = 16.0; -const float Constants::AC_MAX_TEMPERATURE = 32.0; -const float Constants::AC_TEMPERATURE_STEP = 0.5; -const uint32_t Constants::AC_STATES_REQUEST_INTERVAL = 7000; - -class AirCon; - -// состояния конечного автомата компонента -enum acsm_state : uint8_t { - ACSM_IDLE = 0, // ничего не делаем, ждем, на что бы среагировать - ACSM_RECEIVING_PACKET, // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны - ACSM_PARSING_PACKET, // разбираем полученный пакет - ACSM_SENDING_PACKET, // отправляем пакет сплиту -}; - -/** - * Кондиционер отправляет пакеты следующей структуры: - * HEADER: 8 bytes - * BODY: 0..24 bytes - * CRC: 2 bytes - * Весь пакет максимум 34 байта - * По крайней мере все встреченные мной пакеты имели такой размер и структуру. - **/ -#define AC_HEADER_SIZE 8 -#define AC_MAX_BODY_SIZE 24 -#define AC_BUFFER_SIZE 35 - -/** - * таймаут загрузки пакета - * - * через такое количиство миллисекунд конечный автомат перейдет из состояния ACSM_RECEIVING_PACKET в ACSM_IDLE, если пакет не будет загружен - * расчетное время передачи 1 бита при скорости 4800 примерно 0,208 миллисекунд; - * 1 байт передается 11 битами (1 стартовый, 8 бит данных, 1 бит четности и 1 стоповый бит) или 2,30 мс. - * максимальный размер пакета AC_BUFFER_SIZE = 34 байта => 78,2 мсек. Плюс накладные расходы. - * Скорее всего на получение пакета должно хватать 100 мсек. - * - * По факту проверка показала: - * - если отрабатывать по 1 символу из UART на один вызов loop, то на 10 байт пинг-пакета требуется 166 мсек. - * То есть примерно по 16,6 мсек на байт. Примем 17 мсек. - * Значит на максимальный пакет потребуется 17*34 = 578 мсек. Примем 600 мсек. - * - если отрабатывать пакет целиком или хотя бы имеющимися в буфере UART кусками, то на 10 байт пинг-пакета требуется 27 мсек. - * То есть примерно по 2,7 мсек. на байт. Что близко к расчетным значениям. Примем 3 мсек. - * Значит на максимальный пакет потребуется 3*34 = 102 мсек. Примем 150 мсек. - * Опыт показал, что 150 мсек вполне хватает на большие пакеты - **/ -#define AC_PACKET_TIMEOUT 150 // 150 мсек - отработка буфера UART за раз, 600 мсек - отработка буфера UART по 1 байту за вызов loop - -// типы пакетов -#define AC_PTYPE_PING 0x01 // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает -#define AC_PTYPE_CMD 0x06 // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита -#define AC_PTYPE_INFO 0x07 // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля -#define AC_PTYPE_INIT 0x09 // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. -#define AC_PTYPE_UNKN 0x0b // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует - -// типы команд -#define AC_CMD_STATUS_BIG 0x21 // большой пакет статуса кондиционера -#define AC_CMD_STATUS_SMALL 0x11 // маленький пакет статуса кондиционера -#define AC_CMD_STATUS_PERIODIC 0x2C // иногда встречается, сплит её рассылает по своему разумению; (вроде бы может быть и другой код! надо больше данных) -#define AC_CMD_SET_PARAMS 0x01 // команда установки параметров кондиционера - -// значения байтов в пакетах -#define AC_PACKET_START_BYTE 0xBB // Стартовый байт любого пакета 0xBB, других не встречал -#define AC_PACKET_ANSWER 0x80 // признак ответа wifi-модуля - -// заголовок пакета -struct packet_header_t { - uint8_t start_byte; // стартовый бит пакета, всегда 0xBB - uint8_t _unknown1; // не расшифрован - uint8_t packet_type; // тип пакета: - // 0x01 - пинг - // 0x06 - команда кондиционеру - // 0x07 - информационный пакет со статусом кондиционера - // 0x09 - (не разбирался) инициирование коннекта wifi-модуля с приложением на телефоне, с ESP работает и без этого - // 0x0b - (не разбирался) wifi-модуль так сигналит, когда не получает пинги от кондиционера и в каких-то еще случаях - uint8_t wifi; // признак пакета от wifi-модуля - // 0x80 - для всех сообщений, посылаемых модулем - // 0x00 - для всех сообщений, посылаемых кондиционером - uint8_t ping_answer_01; // не расшифрован, почти всегда 0x00, только в ответе на ping этот байт равен 0x01 - uint8_t _unknown2; // не расшифрован - uint8_t body_length; // длина тела пакета в байтах - uint8_t _unknown3; // не расшифрован -}; - -// CRC пакета -union packet_crc_t { - uint16_t crc16; - uint8_t crc[2]; -}; - -struct packet_t { - uint32_t msec; // значение millis в момент определения корректности пакета - packet_header_t * header; - packet_crc_t * crc; - uint8_t * body; // указатель на первый байт тела; можно приведением типов указателей обращаться к отдельным битам как к полям соответсвующей структуры - uint8_t bytesLoaded; //количество загруженных в пакет байт, включая CRC - uint8_t data[AC_BUFFER_SIZE]; -}; - -// тело ответа на пинг -struct packet_ping_answer_body_t { - uint8_t byte_1C = 0x1C; // первый байт всегда 0x1C - uint8_t byte_27 = 0x27; // второй байт тела пинг-ответа всегда 0x27 - uint8_t zero1 = 0; // всегда 0x00 - uint8_t zero2 = 0; // всегда 0x00 - uint8_t zero3 = 0; // всегда 0x00 - uint8_t zero4 = 0; // всегда 0x00 - uint8_t zero5 = 0; // всегда 0x00 - uint8_t zero6 = 0; // всегда 0x00 -}; - -// тело большого информационного пакета -struct packet_big_info_body_t { - uint8_t byte_01; // всегда 0x01 - uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x21); - // пакет может рассылаться и в дежурном режиме (без запроса со стороны wifi-модуля) - // в этом случае тут могут быть значения, отличные от 0x21 - // БАЙТ2 - uint8_t reserv20 :5; // не расшифрован, всегда 0xC0 11000000 - bool is_invertor :1; // флаг инвертора - uint8_t reserv21 :2; // для RoyalClima18HNI: всегда 0xE0 11100000 - // Brokly: для Energolux Bern: 0xE0; иногда, с равными промежутками во времени проскакивает )xE4 - // предполагаю, что это байт конфига кондиционера (5 бит - инвертер), (2 бит - периодический мпульсный сигнал,период пимерно 500 сек) - // БАЙТ 3 - bool power:1; - bool sleep:1; - bool louver_V:1; - uint8_t louver_H:2; // у шторок лево-право, почему то два бита - uint8_t mode:3; // enum { AC_BIG_MODE_AUTO = 0, - // AC_BIG_MODE_COOL = 1, - // AC_BIG_MODE_DRY = 2, - // AC_BIG_MODE_HEAT = 4, - // AC_BIG_MODE_FAN = 6} - // - // - // Встречались такие значения: - // 0x04 100 - сплит выключен, до этого работал (статус держится 1 час после выкл.) - // 0x05 101 - режим AUTO - // 0x24 100100 - режим OFF - // 0x25 100101 - режим COOL - // 0x39 111001 - ?? - // 0x45 1000101 - режим DRY - // 0x85 10000101 - режим HEAT - // 0xC4 11000100 - режим OFF, выключен давно, зима - // 0xC5 11000101 - режим FAN - // Brokly: - // Встречались такие значения : - // 0x00 00000000 - OFF - // 0x01 00000001 - AUTO // режим авто, нет отдельного бита - // 0x41 1000001 - DRY - // 0x21 100001 - COOL - // 0x81 10000001 - HEAT - // 0x85 10000101 - HEAT+шторки верх-низ - // 0x99 10011001 - HEAT+шторки влево вправо - // 0xC1 11000001 - FAN // 7 и 6 бит связаны - // 0x80 10000000 - продувка после переключения из HEAT в OFF - // 0xC5 11000101 - FAN+шторки верх-низ - // 0xDD 11011101 - FAN+шторки лево-право/верх-низ - // 0xD9 11011001 - FAN+шторки лево-право - // 0xD8 11011000 - из FAN+шторки лево-право в OFF - // 0x39 111001 - COOL+шторки лево-право - // Очевидно битовые, но связные, поля, предположительные зависимости - // ВНИМАНИЕ : режимы номинальны, например в режиме АВТО нагрев или охлаждение не отображаются - // 7+6+5 4+3 2 1 0 - // MODE Louv_L Louv_H SLEEP ON/OFF - // - // ФУНКЦМЯ CLEEN, HEALTH, ANTIFUNGUS на данный байт не влияют - // - // #define AC_BIG_MASK_MODE b11100000 - // enum { AC_BIG_MODE_DRY = 0x40, - // AC_BIG_MODE_COOL = 0x20, - // AC_BIG_MODE_HEAT = 0x80, - // AC_BIG_MODE_FAN = 0xC0} - // #define AC_BIG_MASK_MODE b00011100 - // enum { AC_BIG_LOUVERS_H = 0x04, - // AC_BIG_LOUVERS_L = 0x18, - // AC_BIG_LOUVERS_BOTH = 0x1C} - // #define AC_BIG_MASK_POWER b00000001 - // #define AC_BIG_MASK_SLEEP b00000010 - // #define AC_BIG_MASK_COOL b00100000 - // - // БАЙТ 4 - uint8_t reserv40:4; // 8 - bool needDefrost:1; // 5 бит начало разморозки(накопление тепла) - bool defrostMode:1; // 6 бит режим разморозки внешнего блока (прогрев испарителя) - bool reserv41:1; // для RoyalClima18HNI: режим разморозки внешнего блока - 0x20, в других случаях 0x00 - bool cleen:1; // 8 бит CLEAN - - // Для кондея старт-стоп - // x xx - // C5 11000101 - // C4 11000100 - // 85 10000101 - // 84 10000100 - // 3D 00111101 - // 3C 00111100 - // 25 00100101 - // 24 00100100 - // 5 00000101 - // 4 00000100 - - - // БАЙТ5 - uint8_t realFanSpeed:3; // Brokly: Energolux Bern - подтверждаю, ВАЖНО !!!! Это реальная скорость фена - uint8_t reserv30:5; // та которая в данный момент. Например может быть установлен нагрев со скоростью - // вентилятора HI, но кондей еще не произвел достаточно тепла, и крутит на LOW, - // Тут будет отображаться LOW - // fanSpeed: OFF=0x00, MUTE=0x01, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07 - // в дежурных пакетах тут похоже что-то другое - // БАЙТ6 - bool reserv60:1; - uint8_t fanPWM:7; // скорость шима вентилятора - // 126...128 - turbo - // 100...113 - hi - // 84...85 - mid - // 59...62 - low - // 0 - off - // - // БАЙТ7 - uint8_t ambient_temperature_int; // Brokly: ПОДТВЕРЖДАЮ это точно показания датчика под крышкой внутреннего блока, - // физически доступен для пользователя - // целая часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // перевод по формуле T = Тin - 0x20 + Tid/10 - // где - // Tin - целая часть температуры - // Tid - десятичная часть температуры - - // В ВЫКЛЮЧЕНОМ СОСТОНИИ занчение 7 8 9 10 равны !!!!!! - // А значит это термодатчики внутри внутреннего блока !!!!!! - - // БАЙТ8 - uint8_t zero3; // Brokly: полностью повторяет значение 9 байта - - // БАЙТ9 - uint8_t in_temperature_int; // Brokly: скорее всего это действительно какая то температура или дельта температур - // СКОРЕЕ ВСЕГО ЭТО ТЕМПЕРАТУРА ПОДАЧИ !!!!!! При охлаждении - холодная, при нагреве теплая - - // холоднее или горячее температуры в команте. В выключеном состоянии стремится к комнатной темп. - // у меня на трех инверторных кондиционерах Energolux серии Bern - // в выключеном состоянии значение этого байта находится на уровне 57-68 (мощность 0%) - // зависит от мощности работы компрессора (измерения при 12гр на улице) - // в режиме охлаждения уменьшается и при мощности 47% = 40 / 73% = 38 - // в режиме нагрева увеличивается и при мощности 47% = 70 / 73% = 75 / 84% = 84 - // изменение этого значения более вялое, с западыванием относительно изменения мощности - // видимо является реакцией (следствием работы) на изменение мощности инвертора - // учитывая стиль записи температур имеет смысл рассматривать это значение как увеличенное на 0x20 - - - - // этот байт как-то связан с температурой во внешнем блоке. Требуются дополнительные исследования. - // При выключенном сплите характер изменения значения примерно соответствует изменению температуры на улице. - // При включенном сплите значение может очень сильно скакать. - // По схеме wiring diagram сплит-системы, во внешнем блоке есть термодатчик, отслеживающий температуру испарителя. - // Возможно, этот байт как раз и отражает изменение температуры на испарителе. - // Но я не смог разобраться, как именно перевести эти значения в градусы. - // Кроме того, зимой даже в минусовую температуру этот байт не уходит ниже 0x33 по крайней мере - // для температур в диапазоне -5..-10 градусов Цельсия. - // БАЙТ10 - uint8_t zero4; // Brokly: полностью повторяет значение 9 байта - - // БАЙТ11 - uint8_t zero5; // всегда = 100 (0x64) - - // БАЙТ12 - uint8_t outdoor_temperature; // Brokly Energolux Bern: Внешняя температура формула T=БАЙТ12 - 0x20 - // Датчик на радиаторе внешнего блока, доступен для пользователя, без разборки блока - // температура внешнего теплообменника влияет на это значение (при работе на обогрев - понижает, при охлаждении или при разморозке - повышает) - // для RoyalClima18HNI: похоже на какую-то температуру, точно неизвестно - - // БАЙТ13 - uint8_t out_temperature_int; // всегда 0x00 - // для RoyalClima18HNI: 0x20 - // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ13 - 0x20 - // При охлаждении растет, при нагреве падает, можно делать вывод о режиме COOL или HEAT - // ПОХОЖЕ НА ТЕМПЕРАТУРУ ОБРАТКИ !!! - - // БАЙТ14 - uint8_t strange_temperature_int;// всегда 0x00 - // для RoyalClima18HNI: 0x20 - // Brokly Energolux Bern: похоже не какой то Термодатчик T=БАЙТ14 - 0x20 - // показания РАСТЕТ ПРИ ВКЛЮЧЕНИИ ИНВЕРТОРА, при выключении падают до комнатной - // от режима охлаждения или нагрева не зависит !!! - - - // БАЙТ15 - uint8_t zero9; // всегда 0x00, Brokly: Energolux Bern, всегда 0x39 111001 - // БАЙТ16 - uint8_t invertor_power; // МОщность инвертера (Brokly: подтверждаю) - // для RoyalClima18HNI: мощность инвертора (от 0 до 100) в % - // например, разморозка внешнего блока происходит при 80% - // БАЙТ17 - uint8_t zero11; // всегда 0x00 - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 22, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика достаточно плавна - - // БАЙТ18 - uint8_t zero12; // - // Brokly: Energolux Bern : наложение на показания инвертора (от 144 до 174, когда инвертор отключен - // показания немного скачут в районе 149...154, при включении инвертора быстро растет, при выключении - // моментально падает до 149...154, бывают опускания ниже этих значений до 144, чаще в момент первоначального - // включения инвертора, а потом вверх, не всегда. При включении уходит в 0 на одну посылку - - // БАЙТ19 - uint8_t zero13; // Brokly: Energolux Bern : включение 144 -> 124 -> 110 далее все время держим 110 - - // БАЙТ20 - uint8_t zero14; // - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 45, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика дрожащая нестабильная - // колебания в районе +-2...4 единицы - // БАЙТ21 - uint8_t zero15; // всегда 0x00 Brokly: подтверждаю - - // БАЙТ22 - uint8_t zero16; // всегда 0x00 Brokly: подтверждаю - - // БАЙТ23 - uint8_t ambient_temperature_frac:4; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // подробнее смотреть ambient_temperature_int - // для RoyalClima18HNI: старшие 4 бита - 0x2 - uint8_t reserv023:4; -}; - -// тело малого информационного пакета -struct packet_small_info_body_t { - uint8_t byte_01; // не расшифрован, всегда 0x01 - uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x11); - // в пакетах сплита другие варианты не встречаются - // в отправляемых wifi-модулем пакетах тут может быть 0x01, если требуется установить режим работы - uint8_t target_temp_int_and_v_louver; // целая часть целевой температуры и положение вертикальных жалюзи - // три младших бита - положение вертикальных жалюзи - // если они все = 0, то вертикальный SWING включен - // если они все = 1, то выключен вертикальный SWING - // протокол универсильный, другие комбинации битов могут задавать какие-то положения - // вертикальных жалюзи, но у меня на пульте таких возможностей нет, надо экспериментировать. - // пять старших бит - целая часть целевой температуры - // температура определяется по формуле: - // 8 + (target_temp_int_and_v_louver >> 3) + (0.5 * (target_temp_frac >> 7)) - uint8_t h_louver; // старшие 3 бита - положение горизонтальных жалюзи, остальное не изучено и всегда было 0 - // если все 3 бита = 0, то горизонтальный SWING включен - // если все 3 бита = 1, то горизонтальный SWING отключен - // надо изучить другие комбинации - uint8_t target_temp_frac; // старший бит - дробная часть целевой температуры - // остальные биты до конца не изучены: - // бит 6 был всегда 0 - // биты 0..5 растут на 1 каждую минуту, возможно внутренний таймер для включения/выключения по времени - uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны - // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 - uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны - // БФЙТ 7 - uint8_t mode; // режим работы сплита: - // AUTO : bits[7, 6, 5] = [0, 0, 0] - // COOL : bits[7, 6, 5] = [0, 0, 1] - // DRY : bits[7, 6, 5] = [0, 1, 0] - // HEAT : bits[7, 6, 5] = [1, 0, 1] - // FAN : bits[7, 6, 5] = [1, 1, 1] - // Sleep function : bit 2 = 1 - // iFeel function : bit 3 = 1 - uint8_t zero1; // всегда 0x00 - uint8_t zero2; // всегда 0x00 - uint8_t status; // бит 5 = 1: включен, обычный режим работы (когда можно включить нагрев, охлаждение и т.п.) - // бит 2 = 1: режим самоочистки, должен запускаться только при бит 5 = 0 - // бит 0 и бит 1: активация режима ионизатора воздуха (не проверен, у меня его нет) - uint8_t zero3; // всегда 0x00 - uint8_t display_and_mildew; // бит4 = 1, чтобы погасить дисплей на внутреннем блоке сплита - // бит3 = 1, чтобы включить функцию "антиплесень" (после отключения как-то прогревает или просушивает теплообменник, чтобы на нем не росла плесень) - uint8_t zero4; // всегда 0x00 - uint8_t target_temp_frac2; // дробная часть целевой температуры, может быть только 0x00 и 0x05 - // при установке температуры тут 0x00, а заданная температура передается в target_temp_int_and_v_louver и target_temp_frac - // после установки сплит в информационных пакетах тут начинает показывать дробную часть - // не очень понятно, зачем так сделано -}; - -//**************************************************************************************************************************************************** -//*************************************************** ПАРАМЕТРЫ РАБОТЫ КОНДИЦИОНЕРА ****************************************************************** -//**************************************************************************************************************************************************** - -// для показаний о реальной скорости фена из большого пакета -enum ac_realFan : uint8_t { AC_REAL_FAN_OFF = 0x00, AC_REAL_FAN_MUTE = 0x01, AC_REAL_FAN_LOW = 0x02, AC_REAL_FAN_MID = 0x04, - AC_REAL_FAN_HIGH = 0x06, AC_REAL_FAN_TURBO = 0x07, AC_REAL_FAN_UNTOUCHED = 0xFF }; - -// для всех параметров ниже вариант X_UNTOUCHED = 0xFF означает, что этот параметр команды должен остаться тот, который уже установлен -// питание кондиционера -#define AC_POWER_MASK 0b00100000 -enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTOUCHED = 0xFF }; - -// режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF -#define AC_CLEAN_MASK 0b00000100 -enum ac_clean : uint8_t { AC_CLEAN_OFF = 0x00, AC_CLEAN_ON = 0x04, AC_CLEAN_UNTOUCHED = 0xFF }; - -// для включения ионизатора нужно установить второй бит в байте -// по результату этот бит останется установленным, но кондиционер еще и установит первый бит -#define AC_HEALTH_MASK 0b00000010 -enum ac_health : uint8_t { AC_HEALTH_OFF = 0x00, AC_HEALTH_ON = 0x02, AC_HEALTH_UNTOUCHED = 0xFF }; - -// Статус ионизатора. Если бит поднят, то обнаружена ошибка ключения ионизатора -#define AC_HEALTH_STATUS_MASK 0b00000001 -enum ac_health_status : uint8_t { AC_HEALTH_STATUS_OFF = 0x00, AC_HEALTH_STATUS_ON = 0x01, AC_HEALTH_STATUS_UNTOUCHED = 0xFF }; - -// целевая температура -#define AC_TEMP_TARGET_INT_PART_MASK 0b11111000 -#define AC_TEMP_TARGET_FRAC_PART_MASK 0b10000000 - -// задержка отключения кондиционера -#define AC_TIMER_MINUTES_MASK 0b00111111 -#define AC_TIMER_HOURS_MASK 0b00011111 - -// включение таймера сна -#define AC_TIMER_MASK 0b01000000 -enum ac_timer : uint8_t {AC_TIMER_OFF = 0x00, AC_TIMER_ON = 0x40, AC_TIMER_UNTOUCHED = 0xFF}; - -// основные режимы работы кондиционера -#define AC_MODE_MASK 0b11100000 -enum ac_mode : uint8_t { AC_MODE_AUTO = 0x00, AC_MODE_COOL = 0x20, AC_MODE_DRY = 0x40, AC_MODE_HEAT = 0x80, AC_MODE_FAN = 0xC0, AC_MODE_UNTOUCHED = 0xFF }; - -// Ночной режим (SLEEP). Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. -// COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. -// HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. -// Восстанавливается ли температура через 7 часов при отключении режима - не понятно. -#define AC_SLEEP_MASK 0b00000100 -enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTOUCHED = 0xFF }; - -// функция iFeel - поддерживате температуру по датчику в пульте ДУ, а не во внутреннем блоке кондиционера -#define AC_IFEEL_MASK 0b00001000 -enum ac_ifeel : uint8_t { AC_IFEEL_OFF = 0x00, AC_IFEEL_ON = 0x08, AC_IFEEL_UNTOUCHED = 0xFF }; - -// Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V -#define AC_LOUVERV_MASK 0b00000111 -enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_UPDOWN = 0x00, AC_LOUVERV_OFF = 0x07, AC_LOUVERV_UNTOUCHED = 0xFF }; - -// Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H -#define AC_LOUVERH_MASK 0b11100000 -enum ac_louver_H : uint8_t { AC_LOUVERH_SWING_LEFTRIGHT = 0x00, AC_LOUVERH_OFF = 0xE0, AC_LOUVERH_UNTOUCHED = 0xFF }; - -struct ac_louver { - ac_louver_H louver_h; - ac_louver_V louver_v; -}; - -// скорость вентилятора -#define AC_FANSPEED_MASK 0b11100000 -enum ac_fanspeed : uint8_t { AC_FANSPEED_HIGH = 0x20, AC_FANSPEED_MEDIUM = 0x40, AC_FANSPEED_LOW = 0x60, AC_FANSPEED_AUTO = 0xA0, AC_FANSPEED_UNTOUCHED = 0xFF }; - -// TURBO работает только в режимах COOL и HEAT -#define AC_FANTURBO_MASK 0b01000000 -enum ac_fanturbo : uint8_t { AC_FANTURBO_OFF = 0x00, AC_FANTURBO_ON = 0x40, AC_FANTURBO_UNTOUCHED = 0xFF }; - -// MUTE работает только в режиме FAN. В режиме COOL кондей команду принимает, но MUTE не устанавливается -#define AC_FANMUTE_MASK 0b10000000 -enum ac_fanmute : uint8_t { AC_FANMUTE_OFF = 0x00, AC_FANMUTE_ON = 0x80, AC_FANMUTE_UNTOUCHED = 0xFF }; - -// включение-выключение дисплея на корпусе внутреннего блока -#define AC_DISPLAY_MASK 0b00010000 -enum ac_display : uint8_t { AC_DISPLAY_OFF = 0x00, AC_DISPLAY_ON = 0x10, AC_DISPLAY_UNTOUCHED = 0xFF }; - -// включение-выключение функции "Антиплесень". -// По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. Уличный блок при этом гудит и тарахтит. -// Возможно, прогревается теплообменник для высыхания. Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. -#define AC_MILDEW_MASK 0b00001000 -enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_UNTOUCHED = 0xFF }; - -// маска счетчика минут прошедших с последней команды -#define AC_MIN_COUTER 0b00111111 // - -// настройка усреднения фильтра температуры. Это значение - взнос нового измерения -// в усредненные показания в процентах -#define OUTDOOR_FILTER_PESCENT 0.2 - -/** команда для кондиционера - * - * ВАЖНО! В коде используется копирование команд простым присваиванием. - * Если в структуру будут введены указатели, то копирование надо будет изменить! -*/ - -// данные структур содержат настройку, специально вынес в макрос -#define AC_COMMAND_BASE float temp_target;\ - ac_power power;\ - ac_clean clean;\ - ac_health health;\ - ac_mode mode;\ - ac_sleep sleep;\ - ac_louver louver;\ - ac_fanspeed fanSpeed;\ - ac_fanturbo fanTurbo;\ - ac_fanmute fanMute;\ - ac_display display;\ - ac_mildew mildew;\ - ac_timer timer;\ - uint8_t timer_hours;\ - uint8_t timer_minutes;\ - bool temp_target_matter - -// чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше -// из-за такого приема нужно контролировать размер копируемых данных руками -#define AC_COMMAND_BASE_SIZE 20 - -struct ac_command_t { -/* - ac_power power; - ac_clean clean; - ac_health health; // включение ионизатора - ac_mode mode; - ac_sleep sleep; - ac_louver louver; - ac_fanspeed fanSpeed; - ac_fanturbo fanTurbo; - ac_fanmute fanMute; - ac_display display; - ac_mildew mildew; - ac_timer timer; - uint8_t timer_hours; - uint8_t timer_minutes; - float temp_target; - bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную -*/ - AC_COMMAND_BASE; - ac_health_status health_status; - float temp_ambient; // внутренняя температура - int8_t temp_outdoor; // внешняя температура - int8_t temp_inbound; // температура входящая - int8_t temp_outbound; // температура исходящая - int8_t temp_strange; // непонятная температура, понаблюдаем - ac_realFan realFanSpeed; // текущая скорость вентилятора - uint8_t invertor_power; // мощность инвертора - uint8_t pressure; // предположительно давление - bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) - ac_ifeel iFeel; -}; - -// структура для сохранения данных -struct ac_save_command_t { - AC_COMMAND_BASE; -}; - -// номера сохранений пресетов -enum store_pos : uint8_t { - POS_MODE_AUTO = 0, - POS_MODE_COOL, - POS_MODE_DRY, - POS_MODE_HEAT, - POS_MODE_FAN, - POS_MODE_OFF}; - -typedef ac_command_t ac_state_t; // текущее состояние параметров кондея можно хранить в таком же формате, как и комманды - -//**************************************************************************************************************************************************** -//************************************************ КОНЕЦ ПАРАМЕТРОВ РАБОТЫ КОНДИЦИОНЕРА ************************************************************** -//**************************************************************************************************************************************************** - - -/***************************************************************************************************************************************************** - * структуры и типы для последовательности команд - ***************************************************************************************************************************************************** - * - * Последовательность команд позволяет выполнить несколько последовательных команд с контролем получаемых в ответ пакетов. - * Если требуется, в получаемых в ответ пакетах можно контролировать значение любых байт. - * Для входящего пакета байт, значение которого не проверяется, должен быть установлен в AC_SEQUENCE_ANY_BYTE. - * Контроль возможен только для входящих пакетов, исходящие отправляются "как есть". - * - * Для исходящих пакетов значения CRC могут не рассчитываться, контрольная сумма будет рассчитана автоматически. - * Для входящих пакетов значение CRC также можно не рассчитывать, установив байты CRC в AC_SEQUENCE_ANY_BYTE, - * так как контроль CRC для получаемых пакетов выполняется автоматически при получении. - * - * Для входящих пакетов в последовательности можно указать таймаут. Если таймаут равен 0, то используется значение AC_SEQUENCE_DEFAULT_TIMEOUT. - * Если в течение указанного времени подходящий пакет не будет получен, то последовательность прерывается с ошибкой. - * Пинг-пакеты в последовательности игнорируются. - * - * Пауза в последовательности задается значением timeout элемента AC_DELAY. Никакие другие параметры такого элемента можно не заполнять. - * - **/ -// максимальная длина последовательности; больше вроде бы не требовалось -#define AC_SEQUENCE_MAX_LEN 0x0F - -// в пакетах никогда не встречалось значение 0xFF (только в CRC), поэтому решено его использовать как признак не важного значение байта -//#define AC_SEQUENCE_ANY_BYTE 0xFF - -// дефолтный таймаут входящего пакета в миллисекундах -// если для входящего пакета в последовательности указан таймаут 0, то используется значение по-умолчанию -// если нужный пакет не поступил в течение указанного времени, то последовательность прерывается с ошибкой -// Brokly: пришлось увеличить -#define AC_SEQUENCE_DEFAULT_TIMEOUT 580 - -enum sequence_item_type_t : uint8_t { - AC_SIT_NONE = 0x00, // пустой элемент последовательности - AC_SIT_DELAY = 0x01, // пауза в последовательности на нужное количество миллисекунд - AC_SIT_FUNC = 0x02 // рабочий элемент последовательности -}; - -// тип пакета в массиве последовательности -// информирует о том, что за пакет лежит в поле packet элемента последовательности -enum sequence_packet_type_t : uint8_t { - AC_SPT_CLEAR = 0x00, // пустой пакет - AC_SPT_RECEIVED_PACKET = 0x01, // полученный пакет - AC_SPT_SENT_PACKET = 0x02 // отправленный пакет -}; - -/** элемент последовательности - * Поля item_type, func, timeout и cmd устанавливаются ручками и задают параметры выполнения шага последовательности. - * Поля msec, packet_type и packet заполняются движком при обработке последовательности. - **/ -struct sequence_item_t { - sequence_item_type_t item_type; // тип элемента последовательности - bool (AirCon::*func)(); // указатель на функцию, отрабатывающую шаг последовательности - uint16_t timeout; // допустимый таймаут в ожидании пакета (применим только для входящих пакетов) - ac_command_t cmd; // новое состояние сплита, нужно для передачи кондиционеру команд - //******* поля ниже заполняются функциями обработки последовательности *********** - uint32_t msec; // время старта текущего шага последовательности (для входящего пакета и паузы) - sequence_packet_type_t packet_type; // тип пакета (входящий, исходящий или вовсе не пакет) - packet_t packet; // данные пакета -}; -/*****************************************************************************************************************************************************/ - - -class AirCon : public esphome::Component, public esphome::climate::Climate { - private: - - // массив для сохранения данных глобальных персетов - ac_save_command_t global_presets[POS_MODE_OFF+1]; - #if defined(ESP32) - // тут будем хранить данные глобальных пресетов во флеше - // ВНИМАНИЕ на данный момент 22.05.22 ESPHOME 20022.5.0 имеет ошибку - // траблтикет: https://github.com/esphome/issues/issues/3298 - // из-за этого сохранение в энергонезависимую память не работает !!! - ESPPreferenceObject storage = global_preferences->make_preference(this->get_object_id_hash(), true); - #endif - // настройка-ключ, для включения сохранения - восстановления настроек каждого - // режима работы в отдельности, то есть каждый режим работы имеет свои настройки - // температуры, шторок, скорости вентилятора, пресетов - bool _store_settings = false; - // флаги для сохранения пресетов - bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено - - // время последнего запроса статуса у кондея - uint32_t _dataMillis; - // периодичность обновления статуса кондея, по дефолту AC_STATES_REQUEST_INTERVAL - uint32_t _update_period = Constants::AC_STATES_REQUEST_INTERVAL; - - // надо ли отображать текущий режим работы внешнего блока - // в режиме нагрева, например, кондиционер может как греть воздух, так и работать в режиме вентилятора, если целевая темпреатура достигнута - // по дефолту показываем - bool _show_action = true; - - // как отрабатывается включание-выключение дисплея. - // если тут false, то 1 в соответствующем бите включает дисплей, а 0 выключает. - // если тут true, то 1 потушит дисплей, а 0 включит. - bool _display_inverted = false; - - // флаг типа кондиционера инвертор - true, ON/OFF - false, начальная установка false - // в таком режиме точность и скорость определения реального состояния системы для инвертора, - // будет работать, но будет ниже, переменная устанавливается при первом получении большого пакета; - // если эта переменная установлена, то режим работы не инверторного кондиционера будет распознаваться - // как "в простое" (IDLE) - bool _is_invertor = false; - - // поддерживаемые кондиционером опции - std::set _supported_modes{}; - std::set _supported_swing_modes{}; - std::set _supported_presets{}; - std::set _supported_custom_presets{}; - std::set _supported_custom_fan_modes{}; - - // состояние конечного автомата - acsm_state _ac_state = ACSM_IDLE; - - // текущее состояние задаваемых пользователем параметров системы - ac_state_t _current_ac_state; - - // флаг подключения к UART - bool _hw_initialized = false; - // указатель на UART, по которому общаемся с кондиционером - esphome::uart::UARTComponent *_ac_serial; - - // UART wrappers: peek - int peek() { - uint8_t data; - if (!_ac_serial->peek_byte(&data)) return -1; - return data; - } - - // UART wrappers: read - int read() { - uint8_t data; - if (!_ac_serial->read_byte(&data)) return -1; - return data; - } - - // флаг обмена пакетами с кондиционером (если проходят пинги, значит есть коннект) - bool _has_connection = false; - - // входящий и исходящий пакеты - packet_t _inPacket; - packet_t _outPacket; - - // пакет для тестирования всякой фигни - packet_t _outTestPacket; - - // последовательность пакетов текущий шаг в последовательности - sequence_item_t _sequence[AC_SEQUENCE_MAX_LEN]; - uint8_t _sequence_current_step; - - // флаг успешного выполнения стартовой последовательности команд - bool _startupSequenceComlete = false; - - // очистка последовательности команд - void _clearSequence(){ - for (uint8_t i = 0; i < AC_SEQUENCE_MAX_LEN; i++) { - _sequence[i].item_type = AC_SIT_NONE; - _sequence[i].func = nullptr; - _sequence[i].timeout = 0; - _sequence[i].msec = 0; - _sequence[i].packet_type = AC_SPT_CLEAR; - _clearPacket(&_sequence[i].packet); - _clearCommand(&_sequence[i].cmd); - } - _sequence_current_step = 0; - } - - // проверяет, есть ли свободные шаги в последовательности команд - bool _hasFreeSequenceStep(){ - return (_getNextFreeSequenceStep() < AC_SEQUENCE_MAX_LEN); - } - - // возвращает индекс первого пустого шага последовательности команд - uint8_t _getNextFreeSequenceStep(){ - for (size_t i = 0; i < AC_SEQUENCE_MAX_LEN; i++) { - if (_sequence[i].item_type == AC_SIT_NONE){ - return i; - } - } - // если свободных слотов нет, то возвращаем значение за пределом диапазона - return AC_SEQUENCE_MAX_LEN; - } - - // возвращает количество свободных шагов в последовательности - uint8_t _getFreeSequenceSpace() { - return (AC_SEQUENCE_MAX_LEN - _getNextFreeSequenceStep()); - } - - // добавляет шаг в последовательность команд - // возвращает false, если не нашлось места для шага - bool _addSequenceStep(const sequence_item_type_t item_type, bool (AirCon::*func)() = nullptr, ac_command_t *cmd = nullptr, uint16_t timeout = AC_SEQUENCE_DEFAULT_TIMEOUT){ - if (!_hasFreeSequenceStep()) return false; // если места нет, то уходим - if (item_type == AC_SIT_NONE) return false; // глупость какая-то, уходим - if ((item_type == AC_SIT_FUNC) && (func == nullptr)) return false; // должна быть передана функция для такого типа шага - if ((item_type != AC_SIT_DELAY) && (item_type != AC_SIT_FUNC)){ - // какой-то неизвестный тип - _debugMsg(F("_addSequenceStep: unknown sequence item type = %u"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__, item_type); - return false; - } - - uint8_t step = _getNextFreeSequenceStep(); - - _sequence[step].item_type = item_type; - - // если задержка нулевая, то присваиваем дефолтную задержку - if (timeout == 0) timeout = AC_SEQUENCE_DEFAULT_TIMEOUT; - _sequence[step].timeout = timeout; - - _sequence[step].func = func; - if (cmd != nullptr) _sequence[step].cmd = *cmd; // так как в структуре команды только простые типы, то можно вот так присваивать - - return true; - } - - // добавляет в последовательность шаг с задержкой - bool _addSequenceDelayStep(uint16_t timeout){ - return this->_addSequenceStep(AC_SIT_DELAY, nullptr, nullptr, timeout); - } - - // добавляет в последовательность функциональный шаг - bool _addSequenceFuncStep(bool (AirCon::*func)(), ac_command_t *cmd = nullptr, uint16_t timeout = AC_SEQUENCE_DEFAULT_TIMEOUT){ - return this->_addSequenceStep(AC_SIT_FUNC, func, cmd, timeout); - } - - // выполняет всю логику очередного шага последовательности команд - void _doSequence(){ - if (!hasSequence()) return; - - // если шаг уже максимальный из возможных - if (_sequence_current_step >= AC_SEQUENCE_MAX_LEN) { - // значит последовательность закончилась, надо её очистить - // при очистке последовательности будет и _sequence_current_step обнулён - _debugMsg(F("Sequence [step %u]: maximum step reached"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _clearSequence(); - return; - } - - // смотрим тип текущего элемента в последовательности - switch (_sequence[_sequence_current_step].item_type) { - case AC_SIT_FUNC: { - // если указатель на функцию пустой, то прерываем последовательность - if (_sequence[_sequence_current_step].func == nullptr) { - _debugMsg(F("Sequence [step %u]: function pointer is NULL, sequence broken"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _clearSequence(); - return; - } - - // сохраняем время начала паузы - if (_sequence[_sequence_current_step].msec == 0) { - _sequence[_sequence_current_step].msec = millis(); - _debugMsg(F("Sequence [step %u]: step started"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - } - - // если таймаут не указан, берем значение по-умолчанию - if (_sequence[_sequence_current_step].timeout == 0 ) _sequence[_sequence_current_step].timeout = AC_SEQUENCE_DEFAULT_TIMEOUT; - - // если время вышло, то отчитываемся в лог и очищаем последовательность - if (millis() - _sequence[_sequence_current_step].msec >= _sequence[_sequence_current_step].timeout) { - _debugMsg(F("Sequence [step %u]: step timed out (it took %u ms instead of %u ms)"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step, millis() - _sequence[_sequence_current_step].msec, _sequence[_sequence_current_step].timeout); - _clearSequence(); - return; - } - - // можно вызывать функцию - // она самомтоятельно загружает отправляемые/полученные пакеты в packet последовательности - // а также самостоятельно увеличивает счетчик шагов последовательности _sequence_current_step - // единственное исключение - таймауты - if (!(this->*_sequence[_sequence_current_step].func)()) { - _debugMsg(F("Sequence [step %u]: error was occur in step function"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step, millis() - _sequence[_sequence_current_step].msec); - _clearSequence(); - return; - } - break; - } - - case AC_SIT_DELAY: { // это пауза в последовательности - // пауза задается параметром timeout элемента последовательности - // начало паузы сохраняется в параметре msec - - // сохраняем время начала паузы - if (_sequence[_sequence_current_step].msec == 0) { - _sequence[_sequence_current_step].msec = millis(); - _debugMsg(F("Sequence [step %u]: begin delay (%u ms)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step, _sequence[_sequence_current_step].timeout); - } - - // если время вышло, то переходим на следующий шаг - if (millis() - _sequence[_sequence_current_step].msec >= _sequence[_sequence_current_step].timeout) { - _debugMsg(F("Sequence [step %u]: delay culminated (plan = %u ms, fact = %u ms)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step, _sequence[_sequence_current_step].timeout, millis() - _sequence[_sequence_current_step].msec); - _sequence_current_step++; - } - break; - } - - case AC_SIT_NONE: // шаги закончились - default: // или какой-то мусор в последовательности - // надо очистить последовательность и уходить - _debugMsg(F("Sequence [step %u]: sequence complete"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _clearSequence(); - break; - } - } - - // заполняет структуру команды нейтральными значениями - void _clearCommand(ac_command_t * cmd){ - cmd->clean = AC_CLEAN_UNTOUCHED; - cmd->display = AC_DISPLAY_UNTOUCHED; - cmd->fanMute = AC_FANMUTE_UNTOUCHED; - cmd->fanSpeed = AC_FANSPEED_UNTOUCHED; - cmd->fanTurbo = AC_FANTURBO_UNTOUCHED; - cmd->health = AC_HEALTH_UNTOUCHED; - cmd->health_status = AC_HEALTH_STATUS_UNTOUCHED; - cmd->iFeel = AC_IFEEL_UNTOUCHED; - cmd->louver.louver_h = AC_LOUVERH_UNTOUCHED; - cmd->louver.louver_v = AC_LOUVERV_UNTOUCHED; - cmd->mildew = AC_MILDEW_UNTOUCHED; - cmd->mode = AC_MODE_UNTOUCHED; - cmd->power = AC_POWER_UNTOUCHED; - cmd->sleep = AC_SLEEP_UNTOUCHED; - cmd->timer = AC_TIMER_UNTOUCHED; - cmd->timer_hours = 0; - cmd->timer_minutes = 0; - cmd->temp_target = 0; - cmd->temp_target_matter = false; - cmd->temp_ambient = 0; - cmd->temp_outdoor = 0; - cmd->temp_inbound = 0; - cmd->temp_outbound = 0; - cmd->temp_strange = 0; - cmd->realFanSpeed = AC_REAL_FAN_UNTOUCHED; - }; - - // очистка буфера размером AC_BUFFER_SIZE - void _clearBuffer(uint8_t * buf){ - memset(buf, 0, AC_BUFFER_SIZE); - } - - // очистка структуры пакета по указателю - void _clearPacket(packet_t * pckt){ - if (pckt == nullptr) { - _debugMsg(F("Clear packet error: pointer is NULL!"), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return; - } - pckt->crc = nullptr; - pckt->header = (packet_header_t *)(pckt->data); // заголовок же всегда стартует с начала пакета - pckt->msec = 0; - pckt->bytesLoaded = 0; - pckt->body = nullptr; - _clearBuffer(pckt->data); - } - - // очистка входящего пакета - void _clearInPacket(){ - _clearPacket(&_inPacket); - } - - // очистка исходящего пакета - void _clearOutPacket(){ - _clearPacket(&_outPacket); - _outPacket.header->start_byte = AC_PACKET_START_BYTE; // для исходящего сразу ставим стартовый байт - _outPacket.header->wifi = AC_PACKET_ANSWER; // для исходящего пакета сразу ставим признак ответа - } - - // копирует пакет из одной структуры в другую с корректным переносом указателей на заголовки и т.п. - bool _copyPacket(packet_t *dest, packet_t *src){ - if (dest == nullptr) return false; - if (src == nullptr) return false; - - dest->msec = src->msec; - dest->bytesLoaded = src->bytesLoaded; - memcpy(dest->data, src->data, AC_BUFFER_SIZE); - dest->header = (packet_header_t *)&dest->data; - if (dest->header->body_length > 0) dest->body = &dest->data[AC_HEADER_SIZE]; - dest->crc = (packet_crc_t *)&dest->data[AC_HEADER_SIZE + dest->header->body_length]; - - return true; - } - - // устанавливает состояние конечного автомата - // можно и напрямую устанавливать переменную, но для целей отладки лучше так - void _setStateMachineState(acsm_state state = ACSM_IDLE){ - if (_ac_state == state) return; // состояние не меняется - - _ac_state = state; - - switch (state) { - case ACSM_IDLE: - _debugMsg(F("State changed to ACSM_IDLE."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - case ACSM_RECEIVING_PACKET: - _debugMsg(F("State changed to ACSM_RECEIVING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - case ACSM_PARSING_PACKET: - _debugMsg(F("State changed to ACSM_PARSING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - case ACSM_SENDING_PACKET: - _debugMsg(F("State changed to ACSM_SENDING_PACKET."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - - default: - _debugMsg(F("State changed to ACSM_IDLE by default. Given state is %02X."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, state); - _ac_state = ACSM_IDLE; - break; - } - } - - // состояние конечного автомата: IDLE - void _doIdleState(){ - // вначале нужно выполнить очередной шаг последовательности команд - _doSequence(); - - // Если нет входящих данных, значит можно отправить исходящий пакет, если он есть - if (_ac_serial->available() == 0) { - // если есть пакет на отправку, то надо отправлять - // вначале думал, что сейчас отправка пакетов тут не нужна, т.к. состояние ACSM_SENDING_PACKET устанавливается сразу в парсере пакетов - // но потом понял, что у нас пакеты уходят не только когда надо отвечать, но и мы можем быть инициаторами - // поэтому вызов отправки тут пригодится - if (_outPacket.msec > 0) _setStateMachineState(ACSM_SENDING_PACKET); - // иначе просто выходим - return; - }; - - if (this->peek() == AC_PACKET_START_BYTE) { - // если во входящий пакет что-то уже загружено, значит это какие-то ошибочные данные или неизвестные пакеты - // надо эту инфу вывалить в лог - if (_inPacket.bytesLoaded > 0){ - _debugMsg(F("Start byte received but there are some unparsed bytes in the buffer:"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - } - _clearInPacket(); - _inPacket.msec = millis(); - _setStateMachineState(ACSM_RECEIVING_PACKET); - //******************************************** экспериментальная секция ************************************************************* - // пробуем сократить время ответа с помощью прямых вызовов обработчиков, а не через состояние IDLE - //_doReceivingPacketState(); - // получилось всё те же 123 мсек. Только изредка падает до 109 мсек. Странно. - // логический анализатор показал примерно то же время от начала запроса до окончания ответа. - // запрос имеет длительность 18 мсек (лог.анализатор говорит 22,5 мсек). - // ответ имеет длительность 41 мсек по лог.анализатору. - // длительность паузы между запросом и ответом порядка 60 мсек. - // Скорее всего за один вызов _doReceivingPacketState не удается загрузить весь пакет (на момент вызова не все байы поступили в буфер UART) - // и поэтому программа отдает управление ESPHome для выполнения своих задач - // Стоит ли переделать код наоборот для непрерывного выполнения всё время, пока ожидается посылка - не знаю. Может быть такой риалтайм и не нужен. - //*********************************************************************************************************************************** - - } else { - while (_ac_serial->available() > 0) - { - // если наткнулись на старт пакета, то выходим из while - // если какие-то данные были загружены в буфер, то они будут выгружены в лог при загрузке нового пакета - if (this->peek() == AC_PACKET_START_BYTE) break; - - // читаем байт в буфер входящего пакета - _inPacket.data[_inPacket.bytesLoaded] = this->read(); - _inPacket.bytesLoaded++; - - // если буфер уже полон, надо его вывалить в лог и очистить - if (_inPacket.bytesLoaded >= AC_BUFFER_SIZE){ - _debugMsg(F("Some unparsed data on the bus:"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _clearInPacket(); - } - } - } - }; - - // состояние конечного автомата: ACSM_RECEIVING_PACKET - void _doReceivingPacketState(){ - while (_ac_serial-> available() > 0) { - // если в буфере пакета данных уже под завязку, то надо сообщить о проблеме и выйти - if (_inPacket.bytesLoaded >= AC_BUFFER_SIZE) { - _debugMsg(F("Receiver: packet buffer overflow!"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - return; - } - - _inPacket.data[_inPacket.bytesLoaded] = this->read(); - _inPacket.bytesLoaded++; - - // данных достаточно для заголовка - if (_inPacket.bytesLoaded == AC_HEADER_SIZE) { - // указатель заголовка установлен еще при обнулении пакета, его можно не трогать - //_inPacket.header = (packet_header_t *)(_inPacket.data); - - // уже знаем размер пакета и можем установить указатели на тело пакета и CRC - _inPacket.crc = (packet_crc_t *)&(_inPacket.data[AC_HEADER_SIZE + _inPacket.header->body_length]); - if (_inPacket.header->body_length > 0) _inPacket.body = &(_inPacket.data[AC_HEADER_SIZE]); - - _debugMsg(F("Header loaded: timestamp = %010u, start byte = %02X, packet type = %02X, body size = %02X"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.msec, _inPacket.header->start_byte, _inPacket.header->packet_type, _inPacket.header->body_length); - } - - // если все байты пакета загружены, надо его распарсить - // максимальный по размеру пакет будет упираться в размер буфера. если такой пакет здесь не уйдет на парсинг, - // то на следующей итерации будет ошибка о переполнении буфера, которая в начале цикла while - if (_inPacket.bytesLoaded == AC_HEADER_SIZE + _inPacket.header->body_length + 2) { - _debugMsg(F("Packet loaded: timestamp = %010u, start byte = %02X, packet type = %02X, body size = %02X, crc = [%02X, %02X]."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.msec, _inPacket.header->start_byte, _inPacket.header->packet_type, _inPacket.header->body_length, _inPacket.crc->crc[0], _inPacket.crc->crc[1]); - _debugMsg(F("Loaded %02u bytes for a %u ms."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _inPacket.bytesLoaded, (millis() - _inPacket.msec)); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - _setStateMachineState(ACSM_PARSING_PACKET); - return; - } - } - - // если пакет не загружен, а время вышло, то надо вернуться в IDLE - if (millis() - _inPacket.msec >= AC_PACKET_TIMEOUT) { - _debugMsg(F("Receiver: packet timed out!"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - return; - } - }; - - // состояние конечного автомата: ACSM_PARSING_PACKET - void _doParsingPacket(){ - if (!_checkCRC(&_inPacket)) { - _debugMsg(F("Parser: packet CRC fail!"), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_ERROR, __LINE__); - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - return; - } - - bool stateChangedFlag = false; // флаг, показывающий, изменилось ли состояние кондиционера - uint8_t stateByte = 0; // переменная для временного сохранения текущих параметров сплита для проверки их изменения - float stateFloat = 0.0; // переменная для временного сохранения текущих параметров сплита для проверки их изменения - - // вначале выводим полученный пакет в лог, чтобы он шел до информации об ответах и т.п. - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - - // разбираем тип пакета - switch (_inPacket.header->packet_type) { - case AC_PTYPE_PING: { // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает - - if (_inPacket.header->body_length != 0 ) { // у входящего ping-пакета тело должно отсутствовать - // если тело есть, то жалуемся в лог - _debugMsg(F("Parser: ping packet should not to have body. Received one has body length %02X."), ESPHOME_LOG_LEVEL_WARN, __LINE__, _inPacket.header->body_length); - // очищаем пакет - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - break; - } - - _debugMsg(F("Parser: ping packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - // поднимаем флаг, что есть коннект с кондиционером - _has_connection = true; - - // надо отправлять ответ на пинг - _clearOutPacket(); - _outPacket.msec = millis(); - _outPacket.header->packet_type = AC_PTYPE_PING; - _outPacket.header->ping_answer_01 = 0x01; // только в ответе на пинг этот байт равен 0x01; что означает не ясно - _outPacket.header->body_length = 8; // в ответе на пинг у нас тело 8 байт - _outPacket.body = &(_outPacket.data[AC_HEADER_SIZE]); - - // заполняем тело пакета - packet_ping_answer_body_t * ping_body; - ping_body = (packet_ping_answer_body_t *) (_outPacket.body); - ping_body->byte_1C = 0x1C; - ping_body->byte_27 = 0x27; - - // расчет контрольной суммы и прописывание её в пакет - _outPacket.crc = (packet_crc_t *)&(_outPacket.data[AC_HEADER_SIZE + _outPacket.header->body_length]); - _setCRC16(&_outPacket); - _outPacket.bytesLoaded = AC_HEADER_SIZE + _outPacket.header->body_length + 2; - - _debugMsg(F("Parser: generated ping answer. Waiting for sending."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // до отправки пинг-ответа проверяем, не выполнялась ли стартовая последовательность команд - // по задумке она выполняется после подключения к кондиционеру после ответа на первый пинг - // нужна для максимально быстрого определния текущих параметров кондиционера - if (!_startupSequenceComlete){ - _startupSequenceComlete = startupSequence(); - } - - // изначально предполагал, что передачу пакета на отправку выполнит обработчик IDLE, но показалось, что слишком долго - // логика отправки через IDLE в том, что получение запросов может быть важнее отправки ответов и IDLE позволяет реализовать такой приоритет - // но потом решил всё же напрямую отправлять в отправку - // в этом случае пинг-ответ заканчивает отправку спустя 144 мсек после стартового байта пинг-запроса - //_setStateMachineState(ACSM_IDLE); - _setStateMachineState(ACSM_SENDING_PACKET); - // решил провести эксперимент - // получилось от начала запроса до отправки ответа порядка 165 мсек., если отправка идет не сразу, а через состояние IDLE - // Если сразу отсюда отправляться в обработчик отправки, то время сокращается до 131 мсек. Основные потери идут до входа в парсер пакетов - break; - } - - case AC_PTYPE_CMD: { // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита - // сплит такие команды отправлять не должен, поэтому жалуемся в лог - _debugMsg(F("Parser: packet type=0x06 received from HVAC. This isn't expected."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - // очищаем пакет - _clearInPacket(); - _setStateMachineState(ACSM_IDLE); - break; - } - - case AC_PTYPE_INFO: { // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля - // смотрим тип поступившего пакета по второму байту тела - _debugMsg(F("Parser: status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - switch (_inPacket.body[1]) { - case AC_CMD_STATUS_SMALL: { // маленький пакет статуса кондиционера - _debugMsg(F("Parser: status packet type = small"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - stateChangedFlag = false; - - // будем обращаться к телу пакета через указатель на структуру - packet_small_info_body_t * small_info_body; - small_info_body = (packet_small_info_body_t *) (_inPacket.body); - - // в малом пакете передается большое количество установленных пользователем параметров работы - stateFloat = 8 + (small_info_body->target_temp_int_and_v_louver >> 3) + 0.5*(float)(small_info_body->target_temp_frac >> 7); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_target != stateFloat); - _current_ac_state.temp_target = stateFloat; - _current_ac_state.temp_target_matter = true; - - stateByte = small_info_body->target_temp_int_and_v_louver & AC_LOUVERV_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.louver.louver_v != (ac_louver_V)stateByte); - _current_ac_state.louver.louver_v = (ac_louver_V)stateByte; - - stateByte = small_info_body->h_louver & AC_LOUVERH_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.louver.louver_h != (ac_louver_H)stateByte); - _current_ac_state.louver.louver_h = (ac_louver_H)stateByte; - - stateByte = small_info_body->fan_speed & AC_FANSPEED_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.fanSpeed != (ac_fanspeed)stateByte); - _current_ac_state.fanSpeed = (ac_fanspeed)stateByte; - - stateByte = small_info_body->fan_turbo_and_mute & AC_FANTURBO_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.fanTurbo != (ac_fanturbo)stateByte); - _current_ac_state.fanTurbo = (ac_fanturbo)stateByte; - - stateByte = small_info_body->fan_turbo_and_mute & AC_FANMUTE_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.fanMute != (ac_fanmute)stateByte); - _current_ac_state.fanMute = (ac_fanmute)stateByte; - - stateByte = small_info_body->mode & AC_MODE_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.mode != (ac_mode)stateByte); - _current_ac_state.mode = (ac_mode)stateByte; - - stateByte = small_info_body->mode & AC_SLEEP_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.sleep != (ac_sleep)stateByte); - _current_ac_state.sleep = (ac_sleep)stateByte; - - stateByte = small_info_body->mode & AC_IFEEL_MASK; - _current_ac_state.iFeel = (ac_ifeel)stateByte; - - stateByte = small_info_body->status & AC_POWER_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.power != (ac_power)stateByte); - _current_ac_state.power = (ac_power)stateByte; - - stateByte = small_info_body->status & AC_HEALTH_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.health != (ac_health)stateByte); - _current_ac_state.health = (ac_health)stateByte; - - stateByte = small_info_body->status & AC_HEALTH_STATUS_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.health_status != (ac_health_status)stateByte); - _current_ac_state.health_status = (ac_health_status)stateByte; - - stateByte = small_info_body->status & AC_CLEAN_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.clean != (ac_clean)stateByte); - _current_ac_state.clean = (ac_clean)stateByte; - - stateByte = small_info_body->display_and_mildew & AC_DISPLAY_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.display != (ac_display)stateByte); - _current_ac_state.display = (ac_display)stateByte; - - stateByte = small_info_body->display_and_mildew & AC_MILDEW_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.mildew != (ac_mildew)stateByte); - _current_ac_state.mildew = (ac_mildew)stateByte; - - // уведомляем об изменении статуса сплита - if (stateChangedFlag) stateChanged(); - - break; - } - - case AC_CMD_STATUS_BIG: // большой пакет статуса кондиционера - case AC_CMD_STATUS_PERIODIC: { // раз в 10 минут рассылается сплитом, структура аналогична большому пакету статуса - // TODO: вроде как AC_CMD_STATUS_PERIODIC могут быть и с другими кодами; пока что другие будут игнорироваться; если это будет критично, надо будет поправить - _debugMsg(F("Parser: status packet type = big or periodic"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - stateChangedFlag = false; - - // будем обращаться к телу пакета через указатель на структуру - packet_big_info_body_t * big_info_body; - big_info_body = (packet_big_info_body_t *) (_inPacket.body); - - // тип кондея (инвертор или старт стоп) - _is_invertor = big_info_body->is_invertor; - - // температура воздуха в помещении по версии сплит-системы - stateFloat = big_info_body->ambient_temperature_int - 0x20 + (float)(big_info_body->ambient_temperature_frac & 0x0f) / 10.0; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_ambient != stateFloat); - _current_ac_state.temp_ambient = stateFloat; - - // некая температура из наружного блока, скорее всего температура испарителя - // temp = big_info_body->outdoor_temperature - 0x20; - // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах - { - const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; - const float antkoef = 1.0 - koef; - static float temp = _current_ac_state.temp_outdoor; - temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); - _current_ac_state.temp_outdoor = temp; - } - - // температура входящей магистрали - stateFloat = big_info_body->in_temperature_int - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_inbound != stateFloat); - _current_ac_state.temp_inbound = stateFloat; - - // температура исходящей магистрали - stateFloat = big_info_body->out_temperature_int - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outbound != stateFloat); - _current_ac_state.temp_outbound = stateFloat; - - // температура непонятная температура - stateFloat = big_info_body->strange_temperature_int - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_strange != stateFloat); - _current_ac_state.temp_strange = stateFloat; - - // реальная скорость проперлера - stateFloat = big_info_body->realFanSpeed; - stateChangedFlag = stateChangedFlag || (_current_ac_state.realFanSpeed != (ac_realFan)stateFloat); - _current_ac_state.realFanSpeed = (ac_realFan)stateFloat; - - // мощность инвертора - stateFloat = big_info_body->invertor_power; - stateChangedFlag = stateChangedFlag || (_current_ac_state.invertor_power != stateFloat); - _current_ac_state.invertor_power = stateFloat; - - // режим разморозки - bool temp = (big_info_body->needDefrost && big_info_body->defrostMode); - stateChangedFlag = stateChangedFlag || (_current_ac_state.defrost != temp); - _current_ac_state.defrost = temp; - - // уведомляем об изменении статуса сплита - if (stateChangedFlag) { - stateChanged(); - } - break; - } - - case AC_CMD_SET_PARAMS: { // такой статусный пакет присылается кондиционером в ответ на команду установки параметров - // в теле пакета нет ничего примечательного - // в байтах 2 и 3 тела похоже передается CRC пакета поступившей команды, на которую сплит отвечает - // но я решил этот момент тут не проверять и не контролировать. - // корректную установку параметров можно определить, запросив статус кондиционера сразу после получения этой команды кондея - // в настоящий момент проверка сделана в механизме sequences - // TODO: если доводить до идеала, то проверку байтов 2 и 3 можно сделать и тут - break; - } - - default: - _debugMsg(F("Parser: status packet type = unknown (%02X)"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _inPacket.body[1]); - break; - } - _setStateMachineState(ACSM_IDLE); - break; - } - - case AC_PTYPE_INIT: // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. - case AC_PTYPE_UNKN: // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует - default: - // игнорируем. Для нашего случая эти пакеты не важны - _setStateMachineState(ACSM_IDLE); - break; - } - - // если есть последовательность команд, то надо отработать проверку последовательности - if (hasSequence()) _doSequence(); - - // после разбора входящего пакета его надо очистить - _clearInPacket(); - } - - // состояние конечного автомата: ACSM_SENDING_PACKET - void _doSendingPacketState(){ - // если нет исходящего пакета, то выходим - if ((_outPacket.msec == 0) || (_outPacket.crc == nullptr) || (_outPacket.bytesLoaded == 0)) { - _debugMsg(F("Sender: no packet to send."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - _setStateMachineState(ACSM_IDLE); - return; - } - - _debugMsg(F("Sender: sending packet."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - _ac_serial->write_array(_outPacket.data, _outPacket.bytesLoaded); - _ac_serial->flush(); - - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_DEBUG, __LINE__); - _debugMsg(F("Sender: %u bytes sent (%u ms)."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _outPacket.bytesLoaded, millis()-_outPacket.msec); - _clearOutPacket(); - - _setStateMachineState(ACSM_IDLE); - }; - - /** вывод отладочной информации в лог - * - * dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе. - * msg - сообщение, выводимое в лог - * line - строка, на которой произошел вызов (удобно при отладке) - */ - void _debugMsg(const String &msg, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0, ... ){ - if (dbgLevel < ESPHOME_LOG_LEVEL_NONE) dbgLevel = ESPHOME_LOG_LEVEL_NONE; - if (dbgLevel > ESPHOME_LOG_LEVEL_VERY_VERBOSE) dbgLevel = ESPHOME_LOG_LEVEL_VERY_VERBOSE; - - if (line == 0) line = __LINE__; // если строка не передана, берем текущую строку - - va_list vl; - va_start(vl, line); - esp_log_vprintf_(dbgLevel, Constants::TAG, line, msg.c_str(), vl); // так тоже вроде через Ж*, только ее не видно :) - va_end(vl); - } - - /** выводим данные пакета в лог для отладки - * - * dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе. - * packet - указатель на пакет для вывода; - * если указатель на crc равен nullptr или первый байт в буфере не AC_PACKET_START_BYTE, то считаем, что передан битый пакет - * или не пакет вовсе. Для такого выводим только массив байт. - * Для нормального пакета данные выводятся с форматированием. - * line - строка, на которой произошел вызов (удобно при отладке) - **/ - void _debugPrintPacket(packet_t * packet, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0){ - // определяем, полноценный ли пакет нам передан - bool notAPacket = false; - // указатель заголовка всегда установден на начало буфера - notAPacket = notAPacket || (packet->crc == nullptr); - notAPacket = notAPacket || (packet->data[0] != AC_PACKET_START_BYTE); - - String st = ""; - char textBuf[11]; - - // заполняем время получения пакета - memset(textBuf, 0, 11); - sprintf(textBuf, "%010u", packet->msec); - st = st + textBuf + ": "; - - // формируем преамбулы - if (packet == &_inPacket) { - st += "[<=] "; // преамбула входящего пакета - } else if (packet == &_outPacket) { - st += "[=>] "; // преамбула исходящего пакета - } else { - st += "[--] "; // преамбула для "непакета" - } - - // формируем данные - #ifdef HOLMS - dbgLevel = ESPHOME_LOG_LEVEL_ERROR; - if(packet->header->body_length > HOLMS){ - for (int i=0; ibytesLoaded; i++){ - sprintf(textBuf, "%03d;", packet->data[i]); - st += textBuf; - } - if (line == 0) line = __LINE__; - _debugMsg(st, dbgLevel, line); - } - #else - for (int i=0; ibytesLoaded; i++){ - // для нормальных пакетов надо заключить заголовок в [] - if ((!notAPacket) && (i == 0)) st += "["; - // для нормальных пакетов надо заключить CRC в [] - if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE)) st += "["; - - //memset(textBuf, 0, 11); - sprintf(textBuf, "%02X", packet->data[i]); - st += textBuf; - - // для нормальных пакетов надо заключить заголовок в [] - if ((!notAPacket) && (i == AC_HEADER_SIZE-1)) st += "]"; - // для нормальных пакетов надо заключить CRC в [] - if ((!notAPacket) && (i == packet->header->body_length+AC_HEADER_SIZE+2-1)) st += "]"; - st += " "; - } - if (line == 0) line = __LINE__; - _debugMsg(st, dbgLevel, line); - #endif - - } - - /** расчет CRC16 для блока данных data длиной len - * data - данные для расчета CRC16, указатель на массив байт - * len - длина блока данных для расчета, в байтах - * - * возвращаем uint16_t CRC16 - **/ - uint16_t _CRC16(uint8_t *data, uint8_t len){ - uint32_t crc = 0; - - // выделяем буфер для расчета CRC и копируем в него переданные данные - // это нужно для того, чтобы в случае нечетной длины данных можно было дополнить тело пакета - // одним нулевым байтом и не попортить загруженный пакет (ведь в загруженном сразу за телом идёт CRC) - uint8_t _crcBuffer[AC_BUFFER_SIZE]; - memset(_crcBuffer, 0, AC_BUFFER_SIZE); - memcpy(_crcBuffer, data, len); - - // если длина данных нечетная, то надо сделать четной, дополнив данные в конце нулевым байтом - // но так как выше буфер заполняли нулями, то отдельно тут присваивать 0x00 нет смысла - if ((len%2) == 1) len++; - - // рассчитываем CRC16 - uint32_t word = 0; - for (uint8_t i=0; i < len; i+=2){ - word = (_crcBuffer[i] << 8) + _crcBuffer[i+1]; - crc += word; - } - crc = (crc >> 16) + (crc & 0xFFFF); - crc = ~ crc; - - return crc & 0xFFFF; - } - - // расчитываем CRC16 и заполняем эти данные в структуре пакета - void _setCRC16(packet_t* pack = nullptr){ - // если пакет не указан, то устанавливаем CRC для исходящего пакета - if (pack == nullptr) pack = &_outPacket; - - packet_crc_t crc; - crc.crc16 = _CRC16(pack->data, AC_HEADER_SIZE + pack->header->body_length); - - // если забыли указатель на crc установить, то устанавливаем - if (pack->crc == nullptr) pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - - pack->crc->crc[0] = crc.crc[1]; - pack->crc->crc[1] = crc.crc[0]; - return; - } - - // проверяет CRC пакета по указателю - bool _checkCRC(packet_t* pack = nullptr){ - // если пакет не указан, то проверяем входящий - if (pack == nullptr) pack = &_inPacket; - if (pack->bytesLoaded < AC_HEADER_SIZE) { - _debugMsg(F("CRC check: incoming packet size error."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - // если забыли указатель на crc установить, то устанавливаем - if (pack->crc == nullptr) pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - - packet_crc_t crc; - crc.crc16 = _CRC16(pack->data, AC_HEADER_SIZE + pack->header->body_length); - - return ((pack->crc->crc[0] == crc.crc[1]) && (pack->crc->crc[1] == crc.crc[0])); - } - - // заполняет пакет по ссылке командой запроса маленького пакета статуса - void _fillStatusSmall(packet_t * pack = nullptr){ - // по умолчанию заполняем исходящий пакет - if (pack == nullptr) pack = &_outPacket; - - // присваиваем параметры пакета - pack->msec = millis(); - pack->header->start_byte = AC_PACKET_START_BYTE; - pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа - pack->header->packet_type = AC_PTYPE_CMD; - pack->header->body_length = 2; // тело команды 2 байта - pack->body = &(pack->data[AC_HEADER_SIZE]); - pack->body[0] = AC_CMD_STATUS_SMALL; - pack->body[1] = 0x01; // он всегда 0x01 - pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; - - // рассчитываем и записываем в пакет CRC - pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - _setCRC16(pack); - } - - // заполняет пакет по ссылке командой запроса большого пакета статуса - void _fillStatusBig(packet_t * pack = nullptr){ - // по умолчанию заполняем исходящий пакет - if (pack == nullptr) pack = &_outPacket; - - // присваиваем параметры пакета - pack->msec = millis(); - pack->header->start_byte = AC_PACKET_START_BYTE; - pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа - pack->header->packet_type = AC_PTYPE_CMD; - pack->header->body_length = 2; // тело команды 2 байта - pack->body = &(pack->data[AC_HEADER_SIZE]); - pack->body[0] = AC_CMD_STATUS_BIG; - pack->body[1] = 0x01; // он всегда 0x01 - pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; - - // рассчитываем и записываем в пакет CRC - pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - _setCRC16(pack); - } - - /** заполняет пакет по ссылке командой установки параметров - * - * указатель на пакет может отсутствовать, тогда заполняется _outPacket - * указатель на команду также может отсутствовать, тогда используется текущее состояние из _current_ac_state - * все *__UNTOUCHED параметры заполняются из _current_ac_state - **/ - void _fillSetCommand(bool clrPacket = false, packet_t * pack = nullptr, ac_state_t *cmd = nullptr){ - // по умолчанию заполняем исходящий пакет - if (pack == nullptr) pack = &_outPacket; - - // очищаем пакет, если это указано - if (clrPacket) _clearPacket(pack); - - // заполняем его параметрами из _current_ac_state - if (cmd != &_current_ac_state) { - _fillSetCommand(false, pack, &_current_ac_state); - } - - // если команда не указана, значит выходим - if (cmd == nullptr) return; - - // команда указана, дополнительно внесем в пакет те параметры, которые установлены в команде - - // присваиваем параметры пакета - pack->msec = millis(); - pack->header->start_byte = AC_PACKET_START_BYTE; - pack->header->wifi = AC_PACKET_ANSWER; // для исходящего пакета ставим признак ответа - pack->header->packet_type = AC_PTYPE_CMD; - pack->header->body_length = 15; // тело команды 15 байт, как у Small status - pack->body = &(pack->data[AC_HEADER_SIZE]); - pack->body[0] = AC_CMD_SET_PARAMS; // устанавливаем параметры - pack->body[1] = 0x01; // он всегда 0x01 - pack->bytesLoaded = AC_HEADER_SIZE + pack->header->body_length + 2; - - // целевая температура кондиционера - if (cmd->temp_target_matter){ - // устраняем выход за границы диапазона (это ограничение самого кондиционера) - if (cmd->temp_target < Constants::AC_MIN_TEMPERATURE) cmd->temp_target = Constants::AC_MIN_TEMPERATURE; - if (cmd->temp_target > Constants::AC_MAX_TEMPERATURE) cmd->temp_target = Constants::AC_MAX_TEMPERATURE; - - // целая часть температуры - pack->body[2] = (pack->body[2] & ~AC_TEMP_TARGET_INT_PART_MASK) | (((uint8_t)(cmd->temp_target) - 8) << 3); - - // дробная часть температуры - if (cmd->temp_target - (uint8_t)(cmd->temp_target) > 0) { - pack->body[4] = (pack->body[4] & ~AC_TEMP_TARGET_FRAC_PART_MASK) | 1; - } else { - pack->body[4] = (pack->body[4] & ~AC_TEMP_TARGET_FRAC_PART_MASK) | 0; - } - } - - // обнулить счетчик минут с последней команды - pack->body[4] &= ~ AC_MIN_COUTER ; - - // вертикальные жалюзи - if (cmd->louver.louver_v != AC_LOUVERV_UNTOUCHED){ - pack->body[2] = (pack->body[2] & ~AC_LOUVERV_MASK) | cmd->louver.louver_v; - } - - // горизонтальные жалюзи - if (cmd->louver.louver_h != AC_LOUVERH_UNTOUCHED){ - pack->body[3] = (pack->body[3] & ~AC_LOUVERH_MASK) | cmd->louver.louver_h; - } - - // скорость вентилятора - if (cmd->fanSpeed != AC_FANSPEED_UNTOUCHED){ - pack->body[5] = (pack->body[5] & ~AC_FANSPEED_MASK) | cmd->fanSpeed; - } - - // спец.режимы вентилятора: TURBO - if (cmd->fanTurbo != AC_FANTURBO_UNTOUCHED){ - pack->body[6] = (pack->body[6] & ~AC_FANTURBO_MASK) | cmd->fanTurbo; - } - - // спец.режимы вентилятора: MUTE - if (cmd->fanMute != AC_FANMUTE_UNTOUCHED){ - pack->body[6] = (pack->body[6] & ~AC_FANMUTE_MASK) | cmd->fanMute; - } - - // режим кондея - if (cmd->mode != AC_MODE_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_MODE_MASK) | cmd->mode; - } - if (cmd->sleep != AC_SLEEP_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_SLEEP_MASK) | cmd->sleep; - } - if (cmd->iFeel != AC_IFEEL_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_IFEEL_MASK) | cmd->iFeel; - } - - // питание вкл/выкл - if (cmd->power != AC_POWER_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_POWER_MASK) | cmd->power; - } - if (cmd->clean != AC_CLEAN_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_CLEAN_MASK) | cmd->clean; - } - // ионизатор - if (cmd->health != AC_HEALTH_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_HEALTH_MASK) | cmd->health; - } - - // какой то флаг ионизатора - if (cmd->health_status != AC_HEALTH_STATUS_UNTOUCHED){ - pack->body[10] = (pack->body[10] & ~AC_HEALTH_STATUS_MASK) | cmd->health_status; - } - - // дисплей - if (cmd->display != AC_DISPLAY_UNTOUCHED){ - pack->body[12] = (pack->body[12] & ~AC_DISPLAY_MASK) | cmd->display; - } - - // антиплесень - if (cmd->mildew != AC_MILDEW_UNTOUCHED){ - pack->body[12] = (pack->body[12] & ~AC_MILDEW_MASK) | cmd->mildew; - } - - - - // рассчитываем и записываем в пакет CRC - pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); - _setCRC16(pack); - } - - // отправка запроса на маленький статусный пакет - bool sq_requestSmallStatus(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _fillStatusSmall(&_outPacket); - _fillStatusSmall(&_sequence[_sequence_current_step].packet); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: small status request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // проверка ответа на запрос маленького статусного пакета - bool sq_controlSmallStatus(){ - // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим - if (_inPacket.bytesLoaded == 0) return true; - - // Пинги игнорируем - if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; - - // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать - _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; - - // проверяем ответ - bool relevant = true; - relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x0F)); - relevant = (relevant && (_inPacket.body[0] == 0x01)); - relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_SMALL)); - - // если пакет подходит, значит можно переходить к следующему шагу - if (relevant) { - _debugMsg(F("Sequence [step %u]: correct small status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _sequence_current_step++; - } else { - // если пакет не подходящий, то отчитываемся в лог... - _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_SMALL); - // ...и прерываем последовательность, так как вернем false - } - return relevant; - } - - // отправка запроса на большой статусный пакет - bool sq_requestBigStatus(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _fillStatusBig(&_outPacket); - _fillStatusBig(&_sequence[_sequence_current_step].packet); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: big status request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // проверка ответа на запрос большого статусного пакета - bool sq_controlBigStatus(){ - // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим - if (_inPacket.bytesLoaded == 0) return true; - - // Пинги игнорируем - if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; - - // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать - _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; - - // проверяем ответ - bool relevant = true; - relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x18 || _inPacket.header->body_length == 0x19)); // канальник Royal Clima отвечает пакетом длиной 0x19 - relevant = (relevant && (_inPacket.body[0] == 0x01)); - relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_BIG)); - - // если пакет подходит, значит можно переходить к следующему шагу - if (relevant) { - _debugMsg(F("Sequence [step %u]: correct big status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _sequence_current_step++; - } else { - // если пакет не подходящий, то отчитываемся в лог... - _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_BIG); - // ...и прерываем последовательность - } - return relevant; - } - - // отправка запроса на выполнение команды - bool sq_requestDoCommand(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _fillSetCommand(true, &_outPacket, &_sequence[_sequence_current_step].cmd); - _fillSetCommand(true, &_sequence[_sequence_current_step].packet, &_sequence[_sequence_current_step].cmd); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: doCommand request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // проверка ответа на выполнение команды - bool sq_controlDoCommand(){ - // если по каким-то причинам нет входящего пакета, значит проверять нам нечего - просто выходим - if (_inPacket.bytesLoaded == 0) return true; - - // Пинги игнорируем - if (_inPacket.header->packet_type == AC_PTYPE_PING) return true; - - // сохраняем полученный пакет в последовательность, чтобы на возможных следующих шагах с ним можно было работать - _copyPacket(&_sequence[_sequence_current_step].packet, &_inPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_RECEIVED_PACKET; - - // проверяем ответ - bool relevant = true; - relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x04)); - relevant = (relevant && (_inPacket.body[0] == 0x01)); - relevant = (relevant && (_inPacket.body[1] == AC_CMD_SET_PARAMS)); - // байты 2 и 3 обычно равны CRC отправленного пакета с командой - relevant = (relevant && (_inPacket.body[2] == _sequence[_sequence_current_step-1].packet.crc->crc[0])); - relevant = (relevant && (_inPacket.body[3] == _sequence[_sequence_current_step-1].packet.crc->crc[1])); - - // если пакет подходит, значит можно переходить к следующему шагу - if (relevant) { - _debugMsg(F("Sequence [step %u]: correct doCommand packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _sequence_current_step++; - } else { - // если пакет не подходящий, то отчитываемся в лог... - _debugMsg(F("Sequence [step %u]: irrelevant incoming packet"), ESPHOME_LOG_LEVEL_WARN, __LINE__, _sequence_current_step); - _debugMsg(F("Incoming packet:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_inPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugMsg(F("Sequence packet needed: PACKET_TYPE = %02X, CMD = %02X"), ESPHOME_LOG_LEVEL_WARN, __LINE__, AC_PTYPE_INFO, AC_CMD_STATUS_BIG); - // ...и прерываем последовательность - } - return relevant; - } - - // отправка запроса с тестовым пакетом - bool sq_requestTestPacket(){ - // если исходящий пакет не пуст, то выходим и ждем освобождения - if (_outPacket.bytesLoaded > 0) return true; - - _copyPacket(&_outPacket, &_outTestPacket); - _copyPacket(&_sequence[_sequence_current_step].packet, &_outTestPacket); - _sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET; - - // Отчитываемся в лог - _debugMsg(F("Sequence [step %u]: Test Packet request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step); - _debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - // увеличиваем текущий шаг - _sequence_current_step++; - return true; - } - - // сенсоры, отображающие параметры сплита - esphome::sensor::Sensor *sensor_indoor_temperature_ = nullptr; - esphome::sensor::Sensor *sensor_outdoor_temperature_ = nullptr; - esphome::sensor::Sensor *sensor_inbound_temperature_ =nullptr; - esphome::sensor::Sensor *sensor_outbound_temperature_ =nullptr; - esphome::sensor::Sensor *sensor_strange_temperature_ =nullptr; - // текущая мощность компрессора - esphome::sensor::Sensor *sensor_invertor_power_ = nullptr; - // бинарный сенсор, отображающий состояние дисплея - esphome::binary_sensor::BinarySensor *sensor_display_ = nullptr; - // бинарный сенсор состония разморозки - esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; - - // загружает на выполнение последовательность команд на включение/выключение табло с температурой - bool _displaySequence(ac_display dsp = AC_DISPLAY_ON){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("displaySequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - if (dsp == AC_DISPLAY_UNTOUCHED) return false; // выходим, чтобы не тратить время - - // формируем команду - ac_command_t cmd; - _clearCommand(&cmd); // не забываем очищать, а то будет мусор - cmd.display = dsp; - // добавляем команду в последовательность - if (!commandSequence(&cmd)) return false; - - _debugMsg(F("displaySequence: loaded (display = %02X)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, dsp); - return true; - } - - // номер глобального пресета от режима работы - uint8_t get_num_preset(ac_command_t* cmd){ - if(cmd->power == AC_POWER_OFF){ - return POS_MODE_OFF; - } else if(cmd->mode == AC_MODE_AUTO){ - return POS_MODE_AUTO; - } else if(cmd->mode == AC_MODE_COOL){ - return POS_MODE_COOL; - } else if(cmd->mode == AC_MODE_DRY){ - return POS_MODE_DRY; - } else if(cmd->mode == AC_MODE_FAN){ - return POS_MODE_FAN; - } else if(cmd->mode == AC_MODE_HEAT){ - return POS_MODE_HEAT; - } - cmd->power = AC_POWER_OFF; - return POS_MODE_OFF; - } - - // восстановление данных из пресета - void load_preset(ac_command_t* cmd, uint8_t num_preset){ - if(num_preset < sizeof(global_presets)/sizeof(global_presets[0])){ // проверка выхода за пределы массива - if(cmd->power == global_presets[num_preset].power && cmd->mode == global_presets[num_preset].mode){ //контроль инициализации - memcpy(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE); // просто копируем из массива - _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } else { - _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } - } - } - - // запись данных в массив персетов - void save_preset(ac_command_t* cmd){ - uint8_t num_preset = get_num_preset(cmd); - if(memcmp(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE) != 0){ // содержимое пресетов разное - memcpy(&(global_presets[num_preset]), cmd, AC_COMMAND_BASE_SIZE); // копируем пресет в массив - #if defined(ESP32) - _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - if(storage.save(global_presets)){ - if(!global_preferences->sync()) // сохраняем все пресеты - _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } else { - _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } - #endif - } else { - _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } - } - - public: - // инициализация объекта - void initAC(esphome::uart::UARTComponent *parent = nullptr){ - _dataMillis = millis(); - _clearInPacket(); - _clearOutPacket(); - - _clearPacket(&_outTestPacket); - _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - _outTestPacket.header->wifi = AC_PACKET_ANSWER; - - _setStateMachineState(ACSM_IDLE); - _ac_serial = parent; - _hw_initialized = (_ac_serial != nullptr); - _has_connection = false; - - // заполняем структуру состояния начальными значениями - _clearCommand((ac_command_t *)&_current_ac_state); - - // очищаем последовательность пакетов - _clearSequence(); - - // выполнена ли уже стартовая последовательность команд (сбор информации о статусе кондея) - _startupSequenceComlete = false; - }; - - float get_setup_priority() const override { return esphome::setup_priority::DATA; } - - void set_indoor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_indoor_temperature_ = temperature_sensor; } - void set_outdoor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_outdoor_temperature_ = temperature_sensor; } - void set_inbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_inbound_temperature_ = temperature_sensor; } - void set_outbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_outbound_temperature_ = temperature_sensor; } - void set_strange_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_strange_temperature_ = temperature_sensor; } - void set_defrost_state(binary_sensor::BinarySensor *defrost_state) { sensor_defrost_ = defrost_state; } - void set_display_sensor(binary_sensor::BinarySensor *display_sensor) { sensor_display_ = display_sensor; } - void set_invertor_power_sensor(sensor::Sensor *invertor_power_sensor) { sensor_invertor_power_ = invertor_power_sensor; } - bool get_hw_initialized(){ return _hw_initialized; }; - bool get_has_connection(){ return _has_connection; }; - - // возвращает, есть ли елементы в последовательности команд - bool hasSequence(){ - return (_sequence[0].item_type != AC_SIT_NONE); - } - - // вызывается для обновления отображения состояния кондиционера, ДЛЯ ПУБЛИКАЦИИ - void stateChanged(){ - _debugMsg(F("State changed, let's publish it."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - if(_is_invertor){ // анализ режима для инвертора, точнее потому что использует показания мощности инвертора - static uint32_t timerInv = 0; - if(_current_ac_state.invertor_power == 0){ // инвертор выключен - timerInv = millis(); - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && - _current_ac_state.power == AC_POWER_OFF ){ // внутренний кулер остановлен, кондей выключен - this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает - } else { - int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; - if (delta_temp > 0 && delta_temp < 2 && - (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else if (_current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_OFF ){ // кулер чуть вертится - this->action = climate::CLIMATE_ACTION_IDLE; // кондей в простое - } else { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция - } - } - } else if(millis()-timerInv > 2000){ // инвертор включен, но нужно дождаться реакции на его включение - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE ){ //медленное вращение - if(_current_ac_state.temp_ambient - _current_ac_state.temp_inbound > 0){ //холодный радиатор - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else { // теплый радиатор, видимо переходный режим - this->action = climate::CLIMATE_ACTION_IDLE; - } - } else { - int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; - if(delta_temp < -2){ // входящая температура выше комнатной, быстрый фен - ОБОГРЕВ - this->action = climate::CLIMATE_ACTION_HEATING; - } else if(delta_temp > 2){ // ниже, быстрый фен - ОХЛАЖДЕНИЕ - this->action = climate::CLIMATE_ACTION_COOLING; - } else { // просто вентиляция - this->action = climate::CLIMATE_ACTION_IDLE; - } - } - } else { - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE){ - this->action = climate::CLIMATE_ACTION_IDLE; - } else { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция - } - } - } else { - if(_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF && - _current_ac_state.power == AC_POWER_OFF){ - this->action = climate::CLIMATE_ACTION_OFF; // значит кондей не работает - } else { - int16_t delta_temp=_current_ac_state.temp_ambient - _current_ac_state.temp_inbound; // разность температуры между комнатной и входящей - if (delta_temp > 0 && delta_temp < 2 && - (_current_ac_state.realFanSpeed == AC_REAL_FAN_OFF || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE || - _current_ac_state.realFanSpeed == AC_REAL_FAN_MUTE )){ - this->action = climate::CLIMATE_ACTION_DRYING; // ОСУШЕНИЕ - } else if(_current_ac_state.realFanSpeed != AC_REAL_FAN_OFF && - _current_ac_state.realFanSpeed != AC_REAL_FAN_MUTE){ - if(delta_temp > 2){ - this->action = climate::CLIMATE_ACTION_COOLING; - } else if(delta_temp < -2){ - this->action = climate::CLIMATE_ACTION_HEATING; - } else { - this->action = climate::CLIMATE_ACTION_FAN; // другие режимы - вентиляция - } - } else { - this->action = climate::CLIMATE_ACTION_IDLE; - } - } - } - _debugMsg(F("Action mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->action); - /*************************** POWER & MODE ***************************/ - if (_current_ac_state.power == AC_POWER_ON){ - switch (_current_ac_state.mode) { - case AC_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_HEAT_COOL; // по факту режим, названный в AUX как AUTO, является режимом HEAT_COOL - break; - - case AC_MODE_COOL: - this->mode = climate::CLIMATE_MODE_COOL; - break; - - case AC_MODE_DRY: - this->mode = climate::CLIMATE_MODE_DRY; - break; - - case AC_MODE_HEAT: - this->mode = climate::CLIMATE_MODE_HEAT; - break; - - case AC_MODE_FAN: - this->mode = climate::CLIMATE_MODE_FAN_ONLY; - break; - - default: - _debugMsg(F("Warning: unknown air conditioner mode."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - break; - } - } else { - this->mode = climate::CLIMATE_MODE_OFF; - } - - _debugMsg(F("Climate mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->mode); - - /*************************** FAN SPEED ***************************/ - if(_current_ac_state.power == AC_POWER_ON){ - this->fan_mode = climate::CLIMATE_FAN_OFF; - switch (_current_ac_state.fanSpeed) { - case AC_FANSPEED_HIGH: - this->fan_mode = climate::CLIMATE_FAN_HIGH; - this->custom_fan_mode = (std::string)""; - break; - - case AC_FANSPEED_MEDIUM: - this->fan_mode = climate::CLIMATE_FAN_MEDIUM; - this->custom_fan_mode = (std::string)""; - break; - - case AC_FANSPEED_LOW: - this->fan_mode = climate::CLIMATE_FAN_LOW; - break; - - case AC_FANSPEED_AUTO: - this->fan_mode = climate::CLIMATE_FAN_AUTO; - break; - - default: - break; - } - /*************************** TURBO FAN MODE ***************************/ - switch (_current_ac_state.fanTurbo) { - case AC_FANTURBO_ON: - this->custom_fan_mode = Constants::TURBO; - break; - case AC_FANTURBO_OFF: - default: - if (this->custom_fan_mode == Constants::TURBO) this->custom_fan_mode = (std::string)""; - break; - } - /*************************** MUTE FAN MODE ***************************/ - switch (_current_ac_state.fanMute) { - case AC_FANMUTE_ON: - this->custom_fan_mode = Constants::MUTE; - break; - case AC_FANMUTE_OFF: - default: - if (this->custom_fan_mode == Constants::MUTE) this->custom_fan_mode = (std::string)""; - break; - } - } else { // при выключеном питании публикуем фальшивый статус - //this->fan_mode = climate::CLIMATE_FAN_LOW ; - this->custom_fan_mode = Constants::MUTE; - } - - _debugMsg(F("Climate fan mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->fan_mode); - _debugMsg(F("Climate fan TURBO mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanTurbo); - _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); - - //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ - - /*************************** SLEEP PRESET ***************************/ - // Комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - if(_current_ac_state.sleep == AC_SLEEP_ON && - _current_ac_state.power == AC_POWER_ON ) { - this->preset = climate::CLIMATE_PRESET_SLEEP; - } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { - this->preset = climate::CLIMATE_PRESET_NONE; - } - - _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); - - /*************************** HEALTH CUSTOM PRESET ***************************/ - // режим работы ионизатора - if(_current_ac_state.health == AC_HEALTH_ON && - _current_ac_state.power == AC_POWER_ON ) { - this->custom_preset = Constants::HEALTH; - } else if (this->custom_preset == Constants::HEALTH) { - this->custom_preset = (std::string)""; - } - - _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); - - /*************************** CLEAN CUSTOM PRESET ***************************/ - // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF - if(_current_ac_state.clean == AC_CLEAN_ON && - _current_ac_state.power == AC_POWER_OFF ) { - this->custom_preset = Constants::CLEAN; - } else if (this->custom_preset == Constants::CLEAN) { - this->custom_preset = (std::string)""; - } - - _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); - - /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ - // пресет просушки кондиционера после выключения - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - // - // Brokly: - // У меня есть на этот режим, конедй реагирует только в выключеном состоянии. Причем пульт шлет - // 5 посылок и при включении и при выключении. Но каких то видимых отличий в работе или в сценарии - // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки - // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало - // и затыкается. - if(_current_ac_state.mildew == AC_MILDEW_ON && - _current_ac_state.power == AC_POWER_OFF ) { - this->custom_preset = Constants::ANTIFUNGUS; - } else if (this->custom_preset == Constants::ANTIFUNGUS) { - this->custom_preset = (std::string)""; - } - - _debugMsg(F("Climate ANTIFUNGUS preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.mildew); - - /*************************** LOUVERs ***************************/ - - if( _current_ac_state.power == AC_POWER_OFF){ //ЕСЛИ КОНДЕЙ ВЫКЛЮЧЕН - this->swing_mode = climate::CLIMATE_SWING_OFF; - } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN && - _current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ - this->swing_mode = climate::CLIMATE_SWING_BOTH; - } else if (_current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN){ - this->swing_mode = climate::CLIMATE_SWING_VERTICAL; - } else { - this->swing_mode = climate::CLIMATE_SWING_OFF; - } - - _debugMsg(F("Climate swing mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->swing_mode); - - /*************************** TEMPERATURE ***************************/ - - if(_current_ac_state.mode == AC_MODE_FAN || _current_ac_state.power == AC_POWER_OFF){ - this->target_temperature = _current_ac_state.temp_ambient; - } else if (_current_ac_state.mode == AC_MODE_AUTO ){ - this->target_temperature = 25; - } else { - this->target_temperature = _current_ac_state.temp_target; - } - _debugMsg(F("Target temperature: %f"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->target_temperature); - this->current_temperature = _current_ac_state.temp_ambient; - _debugMsg(F("Room temperature: %f"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->current_temperature); - - /*********************************************************************/ - /*************************** PUBLISH STATE ***************************/ - /*********************************************************************/ - this->publish_state(); - // температура в комнате - if (sensor_indoor_temperature_ != nullptr) - sensor_indoor_temperature_->publish_state(_current_ac_state.temp_ambient); - // температура уличного блока - if (sensor_outdoor_temperature_ != nullptr) - sensor_outdoor_temperature_->publish_state(_current_ac_state.temp_outdoor); - // температура подводящей магистрали - if (sensor_inbound_temperature_ != nullptr) - sensor_inbound_temperature_->publish_state(_current_ac_state.temp_inbound); - // температура отводящей магистрали - if (sensor_outbound_temperature_ != nullptr) - sensor_outbound_temperature_->publish_state(_current_ac_state.temp_outbound); - // температура странного датчика - if (sensor_strange_temperature_ != nullptr) - sensor_strange_temperature_->publish_state(_current_ac_state.temp_strange); - // мощность инвертора - if (sensor_invertor_power_ != nullptr) - sensor_invertor_power_->publish_state(_current_ac_state.invertor_power); - // флаг режима разморозки - if (sensor_defrost_ != nullptr) - sensor_defrost_->publish_state(_current_ac_state.defrost); - // состояние дисплея - if (sensor_display_ != nullptr) { - switch (_current_ac_state.display) { - case AC_DISPLAY_ON: - if (this->get_display_inverted()) { - sensor_display_->publish_state(false); - } else { - sensor_display_->publish_state(true); - } - break; - - case AC_DISPLAY_OFF: - if (this->get_display_inverted()) { - sensor_display_->publish_state(true); - } else { - sensor_display_->publish_state(false); - } - break; - - default: - // могут быть и другие состояния, поэтому так - break; - } - } - } - - // вывод в дебаг текущей конфигурации компонента - void dump_config() { - ESP_LOGCONFIG(Constants::TAG, "AUX HVAC:"); - ESP_LOGCONFIG(Constants::TAG, " [x] Firmware version: %s", Constants::AC_FIRMWARE_VERSION.c_str()); - ESP_LOGCONFIG(Constants::TAG, " [x] Period: %dms", this->get_period()); - ESP_LOGCONFIG(Constants::TAG, " [x] Show action: %s", TRUEFALSE(this->get_show_action())); - ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", TRUEFALSE(this->get_display_inverted())); - ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); - ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); - if ((this->sensor_indoor_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); - if (!(this->sensor_indoor_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_indoor_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_indoor_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_indoor_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_indoor_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_indoor_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_indoor_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_indoor_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_indoor_temperature_)->unique_id().c_str()); - } - if ((this->sensor_indoor_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_outdoor_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Outdoor Temperature"), (this->sensor_outdoor_temperature_)->get_name().c_str()); - if (!(this->sensor_outdoor_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_outdoor_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_outdoor_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_outdoor_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_outdoor_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_outdoor_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_outdoor_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_outdoor_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_outdoor_temperature_)->unique_id().c_str()); - } - if ((this->sensor_outdoor_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_inbound_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Inbound Temperature"), (this->sensor_inbound_temperature_)->get_name().c_str()); - if (!(this->sensor_inbound_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_inbound_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_inbound_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_inbound_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_inbound_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_inbound_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_inbound_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_inbound_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_inbound_temperature_)->unique_id().c_str()); - } - if ((this->sensor_inbound_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_outbound_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Outbound Temperature"), (this->sensor_outbound_temperature_)->get_name().c_str()); - if (!(this->sensor_outbound_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_outbound_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_outbound_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_outbound_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_outbound_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_outbound_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_outbound_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_outbound_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_outbound_temperature_)->unique_id().c_str()); - } - if ((this->sensor_outbound_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_strange_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Strange Temperature"), (this->sensor_strange_temperature_)->get_name().c_str()); - if (!(this->sensor_strange_temperature_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_strange_temperature_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_strange_temperature_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_strange_temperature_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_strange_temperature_)->get_accuracy_decimals()); - if (!(this->sensor_strange_temperature_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_strange_temperature_)->get_icon().c_str()); - } - if (!(this->sensor_strange_temperature_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_strange_temperature_)->unique_id().c_str()); - } - if ((this->sensor_strange_temperature_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_invertor_power_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Inverter Power"), (this->sensor_invertor_power_)->get_name().c_str()); - if (!(this->sensor_invertor_power_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_invertor_power_)->get_device_class().c_str()); - } - ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_invertor_power_)->get_state_class()).c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_invertor_power_)->get_unit_of_measurement().c_str()); - ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_invertor_power_)->get_accuracy_decimals()); - if (!(this->sensor_invertor_power_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_invertor_power_)->get_icon().c_str()); - } - if (!(this->sensor_invertor_power_)->unique_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_invertor_power_)->unique_id().c_str()); - } - if ((this->sensor_invertor_power_)->get_force_update()) { - ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); - } - } - - if ((this->sensor_defrost_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Defrost status"), (this->sensor_defrost_)->get_name().c_str()); - if (!(this->sensor_defrost_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_defrost_)->get_device_class().c_str()); - } - if (!(this->sensor_defrost_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_defrost_)->get_icon().c_str()); - } - if (!(this->sensor_defrost_)->get_object_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Object ID: '%s'", " ", (this->sensor_defrost_)->get_object_id().c_str()); - } - } - - if ((this->sensor_display_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Display"), (this->sensor_display_)->get_name().c_str()); - if (!(this->sensor_display_)->get_device_class().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_display_)->get_device_class().c_str()); - } - if (!(this->sensor_display_)->get_icon().empty()) { - ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_display_)->get_icon().c_str()); - } - if (!(this->sensor_display_)->get_object_id().empty()) { - ESP_LOGV(Constants::TAG, "%s Object ID: '%s'", " ", (this->sensor_display_)->get_object_id().c_str()); - } - } - this->dump_traits_(Constants::TAG); - } - - // вызывается пользователем из интерфейса ESPHome или Home Assistant - void control(const esphome::climate::ClimateCall &call) override { - bool hasCommand = false; - ac_command_t cmd; - - _clearCommand(&cmd); // не забываем очищать, а то будет мусор - - // User requested mode change - if (call.get_mode().has_value()) { - ClimateMode mode = *call.get_mode(); - // Send mode to hardware - switch (mode) { - case climate::CLIMATE_MODE_OFF: - hasCommand = true; - cmd.power = AC_POWER_OFF; - load_preset(&cmd, POS_MODE_OFF); - cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать - this->mode = mode; - break; - - case climate::CLIMATE_MODE_COOL: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_COOL; - load_preset(&cmd, POS_MODE_COOL); - this->mode = mode; - break; - - case climate::CLIMATE_MODE_HEAT: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_HEAT; - load_preset(&cmd, POS_MODE_HEAT); - this->mode = mode; - break; - - case climate::CLIMATE_MODE_HEAT_COOL: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_AUTO; - load_preset(&cmd, POS_MODE_AUTO); - cmd.temp_target = 25; // зависимость от режима HEAT_COOL - cmd.temp_target_matter = true; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL - this->mode = mode; - break; - - case climate::CLIMATE_MODE_FAN_ONLY: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_FAN; - load_preset(&cmd, POS_MODE_FAN); - cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN - cmd.temp_target_matter = true; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN - cmd.sleep = AC_SLEEP_OFF; - if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ - cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN - } - this->mode = mode; - break; - - case climate::CLIMATE_MODE_DRY: - hasCommand = true; - cmd.power = AC_POWER_ON; - cmd.mode = AC_MODE_DRY; - load_preset(&cmd, POS_MODE_DRY); - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY - cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY - this->mode = mode; - break; - - case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) - default: - break; - } - } - - // User requested fan_mode change - if(_current_ac_state.power == AC_POWER_ON || cmd.power == AC_POWER_ON) { // пресеты для включенного кондея - // User requested swing_mode change - if (call.get_swing_mode().has_value()) { - ClimateSwingMode swingmode = *call.get_swing_mode(); - // Send fan mode to hardware - switch (swingmode) { - // The protocol allows other combinations for SWING. - // For example "turn the louvers to the desired position or "spread to the sides" / "concentrate in the center". - // But the ROVEX IR-remote does not provide this features. Therefore this features haven't been tested. - // May be suitable for other models of AUX-based ACs. - case climate::CLIMATE_SWING_OFF: - cmd.louver.louver_h = AC_LOUVERH_OFF; - cmd.louver.louver_v = AC_LOUVERV_OFF; - hasCommand = true; - this->swing_mode = swingmode; - break; - - case climate::CLIMATE_SWING_BOTH: - cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; - cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; - hasCommand = true; - this->swing_mode = swingmode; - break; - - case climate::CLIMATE_SWING_VERTICAL: - cmd.louver.louver_h = AC_LOUVERH_OFF; - cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; - hasCommand = true; - this->swing_mode = swingmode; - break; - - case climate::CLIMATE_SWING_HORIZONTAL: - cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; - cmd.louver.louver_v = AC_LOUVERV_OFF; - hasCommand = true; - this->swing_mode = swingmode; - break; - } - } - - if (call.get_target_temperature().has_value()) { - if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_FAN) || - (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_FAN)){ // блокировка ввода температуры в режиме FAN - this->target_temperature = _current_ac_state.temp_ambient; - this->publish_state(); - } else if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_AUTO) || - (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_AUTO)){ // блокировка ввода температуры в режиме АВТО - this->target_temperature = 25; - this->publish_state(); - } else { - // User requested target temperature change - float temp = *call.get_target_temperature(); - // Send target temp to climate - if (temp > Constants::AC_MAX_TEMPERATURE) temp = Constants::AC_MAX_TEMPERATURE; - if (temp < Constants::AC_MIN_TEMPERATURE) temp = Constants::AC_MIN_TEMPERATURE; - if(cmd.temp_target != temp){ - cmd.temp_target = temp; - hasCommand = true; - cmd.temp_target_matter = true; - } - } - } - - if (call.get_fan_mode().has_value()) { - ClimateFanMode fanmode = *call.get_fan_mode(); - // Send fan mode to hardware - switch (fanmode) { - case climate::CLIMATE_FAN_AUTO: - if(cmd.mode != AC_MODE_FAN){ // при вентиляции нет такого режима - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_AUTO; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - } - break; - - case climate::CLIMATE_FAN_LOW: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_LOW; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_MEDIUM: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_MEDIUM; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_HIGH: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_HIGH; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_ON: - case climate::CLIMATE_FAN_OFF: - case climate::CLIMATE_FAN_MIDDLE: - case climate::CLIMATE_FAN_FOCUS: - case climate::CLIMATE_FAN_DIFFUSE: - default: - break; - } - - } else if (call.get_custom_fan_mode().has_value()) { - std::string customfanmode = *call.get_custom_fan_mode(); - // Send fan mode to hardware - if (customfanmode == Constants::TURBO) { - // TURBO fan mode is suitable in COOL and HEAT modes for Rovex air conditioners. - // Other modes don't accept TURBO fan mode. - // May be other AUX-based air conditioners do the same. - if ( cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT) { - - hasCommand = true; - cmd.fanTurbo = AC_FANTURBO_ON; - cmd.fanMute = AC_FANMUTE_OFF; // зависимость от fanturbo - this->custom_fan_mode = customfanmode; - } else { - _debugMsg(F("TURBO fan mode is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - } - } else if (customfanmode == Constants::MUTE) { - // MUTE fan mode is suitable in FAN mode only for Rovex air conditioner. - // In COOL mode AC receives command without any changes. - // May be other AUX-based air conditioners do the same. - //if ( cmd.mode == AC_MODE_FAN || - // _current_ac_state.mode == AC_MODE_FAN) { - - hasCommand = true; - cmd.fanMute = AC_FANMUTE_ON; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от fanmute - this->custom_fan_mode = customfanmode; - //} else { - // _debugMsg(F("MUTE fan mode is suitable in FAN mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - //} - } - } - - // Пользователь выбрал пресет - if (call.get_preset().has_value()) { - ClimatePreset preset = *call.get_preset(); - switch (preset) { - case climate::CLIMATE_PRESET_SLEEP: - // Ночной режим (SLEEP). Комбинируется только с режимами COOL, HEAT AUTO и DRY. Автоматически выключается через 7 часов. - // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. - // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. - // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. - if ( cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || cmd.mode == AC_MODE_DRY || cmd.mode == AC_MODE_AUTO || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_DRY || _current_ac_state.mode == AC_MODE_AUTO){ - - hasCommand = true; - cmd.sleep = AC_SLEEP_ON; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - this->preset = preset; - } else { - _debugMsg(F("SLEEP preset is suitable in COOL,HEAT and AUTO modes only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - break; - case climate::CLIMATE_PRESET_NONE: - // выбран пустой пресет, сбрасываем все настройки - hasCommand = true; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - this->preset = preset; - _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - default: - // никакие другие встроенные пресеты не поддерживаются - break; - } - } else if (call.get_custom_preset().has_value()) { - std::string custom_preset = *call.get_custom_preset(); - if (custom_preset == Constants::HEALTH) { - hasCommand = true; - cmd.health = AC_HEALTH_ON; - cmd.health_status = AC_HEALTH_STATUS_ON; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health - cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - if(cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || cmd.mode == AC_MODE_AUTO || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_AUTO ){ - - cmd.fanSpeed = AC_FANSPEED_AUTO; // зависимость от health - } else if(cmd.mode == AC_MODE_FAN || _current_ac_state.mode == AC_MODE_FAN){ - cmd.fanSpeed = AC_FANSPEED_MEDIUM; // зависимость от health - } - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::CLEAN) { - _debugMsg(F("CLEAN work only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } else if (custom_preset == Constants::ANTIFUNGUS) { - _debugMsg(F("Anti-FUNGUS works only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - } - } else if(_current_ac_state.power == AC_POWER_OFF || cmd.power == AC_POWER_OFF){ // функции при выключеном питании - - if (call.get_target_temperature().has_value()) { // блокировка изменения температуры в выключеном состоянии - this->target_temperature = _current_ac_state.temp_ambient; - this->publish_state(); - } - - if (call.get_preset().has_value()) { // пользователь выбрал пустой пресет - ClimatePreset preset = *call.get_preset(); - switch (preset) { - case climate::CLIMATE_PRESET_SLEEP: - _debugMsg(F("SLEEP preset is suitable in POWER ON mode only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - case climate::CLIMATE_PRESET_NONE: - // выбран пустой пресет, сбрасываем все настройки - hasCommand = true; - cmd.clean = AC_CLEAN_OFF; // для логики пресетов - cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->preset = preset; - _debugMsg(F("Clear all 'Power OFF' presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - default: - // никакие другие встроенные пресеты не поддерживаются - break; - } - } else { - std::string custom_preset = *call.get_custom_preset(); - if (call.get_custom_preset().has_value()) { - if (custom_preset == Constants::CLEAN) { - // режим очистки кондиционера при AC_POWER_OFF - hasCommand = true; - cmd.clean = AC_CLEAN_ON; - cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::ANTIFUNGUS) { - // Brokly: - // включение-выключение функции "Антиплесень". - // у меня пульт отправляет 5 посылок и на включение и на выключение, но реагирует на эту кнопку - // только в режиме POWER_OFF - - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - cmd.mildew = AC_MILDEW_ON; - cmd.clean = AC_CLEAN_OFF; // для логики пресетов - hasCommand = true; - this->custom_preset = custom_preset; - } else if (custom_preset == Constants::HEALTH) { - _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - } - } - } - - if (hasCommand) { - commandSequence(&cmd); - this->publish_state(); // Publish updated state - _new_command_set = _store_settings; // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка - } - } - - esphome::climate::ClimateTraits traits() override { - // The capabilities of the climate device - auto traits = climate::ClimateTraits(); - - traits.set_supports_current_temperature(true); - traits.set_supports_two_point_target_temperature(false); // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature - - // tells the frontend what range of temperatures the climate device should display (gauge min/max values) - traits.set_visual_min_temperature(Constants::AC_MIN_TEMPERATURE); - traits.set_visual_max_temperature(Constants::AC_MAX_TEMPERATURE); - // the step with which to increase/decrease target temperature. This also affects with how many decimal places the temperature is shown. - traits.set_visual_temperature_step(Constants::AC_TEMPERATURE_STEP); - - traits.set_supported_modes(this->_supported_modes); - traits.set_supported_swing_modes(this->_supported_swing_modes); - traits.set_supported_presets(this->_supported_presets); - traits.set_supported_custom_presets(this->_supported_custom_presets); - traits.set_supported_custom_fan_modes(this->_supported_custom_fan_modes); - - /* + MINIMAL SET */ - traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF); - traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH); - traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF); - //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL); - //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_BOTH); - traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); - //traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); - - /* *************** TODO: надо сделать информирование о текущем режиме, сплит поддерживает *************** - * смотри climate::ClimateAction - */ - // if the climate device supports reporting the active current action of the device with the action property. - traits.set_supports_action(this->_show_action); - - return traits; - } - - // запрос маленького пакета статуса кондиционера - bool getStatusSmall(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("getStatusSmall: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 2) { - _debugMsg(F("getStatusSmall: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** getSmallInfo request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestSmallStatus)) { - _debugMsg(F("getStatusSmall: getSmallInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /*************************************** getSmallInfo control ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_controlSmallStatus)) { - _debugMsg(F("getStatusSmall: getSmallInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - _debugMsg(F("getStatusSmall: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - // запрос большого пакета статуса кондиционера - bool getStatusBig(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("getStatusBig: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 2) { - _debugMsg(F("getStatusBig: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** getBigInfo request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestBigStatus)) { - _debugMsg(F("getStatusBig: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /*************************************** getBigInfo control ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_controlBigStatus)) { - _debugMsg(F("getStatusBig: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - _debugMsg(F("getStatusBig: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - // запрос большого и малого пакетов статуса последовательно - bool getStatusBigAndSmall(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("getStatusBigAndSmall: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - - if (!getStatusSmall()) { - _debugMsg(F("getStatusBigAndSmall: error with small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - if (!getStatusBig()) { - _debugMsg(F("getStatusBigAndSmall: error with big status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - _debugMsg(F("getStatusBigAndSmall: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - /** стартовая последовательность пакетов - * - * нужна, чтобы не ждать долго обновления статуса кондиционера - * запускаем сразу, как только удалось подключиться к кондиционеру и прошел первый пинг-пакет - * возвращаемое значение будет присвоено флагу выполнения последовательности - * то есть при возврате false последовательность считается не запущенной и будет вызоваться до тех пор, пока не вернет true - **/ - bool startupSequence(){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("startupSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - - // по сути на старте надо получить от кондиционера два статуса - if (!getStatusBigAndSmall()){ - _debugMsg(F("startupSequence: error with big&small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - }; - - _debugMsg(F("startupSequence: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - /** загружает на выполнение команду - * - * стандартная последовательность - это запрос маленького статусного пакета, выполнение команды и повторный запрос - * такого же статуса для проверки, что всё включилось, ну и для обновления интерфейсов всяких связанных компонентов - **/ - bool commandSequence(ac_command_t * cmd){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("commandSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - - // добавление начального запроса маленького статусного пакета в последовательность команд - if (!getStatusSmall()) { - _debugMsg(F("commandSequence: error with first small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 2) { - _debugMsg(F("commandSequence: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** set params request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestDoCommand, cmd)) { - _debugMsg(F("commandSequence: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /*************************************** set params control ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_controlDoCommand)) { - _debugMsg(F("commandSequence: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - // добавление финального запроса маленького статусного пакета в последовательность команд - if (!getStatusSmall()) { - _debugMsg(F("commandSequence: error with last small status sequence."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - _debugMsg(F("commandSequence: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - return true; - } - - // загружает на выполнение последовательность команд на включение/выключение - bool powerSequence(ac_power pwr = AC_POWER_ON){ - // нет смысла в последовательности, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("powerSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - if (pwr == AC_POWER_UNTOUCHED) return false; // выходим, чтобы не тратить время - - // формируем команду - ac_command_t cmd; - _clearCommand(&cmd); // не забываем очищать, а то будет мусор - cmd.power = pwr; - // добавляем команду в последовательность - if (!commandSequence(&cmd)) return false; - - _debugMsg(F("powerSequence: loaded (power = %02X)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, pwr); - return true; - } - - // выключает экран - bool displayOffSequence(){ - ac_display dsp = AC_DISPLAY_OFF; - if (this->get_display_inverted()) dsp = AC_DISPLAY_ON; - return _displaySequence(dsp); - } - - // включает экран - bool displayOnSequence(){ - ac_display dsp = AC_DISPLAY_ON; - if (this->get_display_inverted()) dsp = AC_DISPLAY_OFF; - return _displaySequence(dsp); - } - - // отправляет сплиту заданный набор байт - // Перед отправкой проверяет пакет на корректность структуры. CRC16 рассчитывает самостоятельно и перезаписывает. - bool sendTestPacket(const std::vector &data){ - //bool sendTestPacket(uint8_t *data = nullptr, uitn8_t data_length = 0){ - //if (data == nullptr) return false; - //if (data_length == 0) return false; - if (data.size() == 0) return false; - //if (data_length > AC_BUFFER_SIZE) return false; - if (data.size() > AC_BUFFER_SIZE) return false; - - // нет смысла в отправке, если нет коннекта с кондиционером - if (!get_has_connection()) { - _debugMsg(F("sendTestPacket: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); - return false; - } - // очищаем пакет - _clearPacket(&_outTestPacket); - - // копируем данные в пакет - //memcpy(_outTestPacket.data, data, data_length); - uint8_t i = 0; - for (uint8_t n : data) { - _outTestPacket.data[i] = n; - i++; - } - - // на всякий случай указываем правильные некоторые байты - _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - //_outTestPacket.header->wifi = AC_PACKET_ANSWER; - - _outTestPacket.msec = millis(); - _outTestPacket.body = &(_outTestPacket.data[AC_HEADER_SIZE]); - _outTestPacket.bytesLoaded = AC_HEADER_SIZE + _outTestPacket.header->body_length + 2; - - // рассчитываем и записываем в пакет CRC - _outTestPacket.crc = (packet_crc_t *) &(_outTestPacket.data[AC_HEADER_SIZE + _outTestPacket.header->body_length]); - _setCRC16(&_outTestPacket); - - _debugMsg(F("sendTestPacket: test packet loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - _debugPrintPacket(&_outTestPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); - - // ниже блок добавления отправки пакета в последовательность команд - //***************************************************************** - // есть ли место на запрос в последовательности команд? - if (_getFreeSequenceSpace() < 1) { - _debugMsg(F("sendTestPacket: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - - /*************************************** sendTestPacket request ***********************************************/ - if (!_addSequenceFuncStep(&AirCon::sq_requestTestPacket)) { - _debugMsg(F("sendTestPacket: sendTestPacket request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - return false; - } - /**************************************************************************************/ - - _debugMsg(F("sendTestPacket: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - - return true; - } - - void set_period(uint32_t ms) { this->_update_period = ms; } - uint32_t get_period() { return this->_update_period; } - - void set_show_action(bool show_action) { this->_show_action = show_action; } - bool get_show_action() { return this->_show_action; } - - void set_display_inverted(bool display_inverted) { this->_display_inverted = display_inverted; } - bool get_display_inverted() { return this->_display_inverted; } - - void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } - bool get_store_settings() { return this->_store_settings; } - - void set_supported_modes(const std::set &modes) { this->_supported_modes = modes; } - void set_supported_swing_modes(const std::set &modes) { this->_supported_swing_modes = modes; } - void set_supported_presets(const std::set &presets) { this->_supported_presets = presets; } - void set_custom_presets(const std::set &presets) { this->_supported_custom_presets = presets; } - void set_custom_fan_modes(const std::set &modes) { this->_supported_custom_fan_modes = modes; } - - uint8_t load_presets_result = 0xFF; - void setup() override { - #if defined(ESP32) - load_presets_result = storage.load(global_presets); // читаем все пресеты из флеша - _debugMsg(F("Preset base read from NVRAM, result %02d."), ESPHOME_LOG_LEVEL_WARN, __LINE__, load_presets_result); - #endif - }; - - void loop() override { - if (!get_hw_initialized()) return; - - // контролируем сохранение пресета - if(_new_command_set){ //нужно сохранить пресет - _new_command_set = false; - save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов - } - - // отрабатываем состояния конечного автомата - switch (_ac_state) { - case ACSM_RECEIVING_PACKET: - // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны - _doReceivingPacketState(); - break; - - case ACSM_PARSING_PACKET: - // разбираем полученный пакет - _doParsingPacket(); - break; - - case ACSM_SENDING_PACKET: - // отправляем пакет сплиту - _doSendingPacketState(); - break; - - case ACSM_IDLE: // ничего не делаем, ждем, на что бы среагировать - default: // если состояние какое-то посторонее, то считаем, что IDLE - _doIdleState(); - break; - } - - // раз в заданное количество миллисекунд запрашиваем обновление статуса кондиционера - if ((millis()-_dataMillis) > _update_period){ - _dataMillis = millis(); - - // обычный wifi-модуль запрашивает маленький пакет статуса - // но нам никто не мешает запрашивать и большой и маленький, чтобы чаще обновлять комнатную температуру - // делаем этот запросо только в случае, если есть коннект с кондиционером - if (get_has_connection()) getStatusBigAndSmall(); - } - - }; - }; -} // namespace aux_ac -} // namespace esphome \ No newline at end of file From be35ec6e53a9bf0c9742fc966af8ac504ee285bd Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:17:26 +0300 Subject: [PATCH 43/74] Delete climate.py --- climate.py | 341 ----------------------------------------------------- 1 file changed, 341 deletions(-) delete mode 100644 climate.py diff --git a/climate.py b/climate.py deleted file mode 100644 index 58a9ab2..0000000 --- a/climate.py +++ /dev/null @@ -1,341 +0,0 @@ -import logging -import esphome.config_validation as cv -import esphome.codegen as cg -from esphome.components import climate, uart, sensor, binary_sensor -from esphome import automation -from esphome.automation import maybe_simple_id -from esphome.const import ( - CONF_ID, - CONF_UART_ID, - CONF_PERIOD, - CONF_CUSTOM_FAN_MODES, - CONF_CUSTOM_PRESETS, - CONF_INTERNAL, - CONF_DATA, - CONF_SUPPORTED_MODES, - CONF_SUPPORTED_SWING_MODES, - CONF_SUPPORTED_PRESETS, - CONF_PRESSURE, - UNIT_CELSIUS, - UNIT_PERCENT, - UNIT_PASCAL, - ICON_POWER, - ICON_THERMOMETER, - ICON_GAS_CYLINDER, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_POWER_FACTOR, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, -) -from esphome.components.climate import ( - ClimateMode, - ClimatePreset, - ClimateSwingMode, -) - -_LOGGER = logging.getLogger(__name__) - -CODEOWNERS = ["@GrKoR"] -DEPENDENCIES = ["climate", "uart"] -AUTO_LOAD = ["sensor", "binary_sensor"] - -CONF_SUPPORTED_MODES = 'supported_modes' -CONF_SUPPORTED_SWING_MODES = 'supported_swing_modes' -CONF_SUPPORTED_PRESETS = 'supported_presets' -CONF_SHOW_ACTION = 'show_action' -CONF_INDOOR_TEMPERATURE = 'indoor_temperature' -CONF_OUTDOOR_TEMPERATURE = 'outdoor_temperature' -ICON_OUTDOOR_TEMPERATURE = 'mdi:home-thermometer-outline' -CONF_INBOUND_TEMPERATURE = 'inbound_temperature' -ICON_INBOUND_TEMPERATURE = 'mdi:thermometer-plus' -CONF_OUTBOUND_TEMPERATURE = 'outbound_temperature' -ICON_OUTBOUND_TEMPERATURE = 'mdi:thermometer-minus' -CONF_STRANGE_TEMPERATURE = 'strange_temperature' -ICON_STRANGE_TEMPERATURE = 'mdi:thermometer-lines' -CONF_DISPLAY_STATE = 'display_state' -CONF_INVERTOR_POWER = 'invertor_power' -CONF_DEFROST_STATE = 'defrost_state' -ICON_DEFROST = "mdi:snowflake-melt" -CONF_DISPLAY_INVERTED = 'display_inverted' -ICON_DISPLAY = "mdi:clock-digital" -CONF_STORE_SETTINGS = 'store_settings' - - -aux_ac_ns = cg.esphome_ns.namespace("aux_ac") -AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) -Capabilities = aux_ac_ns.namespace("Constants") - -AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action) -AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action) -AirConSendTestPacketAction = aux_ac_ns.class_("AirConSendTestPacketAction", automation.Action) - -ALLOWED_CLIMATE_MODES = { - "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, - "COOL": ClimateMode.CLIMATE_MODE_COOL, - "HEAT": ClimateMode.CLIMATE_MODE_HEAT, - "DRY": ClimateMode.CLIMATE_MODE_DRY, - "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, -} -validate_modes = cv.enum(ALLOWED_CLIMATE_MODES, upper=True) - -ALLOWED_CLIMATE_PRESETS = { - "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, -} -validate_presets = cv.enum(ALLOWED_CLIMATE_PRESETS, upper=True) - -ALLOWED_CLIMATE_SWING_MODES = { - "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, - "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, - "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, -} -validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True) - -CUSTOM_FAN_MODES = { - "MUTE": Capabilities.MUTE, - "TURBO": Capabilities.TURBO, -} -validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) - -CUSTOM_PRESETS = { - "CLEAN": Capabilities.CLEAN, - "HEALTH": Capabilities.HEALTH, - "ANTIFUNGUS": Capabilities.ANTIFUNGUS, -} -validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True) - - -def validate_raw_data(value): - if isinstance(value, list): - return cv.Schema([cv.hex_uint8_t])(value) - raise cv.Invalid( - "data must be a list of bytes" - ) - - -def output_info(config): - """_LOGGER.info(config)""" - return config - -CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(AirCon), - cv.Optional(CONF_PERIOD, default="7s"): cv.time_period, - cv.Optional(CONF_SHOW_ACTION, default="true"): cv.boolean, - cv.Optional(CONF_DISPLAY_INVERTED, default="false"): cv.boolean, - cv.Optional(CONF_STORE_SETTINGS, default="false"): cv.boolean, - cv.Optional(CONF_DEFROST_STATE, default="false"): cv.boolean, - cv.Optional(CONF_INVERTOR_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_PERCENT, - icon=ICON_POWER, - accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER_FACTOR, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_INDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_THERMOMETER, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_OUTDOOR_TEMPERATURE, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_INBOUND_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_INBOUND_TEMPERATURE, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_OUTBOUND_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_OUTBOUND_TEMPERATURE, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - cv.Optional(CONF_STRANGE_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_STRANGE_TEMPERATURE, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, - } - ), - - cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema( - icon=ICON_DISPLAY - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean - } - ), - - cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema( - icon=ICON_DEFROST - ).extend( - { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean - } - ), - - - cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes), - cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(validate_swing_modes), - cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(validate_presets), - cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(validate_custom_presets), - cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list(validate_custom_fan_modes), - } - ) - .extend(uart.UART_DEVICE_SCHEMA) - .extend(cv.COMPONENT_SCHEMA), - output_info -) - -async def to_code(config): - """_LOGGER.info("--------------")""" - """_LOGGER.info(config)""" - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await climate.register_climate(var, config) - - parent = await cg.get_variable(config[CONF_UART_ID]) - cg.add(var.initAC(parent)) - - if CONF_INDOOR_TEMPERATURE in config: - conf = config[CONF_INDOOR_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_indoor_temperature_sensor(sens)) - - if CONF_OUTDOOR_TEMPERATURE in config: - conf = config[CONF_OUTDOOR_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_outdoor_temperature_sensor(sens)) - - if CONF_OUTBOUND_TEMPERATURE in config: - conf = config[CONF_OUTBOUND_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_outbound_temperature_sensor(sens)) - - if CONF_INBOUND_TEMPERATURE in config: - conf = config[CONF_INBOUND_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_inbound_temperature_sensor(sens)) - - if CONF_STRANGE_TEMPERATURE in config: - conf = config[CONF_STRANGE_TEMPERATURE] - sens = await sensor.new_sensor(conf) - cg.add(var.set_strange_temperature_sensor(sens)) - - if CONF_DISPLAY_STATE in config: - conf = config[CONF_DISPLAY_STATE] - sens = await binary_sensor.new_binary_sensor(conf) - cg.add(var.set_display_sensor(sens)) - - if CONF_DEFROST_STATE in config: - conf = config[CONF_DEFROST_STATE] - sens = await binary_sensor.new_binary_sensor(conf) - cg.add(var.set_defrost_state(sens)) - - if CONF_INVERTOR_POWER in config: - conf = config[CONF_INVERTOR_POWER] - sens = await sensor.new_sensor(conf) - cg.add(var.set_invertor_power_sensor(sens)) - - cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) - cg.add(var.set_show_action(config[CONF_SHOW_ACTION])) - cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED])) - cg.add(var.set_store_settings(config[CONF_STORE_SETTINGS])) - if CONF_SUPPORTED_MODES in config: - cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) - if CONF_SUPPORTED_SWING_MODES in config: - cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) - if CONF_SUPPORTED_PRESETS in config: - cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) - if CONF_CUSTOM_PRESETS in config: - cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS])) - if CONF_CUSTOM_FAN_MODES in config: - cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) - - - -DISPLAY_ACTION_SCHEMA = maybe_simple_id( - { - cv.Required(CONF_ID): cv.use_id(AirCon), - } -) - -@automation.register_action("aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA) -async def display_off_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - return cg.new_Pvariable(action_id, template_arg, paren) - -@automation.register_action("aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA) -async def display_on_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - return cg.new_Pvariable(action_id, template_arg, paren) - - -SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id( - { - cv.Required(CONF_ID): cv.use_id(AirCon), - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), - } -) - - -# ********************************************************************************************************* -# ВАЖНО! Только для инженеров! -# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт -# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает. -# Вы действуете на свой страх и риск. -# ********************************************************************************************************* -@automation.register_action( - "aux_ac.send_packet", - AirConSendTestPacketAction, - SEND_TEST_PACKET_ACTION_SCHEMA -) -async def send_packet_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, paren) - - data = config[CONF_DATA] - if isinstance(data, bytes): - data = list(data) - - if cg.is_template(data): - templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) - cg.add(var.set_data_template(templ)) - else: - cg.add(var.set_data_static(data)) - - return var \ No newline at end of file From 0a0bee8a853375732313603beef0923f74e961dc Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:17:57 +0300 Subject: [PATCH 44/74] Add files via upload --- components/aux_ac/aux_ac.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index ca1f645..c91e397 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -529,7 +529,7 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // настройка усреднения фильтра температуры. Это значение - взнос нового измерения // в усредненные показания в процентах -#define OUTDOOR_FILTER_PESCENT 0.2 +#define OUTDOOR_FILTER_PESCENT 0.5 /** команда для кондиционера * @@ -1314,12 +1314,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // temp = big_info_body->outdoor_temperature - 0x20; // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах { - const float koef = ((float)OUTDOOR_FILTER_PESCENT); - const float antkoef = 100 - koef; - static float temp = _current_ac_state.temp_outdoor*100; + const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; + const float antkoef = 1.0 - koef; + static float temp = big_info_body->outdoor_temperature - 0x20; temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp/100); - _current_ac_state.temp_outdoor = temp/100; + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); + _current_ac_state.temp_outdoor = temp; } // температура входящей магистрали From 022596d555f23916a53187f06ac2e902f98a01a5 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 08:18:52 +0300 Subject: [PATCH 45/74] Add files via upload From b5c591d99f6045e7306ccc2470a582bb08b4ab56 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 11:42:58 +0300 Subject: [PATCH 46/74] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=8B=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=BE=D1=84=D0=B8=D0=BA=D1=81=D0=B8=D1=82=D1=8C?= =?UTF-8?q?=20=D0=BF=D1=83=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D1=8E?= =?UTF-8?q?=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 --- components/aux_ac/aux_ac.h | 69 ++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index c91e397..9600541 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -410,7 +410,7 @@ struct packet_small_info_body_t { uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны - // БФЙТ 7 + // БАЙТ 7 uint8_t mode; // режим работы сплита: // AUTO : bits[7, 6, 5] = [0, 0, 0] // COOL : bits[7, 6, 5] = [0, 0, 1] @@ -2144,16 +2144,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** FAN SPEED ***************************/ if(_current_ac_state.power == AC_POWER_ON){ - this->fan_mode = climate::CLIMATE_FAN_OFF; switch (_current_ac_state.fanSpeed) { case AC_FANSPEED_HIGH: this->fan_mode = climate::CLIMATE_FAN_HIGH; - this->custom_fan_mode = (std::string)""; break; case AC_FANSPEED_MEDIUM: this->fan_mode = climate::CLIMATE_FAN_MEDIUM; - this->custom_fan_mode = (std::string)""; break; case AC_FANSPEED_LOW: @@ -2165,6 +2162,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; default: + this->custom_fan_mode = Constants::MUTE; break; } /*************************** TURBO FAN MODE ***************************/ @@ -2188,7 +2186,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; } } else { // при выключеном питании публикуем фальшивый статус - //this->fan_mode = climate::CLIMATE_FAN_LOW ; this->custom_fan_mode = Constants::MUTE; } @@ -2490,7 +2487,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // вызывается пользователем из интерфейса ESPHome или Home Assistant void control(const esphome::climate::ClimateCall &call) override { bool hasCommand = false; - ac_command_t cmd; + static ac_command_t cmd; _clearCommand(&cmd); // не забываем очищать, а то будет мусор @@ -2504,7 +2501,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.power = AC_POWER_OFF; load_preset(&cmd, POS_MODE_OFF); cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_COOL: @@ -2512,7 +2509,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_COOL; load_preset(&cmd, POS_MODE_COOL); - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_HEAT: @@ -2520,7 +2517,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_HEAT; load_preset(&cmd, POS_MODE_HEAT); - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_HEAT_COOL: @@ -2531,7 +2528,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.temp_target = 25; // зависимость от режима HEAT_COOL cmd.temp_target_matter = true; cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -2546,7 +2543,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN } - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_DRY: @@ -2556,13 +2553,14 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { load_preset(&cmd, POS_MODE_DRY); cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY - this->mode = mode; + //this->mode = mode; break; case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) default: break; } + //this->mode = mode; } // User requested fan_mode change @@ -2580,30 +2578,31 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.louver.louver_h = AC_LOUVERH_OFF; cmd.louver.louver_v = AC_LOUVERV_OFF; hasCommand = true; - this->swing_mode = swingmode; + //this->swing_mode = swingmode; break; case climate::CLIMATE_SWING_BOTH: cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; hasCommand = true; - this->swing_mode = swingmode; + //this->swing_mode = swingmode; break; case climate::CLIMATE_SWING_VERTICAL: cmd.louver.louver_h = AC_LOUVERH_OFF; cmd.louver.louver_v = AC_LOUVERV_SWING_UPDOWN; hasCommand = true; - this->swing_mode = swingmode; + //this->swing_mode = swingmode; break; case climate::CLIMATE_SWING_HORIZONTAL: cmd.louver.louver_h = AC_LOUVERH_SWING_LEFTRIGHT; cmd.louver.louver_v = AC_LOUVERV_OFF; hasCommand = true; - this->swing_mode = swingmode; + //this->swing_mode = swingmode; break; } + //this->swing_mode = swingmode; } if (call.get_target_temperature().has_value()) { @@ -2640,7 +2639,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners cmd.fanTurbo = AC_FANTURBO_OFF; cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; + //this->fan_mode = fanmode; } break; @@ -2650,7 +2649,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners cmd.fanTurbo = AC_FANTURBO_OFF; cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; + //this->fan_mode = fanmode; break; case climate::CLIMATE_FAN_MEDIUM: @@ -2659,7 +2658,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners cmd.fanTurbo = AC_FANTURBO_OFF; cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; + //this->fan_mode = fanmode; break; case climate::CLIMATE_FAN_HIGH: @@ -2668,7 +2667,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners cmd.fanTurbo = AC_FANTURBO_OFF; cmd.fanMute = AC_FANMUTE_OFF; - this->fan_mode = fanmode; + //this->fan_mode = fanmode; break; case climate::CLIMATE_FAN_ON: @@ -2679,6 +2678,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { default: break; } + //this->fan_mode = fanmode; } else if (call.get_custom_fan_mode().has_value()) { std::string customfanmode = *call.get_custom_fan_mode(); @@ -2693,7 +2693,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.fanTurbo = AC_FANTURBO_ON; cmd.fanMute = AC_FANMUTE_OFF; // зависимость от fanturbo - this->custom_fan_mode = customfanmode; + //this->custom_fan_mode = customfanmode; } else { _debugMsg(F("TURBO fan mode is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } @@ -2707,11 +2707,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.fanMute = AC_FANMUTE_ON; cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от fanmute - this->custom_fan_mode = customfanmode; + //this->custom_fan_mode = customfanmode; //} else { // _debugMsg(F("MUTE fan mode is suitable in FAN mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); //} } + //this->custom_fan_mode = customfanmode; } // Пользователь выбрал пресет @@ -2731,7 +2732,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.sleep = AC_SLEEP_ON; cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; - this->preset = preset; + //this->preset = preset; } else { _debugMsg(F("SLEEP preset is suitable in COOL,HEAT and AUTO modes only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } @@ -2742,13 +2743,14 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - this->preset = preset; + //this->preset = preset; _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); break; default: // никакие другие встроенные пресеты не поддерживаются break; } + //this->preset = preset; } else if (call.get_custom_preset().has_value()) { std::string custom_preset = *call.get_custom_preset(); if (custom_preset == Constants::HEALTH) { @@ -2766,12 +2768,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } else if(cmd.mode == AC_MODE_FAN || _current_ac_state.mode == AC_MODE_FAN){ cmd.fanSpeed = AC_FANSPEED_MEDIUM; // зависимость от health } - this->custom_preset = custom_preset; + //this->custom_preset = custom_preset; } else if (custom_preset == Constants::CLEAN) { _debugMsg(F("CLEAN work only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } else if (custom_preset == Constants::ANTIFUNGUS) { _debugMsg(F("Anti-FUNGUS works only in POWER ON mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } + } + //this->custom_preset = custom_preset; } } else if(_current_ac_state.power == AC_POWER_OFF || cmd.power == AC_POWER_OFF){ // функции при выключеном питании @@ -2791,13 +2794,14 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.clean = AC_CLEAN_OFF; // для логики пресетов cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->preset = preset; + //this->preset = preset; _debugMsg(F("Clear all 'Power OFF' presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); break; default: // никакие другие встроенные пресеты не поддерживаются break; } + //this->preset = preset; } else { std::string custom_preset = *call.get_custom_preset(); if (call.get_custom_preset().has_value()) { @@ -2806,7 +2810,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.clean = AC_CLEAN_ON; cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - this->custom_preset = custom_preset; + //this->custom_preset = custom_preset; } else if (custom_preset == Constants::ANTIFUNGUS) { // Brokly: // включение-выключение функции "Антиплесень". @@ -2819,11 +2823,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.mildew = AC_MILDEW_ON; cmd.clean = AC_CLEAN_OFF; // для логики пресетов hasCommand = true; - this->custom_preset = custom_preset; + //this->custom_preset = custom_preset; } else if (custom_preset == Constants::HEALTH) { _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); } } + //this->custom_preset = custom_preset; } } @@ -3144,6 +3149,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { load_presets_result = storage.load(global_presets); // читаем все пресеты из флеша _debugMsg(F("Preset base read from NVRAM, result %02d."), ESPHOME_LOG_LEVEL_WARN, __LINE__, load_presets_result); #endif + this->preset = climate::CLIMATE_PRESET_NONE; + this->custom_preset = (std::string)""; + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->fan_mode = climate::CLIMATE_FAN_LOW; + this->custom_fan_mode = (std::string)""; }; void loop() override { From cb5d3421102428dc243112f47aea13d324183922 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 13:20:31 +0300 Subject: [PATCH 47/74] Update ac-energolux-bern.yaml --- examples/advanced/ac-energolux-bern.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml index 6b7acce..fc7b25b 100644 --- a/examples/advanced/ac-energolux-bern.yaml +++ b/examples/advanced/ac-energolux-bern.yaml @@ -118,7 +118,7 @@ climate: supported_presets: - SLEEP custom_presets: - - FEEL + - CLEEN - HEALTH - ANTIFUNGUS supported_swing_modes: From af7c5f9439a85d3dbd0542c2e94b5dfe90599018 Mon Sep 17 00:00:00 2001 From: Brokly Date: Fri, 27 May 2022 15:36:50 +0300 Subject: [PATCH 48/74] Update ac-energolux-bern.yaml --- examples/advanced/ac-energolux-bern.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml index fc7b25b..d850cb3 100644 --- a/examples/advanced/ac-energolux-bern.yaml +++ b/examples/advanced/ac-energolux-bern.yaml @@ -118,7 +118,7 @@ climate: supported_presets: - SLEEP custom_presets: - - CLEEN + - CLEAN - HEALTH - ANTIFUNGUS supported_swing_modes: From de987738a794eba661d6be9981ff711ecb5398c8 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Fri, 27 May 2022 20:27:44 +0300 Subject: [PATCH 49/74] enhancements by Brokly --- components/aux_ac/aux_ac.h | 348 ++++++++++++++++++++++--------------- 1 file changed, 208 insertions(+), 140 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 5d316ca..977aa10 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -90,7 +90,7 @@ 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::ANTIFUNGUS = "Antifungus"; const float Constants::AC_MIN_TEMPERATURE = 16.0; const float Constants::AC_MAX_TEMPERATURE = 32.0; const float Constants::AC_TEMPERATURE_STEP = 0.5; @@ -207,7 +207,7 @@ struct packet_big_info_body_t { uint8_t reserv20 :2; bool is_invertor_periodic :1; // флаг периодического пакета инверторного кондиционера uint8_t reserv23 :2; - bool is_invertor :1; // флаг инвертора + bool is_invertor :1; // флаг инвертора uint8_t reserv26 :2; // байт 3 тела (байт 11 пакета) @@ -229,14 +229,14 @@ 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 тела (байт 12 пакета) // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b12 uint8_t reserv40 :4; bool needDefrost :1; bool defrostMode :1; bool reserv46 :1; - bool cleen :1; + bool clean :1; // Для кондея старт-стоп // x xx // C5 1100 0101 @@ -258,6 +258,7 @@ struct packet_big_info_body_t { // байт 6 тела (байт 14 пакета) // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b14 + bool reserv60:1; uint8_t fanPWM:7; // ШИМ вентилятора @@ -380,13 +381,17 @@ enum ac_clean : uint8_t { AC_CLEAN_OFF = 0x00, AC_CLEAN_ON = 0x04, AC_CLEAN_UNTO enum ac_health : uint8_t { AC_HEALTH_OFF = 0x00, AC_HEALTH_ON = 0x02, AC_HEALTH_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_HEALTH_STATUS_MASK 0b00000001 +enum ac_health_status : uint8_t { AC_HEALTH_STATUS_OFF = 0x00, AC_HEALTH_STATUS_ON = 0x01, AC_HEALTH_STATUS_UNTOUCHED = 0xFF }; // целевая температура #define AC_TEMP_TARGET_INT_PART_MASK 0b11111000 #define AC_TEMP_TARGET_FRAC_PART_MASK 0b10000000 +// задержка отключения кондиционера +#define AC_TIMER_MINUTES_MASK 0b00111111 +#define AC_TIMER_HOURS_MASK 0b00011111 + // включение таймера сна #define AC_TIMER_MASK 0b01000000 enum ac_timer : uint8_t {AC_TIMER_OFF = 0x00, AC_TIMER_ON = 0x40, AC_TIMER_UNTOUCHED = 0xFF}; @@ -445,8 +450,8 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // маска счетчика минут прошедших с последней команды // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b12 -#define AC_MIN_COUTER 0b00111111 - +// GK: define убрал, т.к. считаю, что сбрасывать счетчик не надо. +// #define AC_MIN_COUNTER 0b00111111 /** команда для кондиционера * @@ -455,7 +460,7 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ */ //***************************************************************************** -// TODO: блок кода под сохранение пресетов. После решения - убрать +// TODO: presets блок кода под сохранение пресетов. После решения - убрать // данные структур содержат настройку, специально вынес в макрос #define AC_COMMAND_BASE float temp_target;\ ac_power power;\ @@ -473,8 +478,8 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_timer timer;\ uint8_t timer_hours;\ uint8_t timer_minutes;\ - bool temp_target_matter\ - + bool temp_target_matter + // чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше // из-за такого приема нужно контролировать размер копируемых данных руками #define AC_COMMAND_BASE_SIZE 21 @@ -496,27 +501,8 @@ enum store_pos : uint8_t { //***************************************************************************** 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_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_COMMAND_BASE; - ac_health_error health_error; + ac_health_status health_status; float temp_ambient; // внутренняя температура int8_t temp_outdoor; // внешняя температура int8_t temp_inbound; // температура входящая @@ -564,8 +550,8 @@ typedef ac_command_t ac_state_t; // текущее состояние пара // если для входящего пакета в последовательности указан таймаут 0, то используется значение по-умолчанию // если нужный пакет не поступил в течение указанного времени, то последовательность прерывается с ошибкой #define AC_SEQUENCE_DEFAULT_TIMEOUT 580 // Brokly: пришлось увеличить с 500 до 580 - -enum sequence_item_type_t : uint8_t { + + enum sequence_item_type_t : uint8_t { AC_SIT_NONE = 0x00, // пустой элемент последовательности AC_SIT_DELAY = 0x01, // пауза в последовательности на нужное количество миллисекунд AC_SIT_FUNC = 0x02 // рабочий элемент последовательности @@ -599,9 +585,8 @@ struct sequence_item_t { class AirCon : public esphome::Component, public esphome::climate::Climate { private: - //***************************************************************************** - // TODO: блок кода под сохранение пресетов. После решения - убрать + // TODO: presets блок кода под сохранение пресетов. После решения - убрать // массив для сохранения данных глобальных персетов ac_save_command_t global_presets[POS_MODE_OFF+1]; #if defined(ESP32) @@ -619,7 +604,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено //***************************************************************************** - // время последнего запроса статуса у кондея uint32_t _dataMillis; // периодичность обновления статуса кондея, по дефолту AC_STATES_REQUEST_INTERVAL @@ -848,7 +832,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_error = AC_HEALTH_ERROR_UNTOUCHED; + cmd->health_status = AC_HEALTH_STATUS_UNTOUCHED; cmd->iFeel = AC_IFEEL_UNTOUCHED; cmd->louver.louver_h = AC_LOUVERH_UNTOUCHED; cmd->louver.louver_v = AC_LOUVERV_UNTOUCHED; @@ -972,18 +956,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _clearInPacket(); _inPacket.msec = millis(); _setStateMachineState(ACSM_RECEIVING_PACKET); - //******************************************** экспериментальная секция ************************************************************* - // пробуем сократить время ответа с помощью прямых вызовов обработчиков, а не через состояние IDLE - //_doReceivingPacketState(); - // получилось всё те же 123 мсек. Только изредка падает до 109 мсек. Странно. - // логический анализатор показал примерно то же время от начала запроса до окончания ответа. - // запрос имеет длительность 18 мсек (лог.анализатор говорит 22,5 мсек). - // ответ имеет длительность 41 мсек по лог.анализатору. - // длительность паузы между запросом и ответом порядка 60 мсек. - // Скорее всего за один вызов _doReceivingPacketState не удается загрузить весь пакет (на момент вызова не все байы поступили в буфер UART) - // и поэтому программа отдает управление ESPHome для выполнения своих задач - // Стоит ли переделать код наоборот для непрерывного выполнения всё время, пока ожидается посылка - не знаю. Может быть такой риалтайм и не нужен. - //*********************************************************************************************************************************** } else { while (_ac_serial->available() > 0) @@ -1196,9 +1168,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_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_HEALTH_STATUS_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.health_status != (ac_health_status)stateByte); + _current_ac_state.health_status = (ac_health_status)stateByte; stateByte = small_info_body->status & AC_CLEAN_MASK; stateChangedFlag = stateChangedFlag || (_current_ac_state.clean != (ac_clean)stateByte); @@ -1214,6 +1186,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // уведомляем об изменении статуса сплита if (stateChangedFlag) stateChanged(); + break; } @@ -1230,22 +1203,22 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // тип кондея (инвертор или старт стоп) _is_invertor = big_info_body->is_invertor; - // температура воздуха в помещении по версии сплит-систему + // температура воздуха в помещении по версии сплит-системы stateFloat = big_info_body->ambient_temperature_int - 0x20 + (float)(big_info_body->ambient_temperature_frac & 0x0f) / 10.0; stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_ambient != stateFloat); _current_ac_state.temp_ambient = stateFloat; // некая температура из наружного блока, скорее всего температура испарителя - // temp = big_info_body->outdoor_temperature - 0x20; - // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах - { - const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; - const float antkoef = 1.0 - koef; - static float temp = _current_ac_state.temp_outdoor; - temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); - _current_ac_state.temp_outdoor = temp; - } + // GK: фильтрацию тут убрал. Лучше это делать в ESPHome. Для этого у сенсора есть возможности. А тут лучше иметь чистые значения для аналлиза. + + + + + stateFloat = big_info_body->outdoor_temperature - 0x20; + + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != stateFloat); + _current_ac_state.temp_outdoor = stateFloat; + // температура входящей магистрали stateFloat = big_info_body->in_temperature_int - 0x20; @@ -1279,6 +1252,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // уведомляем об изменении статуса сплита if (stateChangedFlag) stateChanged(); + + break; } @@ -1422,6 +1397,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (line == 0) line = __LINE__; _debugMsg(st, dbgLevel, line); #endif + } /** расчет CRC16 для блока данных data длиной len @@ -1582,7 +1558,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // обнулить счетчик минут с последней команды // GK: считаю, что так делать не надо. Штатный wifi-модуль не сбрасывает счетчик минут. - // pack->body[4] &= ~ AC_MIN_COUTER ; + // pack->body[4] &= ~ AC_MIN_COUNTER ; // вертикальные жалюзи if (cmd->louver.louver_v != AC_LOUVERV_UNTOUCHED){ @@ -1632,9 +1608,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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->health_status != AC_HEALTH_STATUS_UNTOUCHED){ + pack->body[10] = (pack->body[10] & ~AC_HEALTH_STATUS_MASK) | cmd->health_status; } // дисплей @@ -1647,6 +1624,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { pack->body[12] = (pack->body[12] & ~AC_MILDEW_MASK) | cmd->mildew; } + + // рассчитываем и записываем в пакет CRC pack->crc = (packet_crc_t *) &(pack->data[AC_HEADER_SIZE + pack->header->body_length]); _setCRC16(pack); @@ -1737,7 +1716,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // проверяем ответ bool relevant = true; relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x18 || _inPacket.header->body_length == 0x19)); // канальник Royal Clima отвечает пакетом длиной 0x19 + relevant = (relevant && (_inPacket.header->body_length == 0x18 || _inPacket.header->body_length == 0x19)); // канальник Royal Clima отвечает пакетом длиной 0x19 relevant = (relevant && (_inPacket.body[0] == 0x01)); relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_BIG)); @@ -2034,6 +2013,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } } + _debugMsg(F("Action mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->action); @@ -2089,6 +2069,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { this->fan_mode = climate::CLIMATE_FAN_AUTO; break; + case CLIMATE_FAN_OFF: + // так, чтобы состояние OFF не попадало в предупреждения о неизвестных режимах вентилятора + break; default: _debugMsg(F("Warning: unknown fan speed."), ESPHOME_LOG_LEVEL_WARN, __LINE__); break; @@ -2098,6 +2081,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** TURBO FAN MODE ***************************/ // TURBO работает только в режимах COOL и HEAT + // TODO: проверку на это несовместимые режимы пока выпилили, т.к. нет уверенности, что это поведение одинаково для всех switch (_current_ac_state.fanTurbo) { case AC_FANTURBO_ON: //if ((_current_ac_state.mode == AC_MODE_HEAT) || (_current_ac_state.mode == AC_MODE_COOL)) { @@ -2115,6 +2099,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** MUTE FAN MODE ***************************/ // MUTE работает только в режиме FAN. В режиме COOL кондей команду принимает, но MUTE не устанавливается + // TODO: проверку на это несовместимые режимы пока выпилили, т.к. нет уверенности, что это поведение одинаково для всех switch (_current_ac_state.fanMute) { case AC_FANMUTE_ON: //if (_current_ac_state.mode == AC_MODE_FAN) { @@ -2134,30 +2119,34 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** iFEEL CUSTOM PRESET ***************************/ // режим поддержки температуры в районе пульта, работает только при включенном конедее - switch (_current_ac_state.iFeel) { - case AC_IFEEL_ON: - if ( _current_ac_state.power == AC_POWER_ON) this->custom_preset = Constants::FEEL; - break; + if( _current_ac_state.iFeel == AC_IFEEL_ON && + _current_ac_state.power == AC_POWER_ON ) { + + this->custom_preset = Constants::FEEL; + + } else if ( this->custom_preset == Constants::FEEL ) { + + // AC_IFEEL_OFF + // только в том случае, если до этого пресет был установлен + this->custom_preset = (std::string)""; - 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 ***************************/ // режим работы ионизатора - switch (_current_ac_state.health) { - case AC_HEALTH_ON: - if ( _current_ac_state.power == AC_POWER_ON) this->custom_preset = Constants::HEALTH; - break; + if( _current_ac_state.health == AC_HEALTH_ON && + _current_ac_state.power == AC_POWER_ON ) { + + this->custom_preset = Constants::HEALTH; + + } else if ( this->custom_preset == Constants::HEALTH ) { + + // AC_HEALTH_OFF + // только в том случае, если до этого пресет был установлен + this->custom_preset = (std::string)""; - case AC_HEALTH_OFF: - default: - if (this->custom_preset == Constants::HEALTH) this->custom_preset = (std::string)""; - break; } _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); @@ -2167,33 +2156,34 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // 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) { - if ( _current_ac_state.power == AC_POWER_ON) this->preset = climate::CLIMATE_PRESET_SLEEP; - //} - break; + if( _current_ac_state.sleep == AC_SLEEP_ON && + _current_ac_state.power == AC_POWER_ON ) { + + this->preset = climate::CLIMATE_PRESET_SLEEP; + + } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { + + // AC_SLEEP_OFF + // только в том случае, если до этого пресет был установлен + this->preset = climate::CLIMATE_PRESET_NONE; - 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); /*************************** 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; + if( _current_ac_state.clean == AC_CLEAN_ON && + _current_ac_state.power == AC_POWER_OFF ) { + + this->custom_preset = Constants::CLEAN; + + } else if (this->custom_preset == Constants::CLEAN) { + + // AC_CLEAN_OFF + // только в том случае, если до этого пресет был установлен + this->custom_preset = (std::string)""; - 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); @@ -2210,6 +2200,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало // и затыкается. + // + // GK: оставил возможность включения функции в работающем состоянии, т.к. установка флага должна быть в работающем состоянии, + // а сама функция отработает при выключении сплита. + // У Brokly возможно какие-то особенности кондея. switch (_current_ac_state.mildew) { case AC_MILDEW_ON: this->custom_preset = Constants::ANTIFUNGUS; @@ -2226,9 +2220,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** LOUVERs ***************************/ this->swing_mode = climate::CLIMATE_SWING_OFF; - if( _current_ac_state.power == AC_POWER_OFF) { - this->swing_mode = climate::CLIMATE_SWING_OFF; - } else { + if( _current_ac_state.power == AC_POWER_ON) { if (_current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; } @@ -2317,7 +2309,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { ESP_LOGCONFIG(Constants::TAG, " [x] Show action: %s", TRUEFALSE(this->get_show_action())); ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", TRUEFALSE(this->get_display_inverted())); ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); - ESP_LOGCONFIG(Constants::TAG, " [?] Detect invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "unread"); + ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); if ((this->sensor_indoor_temperature_) != nullptr) { ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); if (!(this->sensor_indoor_temperature_)->get_device_class().empty()) { @@ -2465,6 +2457,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 @@ -2539,17 +2532,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } -/* -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@ -@@@@ -@@@@ ОСТАНОВИЛСЯ ТУТ!! -@@@@ -@@@@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -*/ - - // User requested fan_mode change if (call.get_fan_mode().has_value()) { ClimateFanMode fanmode = *call.get_fan_mode(); @@ -2614,6 +2596,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.fanTurbo = AC_FANTURBO_ON; + cmd.fanMute = AC_FANMUTE_OFF; // зависимость от fanturbo this->custom_fan_mode = customfanmode; } else { _debugMsg(F("TURBO fan mode is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); @@ -2623,46 +2606,65 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // 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 + // or _current_ac_state.mode == AC_MODE_FAN) { hasCommand = true; cmd.fanMute = AC_FANMUTE_ON; + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от fanmute this->custom_fan_mode = customfanmode; - } else { - _debugMsg(F("MUTE fan mode is suitable in FAN mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - } + //} 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 часов. + // Ночной режим (SLEEP). + // По инструкциям комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. + // Brokly: вроде как работает еще и с AUTO и DRY // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. if ( cmd.mode == AC_MODE_COOL or cmd.mode == AC_MODE_HEAT + or cmd.mode == AC_MODE_DRY + or cmd.mode == AC_MODE_AUTO or _current_ac_state.mode == AC_MODE_COOL - or _current_ac_state.mode == AC_MODE_HEAT) { + or _current_ac_state.mode == AC_MODE_HEAT + or _current_ac_state.mode == AC_MODE_DRY + or _current_ac_state.mode == AC_MODE_AUTO) { hasCommand = true; cmd.sleep = AC_SLEEP_ON; + cmd.health = AC_HEALTH_OFF; // для логики пресетов + cmd.health_status = AC_HEALTH_STATUS_OFF; this->preset = preset; } else { _debugMsg(F("SLEEP preset is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } break; - + case climate::CLIMATE_PRESET_NONE: + // выбран пустой пресет, сбрасываем все настройки + hasCommand = true; + cmd.health = AC_HEALTH_OFF; // для логики пресетов + cmd.health_status = AC_HEALTH_STATUS_OFF; + cmd.sleep = AC_SLEEP_OFF; // для логики пресетов + this->preset = preset; + + _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + break; default: // никакие другие встроенные пресеты не поддерживаются break; } } else if (call.get_custom_preset().has_value()) { - std::string custompreset = *call.get_custom_preset(); - if (custompreset == Constants::CLEAN) { + std::string custom_preset = *call.get_custom_preset(); + if (custom_preset == Constants::CLEAN) { // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF // TODO: надо отдебажить выключение этого режима if ( cmd.power == AC_POWER_OFF @@ -2670,31 +2672,66 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.clean = AC_CLEAN_ON; - this->custom_preset = custompreset; + cmd.mildew = AC_MILDEW_OFF; // для логики пресетов + this->custom_preset = custom_preset; } else { _debugMsg(F("CLEAN preset is suitable in POWER_OFF mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } - } else if (custompreset == Constants::FEEL) { + } else if (custom_preset == 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) { + //this->custom_preset = custom_preset; + } else if (custom_preset == Constants::HEALTH) { + //_debugMsg(F("HEALTH preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); + if ( cmd.power == AC_POWER_ON + or _current_ac_state.power == AC_POWER_ON) { + + hasCommand = true; + cmd.health = AC_HEALTH_ON; + //cmd.health_status = AC_HEALTH_STATUS_ON; // GK: статус кондей сам поднимает + 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 { + _debugMsg(F("HEALTH preset is suitable in POWER_ON mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + } + } else if (custom_preset == Constants::ANTIFUNGUS) { // включение-выключение функции "Антиплесень". // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - _debugMsg(F("ANTIFUNGUS preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); + + // Brokly: + // включение-выключение функции "Антиплесень". + // у меня пульт отправляет 5 посылок и на включение и на выключение, но реагирует на эту кнопку + // только в режиме POWER_OFF + // TODO: надо уточнить, в каких режимах штатно включается этот режим у кондиционера - //cmd.mildew = AC_MILDEW_ON; - //hasCommand = true; - //this->custom_preset = custompreset; + cmd.mildew = AC_MILDEW_ON; + cmd.clean = AC_CLEAN_OFF; // для логики пресетов + + hasCommand = true; + this->custom_preset = custom_preset; + //_debugMsg(F("ANTIFUNGUS preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); } } @@ -2748,9 +2785,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.temp_target = temp; cmd.temp_target_matter = true; } + if (hasCommand) { commandSequence(&cmd); this->publish_state(); // Publish updated state + // TODO: presets ********************************** + _new_command_set = _store_settings; // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка + // TODO: presets end ********************************** } } @@ -3049,18 +3090,45 @@ 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; } + // TODO: presets ********************************** + void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } + bool get_store_settings() { return this->_store_settings; } + // TODO: presets end ********************************** + void set_supported_modes(const std::set &modes) { this->_supported_modes = modes; } void set_supported_swing_modes(const std::set &modes) { this->_supported_swing_modes = modes; } void set_supported_presets(const std::set &presets) { this->_supported_presets = presets; } void set_custom_presets(const std::set &presets) { this->_supported_custom_presets = presets; } void set_custom_fan_modes(const std::set &modes) { this->_supported_custom_fan_modes = modes; } + uint8_t load_presets_result = 0xFF; // TODO: presets void setup() override { + // TODO: presets *********************************** + #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 + // TODO: presets ********************************** + this->preset = climate::CLIMATE_PRESET_NONE; + this->custom_preset = (std::string)""; + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->fan_mode = climate::CLIMATE_FAN_LOW; + this->custom_fan_mode = (std::string)""; + // TODO: presets end ?????????????????????????????????? }; void loop() override { if (!get_hw_initialized()) return; + // TODO: presets ********************************** + // контролируем сохранение пресета + if(_new_command_set){ //нужно сохранить пресет + _new_command_set = false; + save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов + } + // TODO: presets end *********************************** + /// отрабатываем состояния конечного автомата switch (_ac_state) { case ACSM_RECEIVING_PACKET: From e768f962c322f6b8c83f890e52366a8277718645 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Fri, 27 May 2022 21:22:25 +0300 Subject: [PATCH 50/74] climate_mode.h --- components/aux_ac/aux_ac.h | 1 + 1 file changed, 1 insertion(+) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 977aa10..eb4168d 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -9,6 +9,7 @@ #include #include "esphome/core/component.h" #include "esphome/components/climate/climate.h" +#include "esphome/components/climate/climate_mode.h" #include "esphome/components/uart/uart.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h" From d007ae78d408a0e0b065f48e2249205d6f1920ad Mon Sep 17 00:00:00 2001 From: GrKoR Date: Fri, 27 May 2022 23:38:47 +0300 Subject: [PATCH 51/74] presets saving was hidden under define --- components/aux_ac/aux_ac.h | 348 +++++++++++++++++++---------------- components/aux_ac/climate.py | 8 +- 2 files changed, 192 insertions(+), 164 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index eb4168d..e362190 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -9,7 +9,6 @@ #include #include "esphome/core/component.h" #include "esphome/components/climate/climate.h" -#include "esphome/components/climate/climate_mode.h" #include "esphome/components/uart/uart.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h" @@ -41,6 +40,7 @@ в общем, надо будет взвесить все за и против */ #if defined(ESP32) + #define PRESETS_SAVING #include "esphome/core/preferences.h" #else #warning "Saving presets does not work with ESP8266" @@ -111,7 +111,7 @@ enum acsm_state : uint8_t { // структура пакета описана тут: // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_structure #define AC_HEADER_SIZE 8 -#define AC_MAX_BODY_SIZE 24 +//#define AC_MAX_BODY_SIZE 24 // TODO: нигде не используется, можно удалить // стандартно длина пакета не более 34 байт // но встретилось исключение Royal Clima (как минимум, модель CO-D xxHNI) - у них 35 байт // пожтому буффер увеличен @@ -364,10 +364,6 @@ struct packet_small_info_body_t { //**************************************************************************************************************************************************** // для всех параметров ниже вариант X_UNTOUCHED = 0xFF означает, что этот параметр команды должен остаться тот, который уже установлен -// для показаний о реальной скорости фена из большого пакета -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 }; - // питание кондиционера #define AC_POWER_MASK 0b00100000 enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTOUCHED = 0xFF }; @@ -439,6 +435,9 @@ enum ac_fanturbo : uint8_t { AC_FANTURBO_OFF = 0x00, AC_FANTURBO_ON = 0x40, AC_F #define AC_FANMUTE_MASK 0b10000000 enum ac_fanmute : uint8_t { AC_FANMUTE_OFF = 0x00, AC_FANMUTE_ON = 0x80, AC_FANMUTE_UNTOUCHED = 0xFF }; +// реальная скорость вентилятора +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 }; + // включение-выключение дисплея на корпусе внутреннего блока #define AC_DISPLAY_MASK 0b00010000 enum ac_display : uint8_t { AC_DISPLAY_OFF = 0x00, AC_DISPLAY_ON = 0x10, AC_DISPLAY_UNTOUCHED = 0xFF }; @@ -452,7 +451,7 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // маска счетчика минут прошедших с последней команды // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b12 // GK: define убрал, т.к. считаю, что сбрасывать счетчик не надо. -// #define AC_MIN_COUNTER 0b00111111 +// #define AC_MIN_COUNTER_MASK 0b00111111 /** команда для кондиционера * @@ -485,20 +484,22 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // из-за такого приема нужно контролировать размер копируемых данных руками #define AC_COMMAND_BASE_SIZE 21 -// структура для сохранения данных -struct ac_save_command_t { - AC_COMMAND_BASE; -}; +#if defined(PRESETS_SAVING) + // структура для сохранения данных + 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 -}; + // номера сохранений пресетов + enum store_pos : uint8_t { + POS_MODE_AUTO = 0, + POS_MODE_COOL, + POS_MODE_DRY, + POS_MODE_HEAT, + POS_MODE_FAN, + POS_MODE_OFF + }; +#endif //***************************************************************************** struct ac_command_t { @@ -586,24 +587,23 @@ struct sequence_item_t { class AirCon : public esphome::Component, public esphome::climate::Climate { private: - //***************************************************************************** - // TODO: presets блок кода под сохранение пресетов. После решения - убрать - // массив для сохранения данных глобальных персетов - ac_save_command_t global_presets[POS_MODE_OFF+1]; - #if defined(ESP32) + #if defined(PRESETS_SAVING) + // массив для сохранения данных глобальных персетов + ac_save_command_t global_presets[POS_MODE_OFF+1]; + // тут будем хранить данные глобальных пресетов во флеше // ВНИМАНИЕ на данный момент 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); + + // настройка-ключ, для включения сохранения - восстановления настроек каждого + // режима работы в отдельности, то есть каждый режим работы имеет свои настройки + // температуры, шторок, скорости вентилятора, пресетов + bool _store_settings = false; + // флаги для сохранения пресетов + bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено #endif - // настройка-ключ, для включения сохранения - восстановления настроек каждого - // режима работы в отдельности, то есть каждый режим работы имеет свои настройки - // температуры, шторок, скорости вентилятора, пресетов - bool _store_settings = false; - // флаги для сохранения пресетов - bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено - //***************************************************************************** // время последнего запроса статуса у кондея uint32_t _dataMillis; @@ -1066,8 +1066,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _clearOutPacket(); _outPacket.msec = millis(); _outPacket.header->packet_type = AC_PTYPE_PING; - _outPacket.header->ping_answer_01 = 0x01; // только в ответе на пинг этот байт равен 0x01; что означает не ясно - _outPacket.header->body_length = 8; // в ответе на пинг у нас тело 8 байт + _outPacket.header->ping_answer_01 = 0x01; // магия, детали тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_type_ping + _outPacket.header->body_length = 8; _outPacket.body = &(_outPacket.data[AC_HEADER_SIZE]); // заполняем тело пакета @@ -1090,15 +1090,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _startupSequenceComlete = startupSequence(); } - // изначально предполагал, что передачу пакета на отправку выполнит обработчик IDLE, но показалось, что слишком долго - // логика отправки через IDLE в том, что получение запросов может быть важнее отправки ответов и IDLE позволяет реализовать такой приоритет - // но потом решил всё же напрямую отправлять в отправку - // в этом случае пинг-ответ заканчивает отправку спустя 144 мсек после стартового байта пинг-запроса - //_setStateMachineState(ACSM_IDLE); _setStateMachineState(ACSM_SENDING_PACKET); - // решил провести эксперимент - // получилось от начала запроса до отправки ответа порядка 165 мсек., если отправка идет не сразу, а через состояние IDLE - // Если сразу отсюда отправляться в обработчик отправки, то время сокращается до 131 мсек. Основные потери идут до входа в парсер пакетов + break; } @@ -1111,7 +1104,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; } - case AC_PTYPE_INFO: { // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля + case AC_PTYPE_INFO: { // информационный пакет // смотрим тип поступившего пакета по второму байту тела _debugMsg(F("Parser: status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); switch (_inPacket.body[1]) { @@ -1211,15 +1204,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // некая температура из наружного блока, скорее всего температура испарителя // GK: фильтрацию тут убрал. Лучше это делать в ESPHome. Для этого у сенсора есть возможности. А тут лучше иметь чистые значения для аналлиза. - - - - stateFloat = big_info_body->outdoor_temperature - 0x20; - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != stateFloat); _current_ac_state.temp_outdoor = stateFloat; - // температура входящей магистрали stateFloat = big_info_body->in_temperature_int - 0x20; @@ -1253,8 +1240,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // уведомляем об изменении статуса сплита if (stateChangedFlag) stateChanged(); - - + break; } @@ -1264,7 +1250,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // но я решил этот момент тут не проверять и не контролировать. // корректную установку параметров можно определить, запросив статус кондиционера сразу после получения этой команды кондея // в настоящий момент проверка сделана в механизме sequences - // TODO: если доводить до идеала, то проверку байтов 2 и 3 можно сделать и тут break; } @@ -1366,17 +1351,20 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // формируем данные #ifdef HOLMS // если этот дефайн объявлен, то в лог попадут только пакеты больше указанного в дефайне размера - // весь вывод будет в десятичном виде, а не в шестнадцатиричном + // при этом весь вывод будет в десятичном виде, данные будут разделены ";" + // и не будет выделения заголовков и CRC квадратными скобками 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 + // если дефайна HOLMS нет, то выводим пакеты в HEX и все подряд for (int i=0; ibytesLoaded; i++){ // для нормальных пакетов надо заключить заголовок в [] if ((!notAPacket) && (i == 0)) st += "["; @@ -1528,7 +1516,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (cmd == nullptr) return; // команда указана, дополнительно внесем в пакет те параметры, которые установлены в команде - // присваиваем параметры пакета pack->msec = millis(); pack->header->start_byte = AC_PACKET_START_BYTE; @@ -1559,7 +1546,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // обнулить счетчик минут с последней команды // GK: считаю, что так делать не надо. Штатный wifi-модуль не сбрасывает счетчик минут. - // pack->body[4] &= ~ AC_MIN_COUNTER ; + // pack->body[4] &= ~ AC_MIN_COUNTER_MASK ; // вертикальные жалюзи if (cmd->louver.louver_v != AC_LOUVERV_UNTOUCHED){ @@ -1843,55 +1830,57 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return true; } - // номер глобального пресета от режима работы - uint8_t get_num_preset(ac_command_t* cmd){ - if(cmd->power == AC_POWER_OFF){ + #if defined(PRESETS_SAVING) + // номер глобального пресета от режима работы + 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; - } else if(cmd->mode == AC_MODE_AUTO){ - return POS_MODE_AUTO; - } else if(cmd->mode == AC_MODE_COOL){ - return POS_MODE_COOL; - } else if(cmd->mode == AC_MODE_DRY){ - return POS_MODE_DRY; - } else if(cmd->mode == AC_MODE_FAN){ - return POS_MODE_FAN; - } else if(cmd->mode == AC_MODE_HEAT){ - return POS_MODE_HEAT; } - cmd->power = AC_POWER_OFF; - return POS_MODE_OFF; - } - - // восстановление данных из пресета - void load_preset(ac_command_t* cmd, uint8_t num_preset){ - if(num_preset < sizeof(global_presets)/sizeof(global_presets[0])){ // проверка выхода за пределы массива - if(cmd->power == global_presets[num_preset].power && cmd->mode == global_presets[num_preset].mode){ //контроль инициализации - memcpy(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE); // просто копируем из массива - _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } else { - _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + + // восстановление данных из пресета + void load_preset(ac_command_t* cmd, uint8_t num_preset){ + if(num_preset < sizeof(global_presets)/sizeof(global_presets[0])){ // проверка выхода за пределы массива + if(cmd->power == global_presets[num_preset].power && cmd->mode == global_presets[num_preset].mode){ //контроль инициализации + memcpy(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE); // просто копируем из массива + _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } else { + _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } } } - } - // запись данных в массив персетов - void save_preset(ac_command_t* cmd){ - uint8_t num_preset = get_num_preset(cmd); - if(memcmp(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE) != 0){ // содержимое пресетов разное - memcpy(&(global_presets[num_preset]), cmd, AC_COMMAND_BASE_SIZE); // копируем пресет в массив - #if defined(ESP32) - _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - if(storage.save(global_presets)){ - if(!global_preferences->sync()) // сохраняем все пресеты - _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } else { - _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } - #endif - } else { - _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + // запись данных в массив персетов + 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(PRESETS_SAVING) + _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + if(storage.save(global_presets)){ + if(!global_preferences->sync()) // сохраняем все пресеты + _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } else { + _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } + #endif + } else { + _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } } - } + #endif public: // инициализация объекта @@ -1899,10 +1888,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _dataMillis = millis(); _clearInPacket(); _clearOutPacket(); - _clearPacket(&_outTestPacket); - _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - _outTestPacket.header->wifi = AC_PACKET_ANSWER; _setStateMachineState(ACSM_IDLE); _ac_serial = parent; @@ -1926,7 +1912,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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_defrost_state(binary_sensor::BinarySensor *defrost_state_sensor) { sensor_defrost_ = defrost_state_sensor; } 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; } @@ -2070,9 +2056,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { this->fan_mode = climate::CLIMATE_FAN_AUTO; break; - case CLIMATE_FAN_OFF: - // так, чтобы состояние OFF не попадало в предупреждения о неизвестных режимах вентилятора - break; default: _debugMsg(F("Warning: unknown fan speed."), ESPHOME_LOG_LEVEL_WARN, __LINE__); break; @@ -2309,7 +2292,11 @@ 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", 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())); + + #if defined(PRESETS_SAVING) + ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); + #endif + ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); if ((this->sensor_indoor_temperature_) != nullptr) { ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); @@ -2469,7 +2456,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { case climate::CLIMATE_MODE_OFF: hasCommand = true; cmd.power = AC_POWER_OFF; - load_preset(&cmd, POS_MODE_OFF); + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_OFF); + #endif + cmd.temp_target = _current_ac_state.temp_ambient; // TODO: не нужно же this->mode = mode; break; @@ -2478,7 +2469,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_COOL; - load_preset(&cmd, POS_MODE_COOL); + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_COOL); + #endif + this->mode = mode; break; @@ -2486,7 +2481,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_HEAT; - load_preset(&cmd, POS_MODE_HEAT); + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_HEAT); + #endif + this->mode = mode; break; @@ -2494,7 +2493,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_AUTO; - load_preset(&cmd, POS_MODE_AUTO); + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_AUTO); + #endif + cmd.temp_target = 25; // зависимость от режима HEAT_COOL cmd.temp_target_matter = true; cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL @@ -2505,7 +2508,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_FAN; - load_preset(&cmd, POS_MODE_FAN); + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_FAN); + #endif + cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN cmd.temp_target_matter = true; cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN @@ -2520,7 +2527,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_DRY; - load_preset(&cmd, POS_MODE_DRY); + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_DRY); + #endif + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY this->mode = mode; @@ -2663,6 +2674,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // никакие другие встроенные пресеты не поддерживаются break; } + } else if (call.get_custom_preset().has_value()) { std::string custom_preset = *call.get_custom_preset(); if (custom_preset == Constants::CLEAN) { @@ -2679,15 +2691,17 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } else { _debugMsg(F("CLEAN preset is suitable in POWER_OFF mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } + } else if (custom_preset == Constants::FEEL) { _debugMsg(F("iFEEL preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); // TODO: надо подумать, как заставить этот режим работать без пульта //hasCommand = true; //this->custom_preset = custom_preset; - } else if (custom_preset == Constants::HEALTH) { - //_debugMsg(F("HEALTH preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); - if ( cmd.power == AC_POWER_ON - or _current_ac_state.power == AC_POWER_ON) { + + } else if ( custom_preset == Constants::HEALTH ) { + + if ( cmd.power == AC_POWER_ON || + _current_ac_state.power == AC_POWER_ON ) { hasCommand = true; cmd.health = AC_HEALTH_ON; @@ -2695,17 +2709,18 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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 ){ + _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){ + _current_ac_state.mode == AC_MODE_FAN ) { cmd.fanSpeed = AC_FANSPEED_MEDIUM; // зависимость от health @@ -2715,6 +2730,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } else { _debugMsg(F("HEALTH preset is suitable in POWER_ON mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } + } else if (custom_preset == Constants::ANTIFUNGUS) { // включение-выключение функции "Антиплесень". // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. @@ -2790,9 +2806,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (hasCommand) { commandSequence(&cmd); this->publish_state(); // Publish updated state - // TODO: presets ********************************** - _new_command_set = _store_settings; // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка - // TODO: presets end ********************************** + + #if defined(PRESETS_SAVING) + // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка + _new_command_set = _store_settings; + #endif } } @@ -2801,7 +2819,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_two_point_target_temperature(false); // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature + + // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature + traits.set_supports_two_point_target_temperature(false); // tells the frontend what range of temperatures the climate device should display (gauge min/max values) traits.set_visual_min_temperature(Constants::AC_MIN_TEMPERATURE); @@ -2828,10 +2848,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); //traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); - /* *************** TODO: надо сделать информирование о текущем режиме, сплит поддерживает *************** - * смотри climate::ClimateAction - */ - // if the climate device supports reporting the active current action of the device with the action property. traits.set_supports_action(this->_show_action); return traits; @@ -3022,34 +3038,44 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } // отправляет сплиту заданный набор байт - // Перед отправкой проверяет пакет на корректность структуры. CRC16 рассчитывает самостоятельно и перезаписывает. + // Перед отправкой: + // устанавливает первый байт в 0xBB + // проверяет, чтобы длина тела пакета в заголовке не превышала длину буфера + // рассчитывает и записывает в конец пакета CRC 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 (data.size() == 0) { + _debugMsg(F("sendTestPacket: no data to send."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + 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) { + // всё, что не влезет в буфер - игнорируем + if (i >= AC_BUFFER_SIZE) { + _debugMsg(F("sendTestPacket: buffer size = %02d, data length = %02d. Extra data was omitted."), ESPHOME_LOG_LEVEL_ERROR, __LINE__, AC_BUFFER_SIZE, data.size()); + break; + } + // что влезает - копируем в буфер _outTestPacket.data[i] = n; i++; } - // на всякий случай указываем правильные некоторые байты + // на всякий случай указываем правильные некоторые байты: + // - установим стартовый байт _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - //_outTestPacket.header->wifi = AC_PACKET_ANSWER; + // - установим длину тела, если она больше возможной для нашего буфера + if (_outTestPacket.header->body_length > (AC_BUFFER_SIZE - AC_HEADER_SIZE - 2)) _outTestPacket.header->body_length = AC_BUFFER_SIZE - AC_HEADER_SIZE - 2; _outTestPacket.msec = millis(); _outTestPacket.body = &(_outTestPacket.data[AC_HEADER_SIZE]); @@ -3059,7 +3085,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _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__); + _debugMsg(F("sendTestPacket: test packet loaded:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); _debugPrintPacket(&_outTestPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); // ниже блок добавления отправки пакета в последовательность команд @@ -3091,44 +3117,46 @@ 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; } - // TODO: presets ********************************** - void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } - bool get_store_settings() { return this->_store_settings; } - // TODO: presets end ********************************** - void set_supported_modes(const std::set &modes) { this->_supported_modes = modes; } void set_supported_swing_modes(const std::set &modes) { this->_supported_swing_modes = modes; } void set_supported_presets(const std::set &presets) { this->_supported_presets = presets; } void set_custom_presets(const std::set &presets) { this->_supported_custom_presets = presets; } void set_custom_fan_modes(const std::set &modes) { this->_supported_custom_fan_modes = modes; } - uint8_t load_presets_result = 0xFF; // TODO: presets + #if defined(PRESETS_SAVING) + void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } + bool get_store_settings() { return this->_store_settings; } + + uint8_t load_presets_result = 0xFF; + #endif + void setup() override { - // TODO: presets *********************************** - #if defined(ESP32) + + #if defined(PRESETS_SAVING) load_presets_result = storage.load(global_presets); // читаем все пресеты из флеша _debugMsg(F("Preset base read from NVRAM, result %02d."), ESPHOME_LOG_LEVEL_WARN, __LINE__, load_presets_result); + + // TODO: Может это надо вынести за пределы дефайна пресетов? + // если это всё же нужно при инициализации объекта, то надо закинуть в initAC() + this->preset = climate::CLIMATE_PRESET_NONE; + this->custom_preset = (std::string)""; + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->fan_mode = climate::CLIMATE_FAN_LOW; + this->custom_fan_mode = (std::string)""; #endif - // TODO: presets ********************************** - this->preset = climate::CLIMATE_PRESET_NONE; - this->custom_preset = (std::string)""; - this->mode = climate::CLIMATE_MODE_OFF; - this->action = climate::CLIMATE_ACTION_IDLE; - this->fan_mode = climate::CLIMATE_FAN_LOW; - this->custom_fan_mode = (std::string)""; - // TODO: presets end ?????????????????????????????????? }; void loop() override { if (!get_hw_initialized()) return; - // TODO: presets ********************************** - // контролируем сохранение пресета - if(_new_command_set){ //нужно сохранить пресет - _new_command_set = false; - save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов - } - // TODO: presets end *********************************** + #if defined(PRESETS_SAVING) + // контролируем сохранение пресета + if(_new_command_set){ //нужно сохранить пресет + _new_command_set = false; + save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов + } + #endif /// отрабатываем состояния конечного автомата switch (_ac_state) { diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index a3c9676..02e9305 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -15,16 +15,16 @@ from esphome.const import ( CONF_SUPPORTED_MODES, CONF_SUPPORTED_SWING_MODES, CONF_SUPPORTED_PRESETS, - CONF_PRESSURE, + #CONF_PRESSURE, UNIT_CELSIUS, UNIT_PERCENT, - UNIT_PASCAL, + #UNIT_PASCAL, ICON_POWER, ICON_THERMOMETER, - ICON_GAS_CYLINDER, + #ICON_GAS_CYLINDER, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_POWER_FACTOR, - DEVICE_CLASS_PRESSURE, + #DEVICE_CLASS_PRESSURE, STATE_CLASS_MEASUREMENT, ) from esphome.components.climate import ( From d83112876eef3dfadfa553d028eb98dd7b02fe79 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Sat, 28 May 2022 00:17:02 +0300 Subject: [PATCH 52/74] + --- components/aux_ac/aux_ac.h | 64 +++++++++++++------------------------- 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index e362190..950dcdb 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -14,39 +14,18 @@ #include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/core/helpers.h" -/* TODO: Помечаю, чтобы не забыть. +// весь функционал сохранения пресетов прячу под дефайн +//#define PRESETS_SAVING +#ifdef PRESETS_SAVING + #ifdef ESP32 + #include "esphome/core/preferences.h" + #else + #warning "Saving presets does not work with ESP8266" + #endif +#endif - GK: - Не понятно пока, зачем сохранять настройки в энергонезависимую память. - Вроде бы компоненту это не требуется - он отражает текущее состояние кондея. - А кондей при включении после пропадания питания сам переходит в последний включенный режим. - В общем, пока не понятно. Ждём пояснений =) - - Brokly: У меня пульт выставляет свои настройки в завасимости от режима. У нагрева свои градусы со шторками , - у охлаждения свои градусы со своими шторками - - GK: ну то есть ты сохраняешь в памяти эти настройки, чтобы в умном доме вместо отправки указания - "выстави нагрев до 24 градусов + шторки так-то", отправлять просто команду "выстави нагрев", - а уже есп будет подтягивать температуру и шторки из памяти. Так? или я не понял? - - Brokly: Да. А что эта функция чему то мешает ? - - GK: фича не мешает, но наверное может вести к проблемам. Типа износа ячеек памяти... а может и не привести... - пока не уверен, что эту фичу надо в мастер кидать. - плюс исходники компонента вбирают в себя дополнительный функционал, который можно реализовать иначе. - Как минимум, старт с определенного пресета можно выполнить настройками в yaml (через глобальные переменные). - Не факт, что это будет проще и\или оптимальнее. Но зато фичи компонента будут соответствовать принципам атомарности, - в нем не будет дополнительного кода, который может в компоненте не быть. - в общем, надо будет взвесить все за и против -*/ -#if defined(ESP32) - #define PRESETS_SAVING - #include "esphome/core/preferences.h" -#else - #warning "Saving presets does not work with ESP8266" -#endif - -//#define HOLMS 19 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны +// раскоментируй ключ HOLMS для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны +//#define HOLMS 19 namespace esphome { @@ -86,8 +65,8 @@ public: const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; const char *const Constants::TAG = "AirCon"; -const std::string Constants::MUTE = "Mute"; -const std::string Constants::TURBO = "Turbo"; +const std::string Constants::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"; @@ -2451,7 +2430,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // User requested mode change if (call.get_mode().has_value()) { ClimateMode mode = *call.get_mode(); - // Send mode to hardware + switch (mode) { case climate::CLIMATE_MODE_OFF: hasCommand = true; @@ -2460,8 +2439,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { #if defined(PRESETS_SAVING) load_preset(&cmd, POS_MODE_OFF); #endif - - cmd.temp_target = _current_ac_state.temp_ambient; // TODO: не нужно же + this->mode = mode; break; @@ -2547,7 +2525,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // 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; @@ -2596,23 +2574,25 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } 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. + // TURBO fan mode is suitable in COOL and HEAT modes. // 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) { - + */ hasCommand = true; cmd.fanTurbo = AC_FANTURBO_ON; cmd.fanMute = AC_FANMUTE_OFF; // зависимость от fanturbo this->custom_fan_mode = customfanmode; + /* } else { _debugMsg(F("TURBO fan mode is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } + */ } else if (customfanmode == Constants::MUTE) { // MUTE fan mode is suitable in FAN mode only for Rovex air conditioner. From 9e4ee7f94631b3fd312be308c8d426b1433ab1dc Mon Sep 17 00:00:00 2001 From: GrKoR Date: Sat, 28 May 2022 01:42:10 +0300 Subject: [PATCH 53/74] fan mode tuned --- components/aux_ac/aux_ac.h | 78 +++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 950dcdb..1bed9af 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -65,12 +65,18 @@ public: const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; const char *const Constants::TAG = "AirCon"; + +// custom fan modes const std::string Constants::MUTE = "mute"; const std::string Constants::TURBO = "turbo"; + +// custom presets const std::string Constants::CLEAN = "Clean"; const std::string Constants::FEEL = "Feel"; const std::string Constants::HEALTH = "Health"; const std::string Constants::ANTIFUNGUS = "Antifungus"; + +// params const float Constants::AC_MIN_TEMPERATURE = 16.0; const float Constants::AC_MAX_TEMPERATURE = 32.0; const float Constants::AC_TEMPERATURE_STEP = 0.5; @@ -2493,11 +2499,13 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN cmd.temp_target_matter = true; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN + // GK: в режиме FAN работает TURBO, так что отключать не нужно! + //cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN cmd.sleep = AC_SLEEP_OFF; - if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ + // GK: для меня AUTO = HIGH. Скорее всего сплит сам меняет скорость. Поэтому ниже закомментировал + /* if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN - } + } */ this->mode = mode; break; @@ -2515,7 +2523,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { this->mode = mode; break; - case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) + // другие возможные значения (чтоб не забыть) + //case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) default: break; } @@ -2530,7 +2539,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; @@ -2539,7 +2547,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; @@ -2548,7 +2555,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; @@ -2557,17 +2563,17 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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: + // другие возможные значения (чтобы не забыть) + //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; } @@ -2614,6 +2620,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // Пользователь выбрал пресет if (call.get_preset().has_value()) { ClimatePreset preset = *call.get_preset(); + switch (preset) { case climate::CLIMATE_PRESET_SLEEP: // Ночной режим (SLEEP). @@ -2640,23 +2647,29 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("SLEEP preset is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } break; - case climate::CLIMATE_PRESET_NONE: - // выбран пустой пресет, сбрасываем все настройки - hasCommand = true; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - this->preset = preset; - - _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + case climate::CLIMATE_PRESET_NONE: + // выбран пустой пресет, сбрасываем все настройки + hasCommand = true; + cmd.health = AC_HEALTH_OFF; + //cmd.health_status = AC_HEALTH_STATUS_OFF; // GK: не нужно ставить, т.к. этот флаг устанавливается самим сплитом + cmd.sleep = AC_SLEEP_OFF; + cmd.mildew = AC_MILDEW_OFF; + cmd.clean = AC_CLEAN_OFF; + cmd.iFeel = AC_IFEEL_OFF; // хоть и не реализован, но отрубим. А то потом забудем указать. + this->preset = preset; + + _debugMsg(F("Clear all builtin presets."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); break; default: // никакие другие встроенные пресеты не поддерживаются + _debugMsg(F("Preset %02X is unsupported."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, preset); break; } } else if (call.get_custom_preset().has_value()) { std::string custom_preset = *call.get_custom_preset(); + if (custom_preset == Constants::CLEAN) { // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF // TODO: надо отдебажить выключение этого режима @@ -2735,7 +2748,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // 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". @@ -2772,15 +2785,18 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } + // User requested target temperature change 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; + // выставлять температуру в режиме FAN не нужно + if ( cmd.fanSpeed != AC_FANSPEED_AUTO && _current_ac_state.fanSpeed != AC_FANSPEED_AUTO ) { + hasCommand = true; + 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) { From 576c6e249e83bbe823556bb84298c06f75184c08 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Sat, 28 May 2022 05:01:48 +0300 Subject: [PATCH 54/74] some tests --- components/aux_ac/aux_ac.h | 32 ++++--- tests/ac_send_packet_for_engineer.py | 119 +++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 33 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 1bed9af..98308c5 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -393,12 +393,12 @@ enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTO #define AC_IFEEL_MASK 0b00001000 enum ac_ifeel : uint8_t { AC_IFEEL_OFF = 0x00, AC_IFEEL_ON = 0x08, AC_IFEEL_UNTOUCHED = 0xFF }; -// Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. +// Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но должна быть такая возможность на уровне железа. // TODO: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V #define AC_LOUVERV_MASK 0b00000111 enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_UPDOWN = 0x00, AC_LOUVERV_OFF = 0x07, AC_LOUVERV_UNTOUCHED = 0xFF }; -// Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. +// Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но должна быть такая возможность на уровне железа. // TODO: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H #define AC_LOUVERH_MASK 0b11100000 enum ac_louver_H : uint8_t { AC_LOUVERH_SWING_LEFTRIGHT = 0x00, AC_LOUVERH_OFF = 0xE0, AC_LOUVERH_UNTOUCHED = 0xFF }; @@ -1993,7 +1993,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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 + // по факту режим, названный в AUX как AUTO, является режимом HEAT_COOL + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; case AC_MODE_COOL: @@ -2049,8 +2050,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("Climate fan mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->fan_mode); /*************************** TURBO FAN MODE ***************************/ - // TURBO работает только в режимах COOL и HEAT - // TODO: проверку на это несовместимые режимы пока выпилили, т.к. нет уверенности, что это поведение одинаково для всех + // TURBO работает в режимах FAN, COOL, HEAT, HEAT_COOL + // в режиме DRY изменение скорости вентилятора никак не влияло на его скорость, может сплит просто не вышел еще на режим? Надо попробовать долгую работу в этом режиме. switch (_current_ac_state.fanTurbo) { case AC_FANTURBO_ON: //if ((_current_ac_state.mode == AC_MODE_HEAT) || (_current_ac_state.mode == AC_MODE_COOL)) { @@ -2067,7 +2068,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("Climate fan TURBO mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanTurbo); /*************************** MUTE FAN MODE ***************************/ - // MUTE работает только в режиме FAN. В режиме COOL кондей команду принимает, но MUTE не устанавливается + // MUTE работает в режиме FAN. В режимах HEAT, COOL, HEAT_COOL не работает. DRY не проверял. // TODO: проверку на это несовместимые режимы пока выпилили, т.к. нет уверенности, что это поведение одинаково для всех switch (_current_ac_state.fanMute) { case AC_FANMUTE_ON: @@ -2085,7 +2086,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ - /*************************** iFEEL CUSTOM PRESET ***************************/ // режим поддержки температуры в районе пульта, работает только при включенном конедее if( _current_ac_state.iFeel == AC_IFEEL_ON && @@ -2190,15 +2190,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** LOUVERs ***************************/ this->swing_mode = climate::CLIMATE_SWING_OFF; if( _current_ac_state.power == AC_POWER_ON) { - if (_current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ + if ( _current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT && _current_ac_state.louver.louver_v == AC_LOUVERV_OFF ){ 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_h == AC_LOUVERH_OFF && _current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN ){ + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else if ( _current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT && _current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN ){ + this->swing_mode = climate::CLIMATE_SWING_BOTH; } } @@ -2206,7 +2203,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** TEMPERATURE ***************************/ if(_current_ac_state.mode == AC_MODE_FAN || _current_ac_state.power == AC_POWER_OFF){ - this->target_temperature = _current_ac_state.temp_ambient; // в режиме вентилятора и в выключенном состоянии будем показывать текущую температуру + // в режиме вентилятора и в выключенном состоянии будем показывать текущую температуру + this->target_temperature = _current_ac_state.temp_ambient; } else if (_current_ac_state.mode == AC_MODE_AUTO ){ this->target_temperature = 25; // в AUTO зашита температура 25 градусов } else { @@ -2788,7 +2786,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // User requested target temperature change if (call.get_target_temperature().has_value()) { // выставлять температуру в режиме FAN не нужно - if ( cmd.fanSpeed != AC_FANSPEED_AUTO && _current_ac_state.fanSpeed != AC_FANSPEED_AUTO ) { + if ( cmd.mode != AC_MODE_FAN && _current_ac_state.mode != AC_MODE_FAN ) { hasCommand = true; float temp = *call.get_target_temperature(); // Send target temp to climate diff --git a/tests/ac_send_packet_for_engineer.py b/tests/ac_send_packet_for_engineer.py index ccc1571..afd4380 100644 --- a/tests/ac_send_packet_for_engineer.py +++ b/tests/ac_send_packet_for_engineer.py @@ -81,12 +81,65 @@ async def main(): if isDallasLog: log_Dallas(isDallasLog) + async def display_off(): + await api.execute_service( + service, + data={ + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x01, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00], + } + ) + + async def display_on(): + await api.execute_service( + service, + data={ + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x01, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00], + } + ) + + async def ac_enable(): + await api.execute_service( + service, + data={ + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x19, 0x60, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00], + } + ) + + async def ac_disable(): + await api.execute_service( + service, + data={ + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x19, 0x60, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + } + ) + + async def ac_set_vlouver(lvr): + await api.execute_service( + service, + data={ + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, lvr, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00], + } + ) + + async def ac_set_hlouver(lvr): + await api.execute_service( + service, + data={ + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, lvr, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00], + } + ) # Subscribe to the log # await api.subscribe_logs(log_callback, LOG_LEVEL_DEBUG) # print(await api.device_info()) - print(f"%s" % (await api.list_entities_services(),)) + # print(f"%s" % (await api.list_entities_services(),)) # key надо искать в выводе list_entities_services service = aioesphomeapi.UserService( @@ -97,23 +150,57 @@ async def main(): ], ) - await api.execute_service( - service, - data={ - # display off - "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x7F, 0xE0, 0x00, 0x20, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00], - } - ) + #await ac_set_vlouver( 0b10010000 ) # swing on + #await ac_set_vlouver( 0b10010111 ) # swing off + #await ac_set_vlouver( 0b10010001 ) # 1 + #await ac_set_vlouver( 0b10010010 ) # 2 + #await ac_set_vlouver( 0b10010011 ) # 3 + #await ac_set_vlouver( 0b10010100 ) # 4 + #await ac_set_vlouver( 0b10010101 ) # 5 + #await ac_set_vlouver( 0b10010110 ) # не работает, сбрасывает на swing on + #time.sleep(5) - time.sleep(3) + #await ac_set_hlouver( 0b00000000 ) # swing on + #await ac_set_hlouver( 0b11100000 ) # swing off + #await ac_set_hlouver( 0b00100000 ) # не работает, сбрасывает в swing off + #await ac_set_hlouver( 0b01000000 ) # не работает, сбрасывает в swing off + #await ac_set_hlouver( 0b01100000 ) # не работает, сбрасывает в swing off + #await ac_set_hlouver( 0b10000000 ) # не работает, сбрасывает в swing off + #await ac_set_hlouver( 0b10100000 ) # не работает, сбрасывает в swing off + #await ac_set_hlouver( 0b11000000 ) # не работает, сбрасывает в swing off + #time.sleep(5) + + async def test_byte(bt): + await api.execute_service( + service, + data={ + #display on + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + #"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00], + #display off + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00], + # swing on + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + #"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x90, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00], + # swing off + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + #"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00], + } + ) + ''' + не проходит команда, если байт 1 или 7 не 0x00 + не проходит команда, если байт 3 не 0x80 + + проходит и не меняется, если меняю байт 4 или 5 + ''' + + #await test_byte(0b10000110) + #await test_byte(0b01000110) + #await test_byte(0b00100110) + #await test_byte(0b00010110) + time.sleep(2) - await api.execute_service( - service, - data={ - # display on - "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x7F, 0xE0, 0x00, 0x20, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], - } - ) parser = createParser() namespace = parser.parse_args() From d2c96a2da952a321fd3502f82b36502e755fa4b9 Mon Sep 17 00:00:00 2001 From: Brokly Date: Sun, 29 May 2022 15:05:46 +0300 Subject: [PATCH 55/74] Retain --- components/aux_ac/aux_ac.h | 1687 ++++++++++++++++++------------------ 1 file changed, 837 insertions(+), 850 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 9600541..6afeb8c 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -13,13 +13,20 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/core/helpers.h" -#if defined(ESP32) - #include "esphome/core/preferences.h" -#else - #warning "Saving presets does not work with ESP8266" -#endif -//#define HOLMS 9 // раскоментируй ключ для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны +// весь функционал сохранения пресетов прячу под дефайн +//#define PRESETS_SAVING +#ifdef PRESETS_SAVING + #ifdef ESP32 + #include "esphome/core/preferences.h" + #else + #warning "Saving presets does not work with ESP8266" + #endif +#endif + +// раскоментируй ключ HOLMS для вывода лога под Эксель, значение ключа - размер пакетов которые будут видны +//#define HOLMS 19 + namespace esphome { namespace aux_ac { @@ -40,6 +47,7 @@ public: static const std::string MUTE; static const std::string TURBO; static const std::string CLEAN; + static const std::string FEEL; static const std::string HEALTH; static const std::string ANTIFUNGUS; @@ -57,16 +65,24 @@ public: const std::string Constants::AC_FIRMWARE_VERSION = "0.2.4"; const char *const Constants::TAG = "AirCon"; + +// custom fan modes const std::string Constants::MUTE = "mute"; const std::string Constants::TURBO = "turbo"; -const std::string Constants::CLEAN = "clean"; -const std::string Constants::HEALTH = "health"; -const std::string Constants::ANTIFUNGUS = "antifungus"; + +// custom presets +const std::string Constants::CLEAN = "Clean"; +const std::string Constants::FEEL = "Feel"; +const std::string Constants::HEALTH = "Health"; +const std::string Constants::ANTIFUNGUS = "Antifungus"; + +// params 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; // состояния конечного автомата компонента @@ -77,80 +93,68 @@ enum acsm_state : uint8_t { ACSM_SENDING_PACKET, // отправляем пакет сплиту }; -/** - * Кондиционер отправляет пакеты следующей структуры: - * HEADER: 8 bytes - * BODY: 0..24 bytes - * CRC: 2 bytes - * Весь пакет максимум 34 байта - * По крайней мере все встреченные мной пакеты имели такой размер и структуру. - **/ +// структура пакета описана тут: +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_structure #define AC_HEADER_SIZE 8 -#define AC_MAX_BODY_SIZE 24 +//#define AC_MAX_BODY_SIZE 24 // TODO: нигде не используется, можно удалить +// стандартно длина пакета не более 34 байт +// но встретилось исключение Royal Clima (как минимум, модель CO-D xxHNI) - у них 35 байт +// пожтому буффер увеличен #define AC_BUFFER_SIZE 35 /** * таймаут загрузки пакета * * через такое количиство миллисекунд конечный автомат перейдет из состояния ACSM_RECEIVING_PACKET в ACSM_IDLE, если пакет не будет загружен - * расчетное время передачи 1 бита при скорости 4800 примерно 0,208 миллисекунд; - * 1 байт передается 11 битами (1 стартовый, 8 бит данных, 1 бит четности и 1 стоповый бит) или 2,30 мс. - * максимальный размер пакета AC_BUFFER_SIZE = 34 байта => 78,2 мсек. Плюс накладные расходы. - * Скорее всего на получение пакета должно хватать 100 мсек. - * - * По факту проверка показала: - * - если отрабатывать по 1 символу из UART на один вызов loop, то на 10 байт пинг-пакета требуется 166 мсек. - * То есть примерно по 16,6 мсек на байт. Примем 17 мсек. - * Значит на максимальный пакет потребуется 17*34 = 578 мсек. Примем 600 мсек. - * - если отрабатывать пакет целиком или хотя бы имеющимися в буфере UART кусками, то на 10 байт пинг-пакета требуется 27 мсек. - * То есть примерно по 2,7 мсек. на байт. Что близко к расчетным значениям. Примем 3 мсек. - * Значит на максимальный пакет потребуется 3*34 = 102 мсек. Примем 150 мсек. - * Опыт показал, что 150 мсек вполне хватает на большие пакеты + * По расчетам выходит: + * - получение и обработка посимвольно не должна длиться дольше 600 мсек. + * - получение и обработка пакетов целиком не должна длиться дольше 150 мсек. + * Мы будем обрабатывать пакетами. **/ -#define AC_PACKET_TIMEOUT 150 // 150 мсек - отработка буфера UART за раз, 600 мсек - отработка буфера UART по 1 байту за вызов loop +#define AC_PACKET_TIMEOUT 150 // типы пакетов -#define AC_PTYPE_PING 0x01 // ping-пакет, рассылается кондиционером каждые 3 сек.; модуль на него отвечает -#define AC_PTYPE_CMD 0x06 // команда сплиту; модуль отправляет такие команды, когда что-то хочет от сплита -#define AC_PTYPE_INFO 0x07 // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля -#define AC_PTYPE_INIT 0x09 // инициирующий пакет; присылается сплитом, если кнопка HEALTH на пульте нажимается 8 раз; как там и что работает - не разбирался. -#define AC_PTYPE_UNKN 0x0b // какой-то странный пакет, отправляемый пультом при инициации и иногда при включении питания... как работает и зачем нужен - не разбирался, сплит на него вроде бы не реагирует +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_types +#define AC_PTYPE_PING 0x01 // ping-пакет +#define AC_PTYPE_CMD 0x06 // команда сплиту +#define AC_PTYPE_INFO 0x07 // информационный пакет +#define AC_PTYPE_INIT 0x09 // инициирующий пакет +#define AC_PTYPE_UNKN 0x0b // какой-то странный пакет // типы команд -#define AC_CMD_STATUS_BIG 0x21 // большой пакет статуса кондиционера -#define AC_CMD_STATUS_SMALL 0x11 // маленький пакет статуса кондиционера -#define AC_CMD_STATUS_PERIODIC 0x2C // иногда встречается, сплит её рассылает по своему разумению; (вроде бы может быть и другой код! надо больше данных) +// смотреть тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_type_cmd #define AC_CMD_SET_PARAMS 0x01 // команда установки параметров кондиционера +#define AC_CMD_STATUS_SMALL 0x11 // маленький пакет статуса кондиционера +#define AC_CMD_STATUS_BIG 0x21 // большой пакет статуса кондиционера +// TODO: Нужно посмотреть, где используется AC_CMD_STATUS_PERIODIC, и изменить логику. +// на сегодня уже известно, что периодически рассылаются команды в диапазоне 0x20..0x2F +#define AC_CMD_STATUS_PERIODIC 0x2C // иногда встречается // значения байтов в пакетах #define AC_PACKET_START_BYTE 0xBB // Стартовый байт любого пакета 0xBB, других не встречал #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; + 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]; }; +// структура пекета struct packet_t { uint32_t msec; // значение millis в момент определения корректности пакета packet_header_t * header; @@ -161,288 +165,190 @@ 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 byte_1C = 0x1C; + uint8_t byte_27 = 0x27; + uint8_t zero1 = 0; + uint8_t zero2 = 0; + uint8_t zero3 = 0; + uint8_t zero4 = 0; + uint8_t zero5 = 0; + uint8_t zero6 = 0; }; // тело большого информационного пакета +// 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 - // БАЙТ2 - uint8_t reserv20 :5; // не расшифрован, всегда 0xC0 11000000 - bool is_invertor :1; // флаг инвертора - uint8_t reserv21 :2; // для RoyalClima18HNI: всегда 0xE0 11100000 - // Brokly: для Energolux Bern: 0xE0; иногда, с равными промежутками во времени проскакивает )xE4 - // предполагаю, что это байт конфига кондиционера (5 бит - инвертер), (2 бит - периодический мпульсный сигнал,период пимерно 500 сек) - // БАЙТ 3 - bool power:1; - bool sleep:1; - bool louver_V:1; - uint8_t louver_H:2; // у шторок лево-право, почему то два бита - uint8_t mode:3; // enum { AC_BIG_MODE_AUTO = 0, - // AC_BIG_MODE_COOL = 1, - // AC_BIG_MODE_DRY = 2, - // AC_BIG_MODE_HEAT = 4, - // AC_BIG_MODE_FAN = 6} - // - // - // Встречались такие значения: - // 0x04 100 - сплит выключен, до этого работал (статус держится 1 час после выкл.) - // 0x05 101 - режим AUTO - // 0x24 100100 - режим OFF - // 0x25 100101 - режим COOL - // 0x39 111001 - ?? - // 0x45 1000101 - режим DRY - // 0x85 10000101 - режим HEAT - // 0xC4 11000100 - режим OFF, выключен давно, зима - // 0xC5 11000101 - режим FAN - // Brokly: - // Встречались такие значения : - // 0x00 00000000 - OFF - // 0x01 00000001 - AUTO // режим авто, нет отдельного бита - // 0x41 1000001 - DRY - // 0x21 100001 - COOL - // 0x81 10000001 - HEAT - // 0x85 10000101 - HEAT+шторки верх-низ - // 0x99 10011001 - HEAT+шторки влево вправо - // 0xC1 11000001 - FAN // 7 и 6 бит связаны - // 0x80 10000000 - продувка после переключения из HEAT в OFF - // 0xC5 11000101 - FAN+шторки верх-низ - // 0xDD 11011101 - FAN+шторки лево-право/верх-низ - // 0xD9 11011001 - FAN+шторки лево-право - // 0xD8 11011000 - из FAN+шторки лево-право в OFF - // 0x39 111001 - COOL+шторки лево-право - // Очевидно битовые, но связные, поля, предположительные зависимости - // ВНИМАНИЕ : режимы номинальны, например в режиме АВТО нагрев или охлаждение не отображаются - // 7+6+5 4+3 2 1 0 - // MODE Louv_L Louv_H SLEEP ON/OFF - // - // ФУНКЦМЯ CLEEN, HEALTH, ANTIFUNGUS на данный байт не влияют - // - // #define AC_BIG_MASK_MODE b11100000 - // enum { AC_BIG_MODE_DRY = 0x40, - // AC_BIG_MODE_COOL = 0x20, - // AC_BIG_MODE_HEAT = 0x80, - // AC_BIG_MODE_FAN = 0xC0} - // #define AC_BIG_MASK_MODE b00011100 - // enum { AC_BIG_LOUVERS_H = 0x04, - // AC_BIG_LOUVERS_L = 0x18, - // AC_BIG_LOUVERS_BOTH = 0x1C} - // #define AC_BIG_MASK_POWER b00000001 - // #define AC_BIG_MASK_SLEEP b00000010 - // #define AC_BIG_MASK_COOL b00100000 - // - // БАЙТ 4 - uint8_t reserv40:4; // 8 - bool needDefrost:1; // 5 бит начало разморозки(накопление тепла) - bool defrostMode:1; // 6 бит режим разморозки внешнего блока (прогрев испарителя) - bool reserv41:1; // для RoyalClima18HNI: режим разморозки внешнего блока - 0x20, в других случаях 0x00 - bool cleen:1; // 8 бит CLEAN - - // Для кондея старт-стоп - // x xx - // C5 11000101 - // C4 11000100 - // 85 10000101 - // 84 10000100 - // 3D 00111101 - // 3C 00111100 - // 25 00100101 - // 24 00100100 - // 5 00000101 - // 4 00000100 - - - // БАЙТ5 - uint8_t realFanSpeed:3; // Brokly: Energolux Bern - подтверждаю, ВАЖНО !!!! Это реальная скорость фена - uint8_t reserv30:5; // та которая в данный момент. Например может быть установлен нагрев со скоростью - // вентилятора HI, но кондей еще не произвел достаточно тепла, и крутит на LOW, - // Тут будет отображаться LOW - // fanSpeed: OFF=0x00, MUTE=0x01, LOW=0x02, MID=0x04, HIGH=0x06, TURBO=0x07 - // в дежурных пакетах тут похоже что-то другое - // БАЙТ6 + // байт 0 тела (байт 8 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b08 + uint8_t byte_01 = 0x01; + + // байт 1 тела (байт 9 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b09 + uint8_t cmd_answer; + + // байт 2 тела (байт 10 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b10 + uint8_t reserv20 :2; + bool is_invertor_periodic :1; // флаг периодического пакета инверторного кондиционера + uint8_t reserv23 :2; + bool is_invertor :1; // флаг инвертора + uint8_t reserv26 :2; + + // байт 3 тела (байт 11 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b11 + 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, + // 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 тела (байт 12 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b12 + uint8_t reserv40 :4; + bool needDefrost :1; + bool defrostMode :1; + bool reserv46 :1; + bool clean :1; + // Для кондея старт-стоп + // x xx + // C5 1100 0101 + // C4 1100 0100 + // 85 1000 0101 + // 84 1000 0100 + // 3D 0011 1101 + // 3C 0011 1100 + // 25 0010 0101 + // 24 0010 0100 + // 5 0000 0101 + // 4 0000 0100 + + // байт 5 тела (байт 13 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b13 + uint8_t realFanSpeed:3; // реальная (не заданная) скорость вентилятора + uint8_t reserv53:5; + // в дежурных пакетах тут похоже что-то другое + + // байт 6 тела (байт 14 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b14 + 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 fanPWM:7; // ШИМ вентилятора - // В ВЫКЛЮЧЕНОМ СОСТОНИИ занчение 7 8 9 10 равны !!!!!! - // А значит это термодатчики внутри внутреннего блока !!!!!! + // байт 7 тела (байт 15 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b15 + uint8_t ambient_temperature_int; - // БАЙТ8 - uint8_t zero3; // Brokly: полностью повторяет значение 9 байта + // байт 8 тела (байт 16 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b16 + uint8_t zero3; // не расшифрован, у кого-то всегда 0x00, у кого-то повторяет значение байта 17 пакета. Непонятно. - // БАЙТ9 - uint8_t in_temperature_int; // Brokly: скорее всего это действительно какая то температура или дельта температур - // СКОРЕЕ ВСЕГО ЭТО ТЕМПЕРАТУРА ПОДАЧИ !!!!!! При охлаждении - холодная, при нагреве теплая - - // холоднее или горячее температуры в команте. В выключеном состоянии стремится к комнатной темп. - // у меня на трех инверторных кондиционерах Energolux серии Bern - // в выключеном состоянии значение этого байта находится на уровне 57-68 (мощность 0%) - // зависит от мощности работы компрессора (измерения при 12гр на улице) - // в режиме охлаждения уменьшается и при мощности 47% = 40 / 73% = 38 - // в режиме нагрева увеличивается и при мощности 47% = 70 / 73% = 75 / 84% = 84 - // изменение этого значения более вялое, с западыванием относительно изменения мощности - // видимо является реакцией (следствием работы) на изменение мощности инвертора - // учитывая стиль записи температур имеет смысл рассматривать это значение как увеличенное на 0x20 - - - - // этот байт как-то связан с температурой во внешнем блоке. Требуются дополнительные исследования. - // При выключенном сплите характер изменения значения примерно соответствует изменению температуры на улице. - // При включенном сплите значение может очень сильно скакать. - // По схеме wiring diagram сплит-системы, во внешнем блоке есть термодатчик, отслеживающий температуру испарителя. - // Возможно, этот байт как раз и отражает изменение температуры на испарителе. - // Но я не смог разобраться, как именно перевести эти значения в градусы. - // Кроме того, зимой даже в минусовую температуру этот байт не уходит ниже 0x33 по крайней мере - // для температур в диапазоне -5..-10 градусов Цельсия. - // БАЙТ10 - uint8_t zero4; // Brokly: полностью повторяет значение 9 байта + // байт 9 тела (байт 17 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b17 + uint8_t in_temperature_int; // какая-то температура, детали см. в описании на гитхабе - // БАЙТ11 - uint8_t zero5; // всегда = 100 (0x64) + // байт 10 тела (байт 18 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b18 + uint8_t zero4; // не расшифрован - // БАЙТ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 - // показания РАСТЕТ ПРИ ВКЛЮЧЕНИИ ИНВЕРТОРА, при выключении падают до комнатной - // от режима охлаждения или нагрева не зависит !!! + // байт 11 тела (байт 19 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b19 + uint8_t zero5; // всегда 0x00 или 0x64 + // байт 12 тела (байт 20 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b20 + uint8_t outdoor_temperature; // Внешняя температура; формула T - 0x20 - // БАЙТ15 - uint8_t zero9; // всегда 0x00, Brokly: Energolux Bern, всегда 0x39 111001 - // БАЙТ16 - uint8_t invertor_power; // МОщность инвертера (Brokly: подтверждаю) - // для RoyalClima18HNI: мощность инвертора (от 0 до 100) в % - // например, разморозка внешнего блока происходит при 80% - // БАЙТ17 - uint8_t zero11; // всегда 0x00 - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 22, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика достаточно плавна + // байт 13 тела (байт 21 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b21 + uint8_t out_temperature_int; // похоже на температуру обратки, T - 0x20 - // БАЙТ18 - uint8_t zero12; // - // Brokly: Energolux Bern : наложение на показания инвертора (от 144 до 174, когда инвертор отключен - // показания немного скачут в районе 149...154, при включении инвертора быстро растет, при выключении - // моментально падает до 149...154, бывают опускания ниже этих значений до 144, чаще в момент первоначального - // включения инвертора, а потом вверх, не всегда. При включении уходит в 0 на одну посылку + // байт 14 тела (байт 22 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b22 + uint8_t strange_temperature_int; // от режима не зависит, растет при включении инвертора; температура двигателя? - // БАЙТ19 - uint8_t zero13; // Brokly: Energolux Bern : включение 144 -> 124 -> 110 далее все время держим 110 + // байт 15 тела (байт 23 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b23 + uint8_t zero9; // не расшифрован, подробнее в описании - // БАЙТ20 - uint8_t zero14; // - // Brokly: Energolux Bern : полное наложение на показания инвертора (от 0 до 45, когда инвертор отключен и тут 0 - // при включении инвертора плавно растет, при выключении резко падает в 0, форма графика дрожащая нестабильная - // колебания в районе +-2...4 единицы - // БАЙТ21 - uint8_t zero15; // всегда 0x00 Brokly: подтверждаю + // байт 16 тела (байт 24 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b24 + uint8_t invertor_power; // мощность инвертора (от 0 до 100) в % - // БАЙТ22 - uint8_t zero16; // всегда 0x00 Brokly: подтверждаю + // байт 17 тела (байт 25 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b25 + //TODO: в описание протокола требуется пояснение от Brokly + uint8_t zero11; // не расшифрован, подробнее в описании. - // БАЙТ23 - uint8_t ambient_temperature_frac:4; // младшие 4 бита - дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы - // подробнее смотреть ambient_temperature_int - // для RoyalClima18HNI: старшие 4 бита - 0x2 - uint8_t reserv023:4; + // байт 18 тела (байт 26 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b26 + uint8_t zero12; // не расшифрован, подробнее в описании. + + // байт 19 тела (байт 27 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b27 + //TODO: в описание протокола требуется пояснение от Brokly + uint8_t zero13; // не расшифрован, подробнее в описании. + + // байт 20 тела (байт 28 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b28 + uint8_t zero14; // не расшифрован, подробнее в описании. + + // байт 21 тела (байт 29 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b29 + uint8_t zero15; // всегда 0x00 + + // байт 22 тела (байт 30 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b30 + uint8_t zero16; // всегда 0x00 + + // байт 23 тела (байт 31 пакета) + // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b31 + uint8_t ambient_temperature_frac:4; // дробная часть комнатной температуры воздуха с датчика на внутреннем блоке сплит-системы + uint8_t reserv234:1; + bool unknown:1; // для `Royal Clima 18HNI` в этом бите `1`. Не понятно, что это значит. У других сплитов такое не встречалось. + uint8_t reserv236:2; }; // тело малого информационного пакета +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11 struct packet_small_info_body_t { - uint8_t byte_01; // не расшифрован, всегда 0x01 - uint8_t cmd_answer; // код команды, ответом на которую пришел данный пакет (0x11); - // в пакетах сплита другие варианты не встречаются - // в отправляемых wifi-модулем пакетах тут может быть 0x01, если требуется установить режим работы - uint8_t target_temp_int_and_v_louver; // целая часть целевой температуры и положение вертикальных жалюзи - // три младших бита - положение вертикальных жалюзи - // если они все = 0, то вертикальный SWING включен - // если они все = 1, то выключен вертикальный SWING - // протокол универсильный, другие комбинации битов могут задавать какие-то положения - // вертикальных жалюзи, но у меня на пульте таких возможностей нет, надо экспериментировать. - // пять старших бит - целая часть целевой температуры - // температура определяется по формуле: - // 8 + (target_temp_int_and_v_louver >> 3) + (0.5 * (target_temp_frac >> 7)) - uint8_t h_louver; // старшие 3 бита - положение горизонтальных жалюзи, остальное не изучено и всегда было 0 - // если все 3 бита = 0, то горизонтальный SWING включен - // если все 3 бита = 1, то горизонтальный SWING отключен - // надо изучить другие комбинации - uint8_t target_temp_frac; // старший бит - дробная часть целевой температуры - // остальные биты до конца не изучены: - // бит 6 был всегда 0 - // биты 0..5 растут на 1 каждую минуту, возможно внутренний таймер для включения/выключения по времени - uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны - // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 - uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны - // БАЙТ 7 - uint8_t mode; // режим работы сплита: - // AUTO : bits[7, 6, 5] = [0, 0, 0] - // COOL : bits[7, 6, 5] = [0, 0, 1] - // DRY : bits[7, 6, 5] = [0, 1, 0] - // HEAT : bits[7, 6, 5] = [1, 0, 1] - // FAN : bits[7, 6, 5] = [1, 1, 1] - // Sleep function : bit 2 = 1 - // iFeel function : bit 3 = 1 + uint8_t byte_01; + uint8_t cmd_answer; + uint8_t target_temp_int_and_v_louver; + uint8_t h_louver; + uint8_t target_temp_frac; + uint8_t fan_speed; + uint8_t fan_turbo_and_mute; + uint8_t mode; uint8_t zero1; // всегда 0x00 uint8_t zero2; // всегда 0x00 - uint8_t status; // бит 5 = 1: включен, обычный режим работы (когда можно включить нагрев, охлаждение и т.п.) - // бит 2 = 1: режим самоочистки, должен запускаться только при бит 5 = 0 - // бит 0 и бит 1: активация режима ионизатора воздуха (не проверен, у меня его нет) + uint8_t status; uint8_t zero3; // всегда 0x00 - uint8_t display_and_mildew; // бит4 = 1, чтобы погасить дисплей на внутреннем блоке сплита - // бит3 = 1, чтобы включить функцию "антиплесень" (после отключения как-то прогревает или просушивает теплообменник, чтобы на нем не росла плесень) + uint8_t display_and_mildew; uint8_t zero4; // всегда 0x00 - uint8_t target_temp_frac2; // дробная часть целевой температуры, может быть только 0x00 и 0x05 - // при установке температуры тут 0x00, а заданная температура передается в target_temp_int_and_v_louver и target_temp_frac - // после установки сплит в информационных пакетах тут начинает показывать дробную часть - // не очень понятно, зачем так сделано + uint8_t target_temp_frac2; }; + + //**************************************************************************************************************************************************** //*************************************************** ПАРАМЕТРЫ РАБОТЫ КОНДИЦИОНЕРА ****************************************************************** //**************************************************************************************************************************************************** - -// для показаний о реальной скорости фена из большого пакета -enum ac_realFan : uint8_t { AC_REAL_FAN_OFF = 0x00, AC_REAL_FAN_MUTE = 0x01, AC_REAL_FAN_LOW = 0x02, AC_REAL_FAN_MID = 0x04, - AC_REAL_FAN_HIGH = 0x06, AC_REAL_FAN_TURBO = 0x07, AC_REAL_FAN_UNTOUCHED = 0xFF }; - // для всех параметров ниже вариант X_UNTOUCHED = 0xFF означает, что этот параметр команды должен остаться тот, который уже установлен + // питание кондиционера #define AC_POWER_MASK 0b00100000 enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTOUCHED = 0xFF }; @@ -452,7 +358,7 @@ enum ac_power : uint8_t { AC_POWER_OFF = 0x00, AC_POWER_ON = 0x20, AC_POWER_UNTO 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 }; @@ -466,7 +372,7 @@ enum ac_health_status : uint8_t { AC_HEALTH_STATUS_OFF = 0x00, AC_HEALTH_STATUS_ // задержка отключения кондиционера #define AC_TIMER_MINUTES_MASK 0b00111111 -#define AC_TIMER_HOURS_MASK 0b00011111 +#define AC_TIMER_HOURS_MASK 0b00011111 // включение таймера сна #define AC_TIMER_MASK 0b01000000 @@ -487,13 +393,13 @@ enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTO #define AC_IFEEL_MASK 0b00001000 enum ac_ifeel : uint8_t { AC_IFEEL_OFF = 0x00, AC_IFEEL_ON = 0x08, AC_IFEEL_UNTOUCHED = 0xFF }; -// Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V +// Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но должна быть такая возможность на уровне железа. +// TODO: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V #define AC_LOUVERV_MASK 0b00000111 enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_UPDOWN = 0x00, AC_LOUVERV_OFF = 0x07, AC_LOUVERV_UNTOUCHED = 0xFF }; -// Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но додлжна быть такая возможность на уровне железа. -// ToDo: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H +// Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но должна быть такая возможность на уровне железа. +// TODO: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H #define AC_LOUVERH_MASK 0b11100000 enum ac_louver_H : uint8_t { AC_LOUVERH_SWING_LEFTRIGHT = 0x00, AC_LOUVERH_OFF = 0xE0, AC_LOUVERH_UNTOUCHED = 0xFF }; @@ -514,6 +420,9 @@ enum ac_fanturbo : uint8_t { AC_FANTURBO_OFF = 0x00, AC_FANTURBO_ON = 0x40, AC_F #define AC_FANMUTE_MASK 0b10000000 enum ac_fanmute : uint8_t { AC_FANMUTE_OFF = 0x00, AC_FANMUTE_ON = 0x80, AC_FANMUTE_UNTOUCHED = 0xFF }; +// реальная скорость вентилятора +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 }; + // включение-выключение дисплея на корпусе внутреннего блока #define AC_DISPLAY_MASK 0b00010000 enum ac_display : uint8_t { AC_DISPLAY_OFF = 0x00, AC_DISPLAY_ON = 0x10, AC_DISPLAY_UNTOUCHED = 0xFF }; @@ -525,11 +434,9 @@ enum ac_display : uint8_t { AC_DISPLAY_OFF = 0x00, AC_DISPLAY_ON = 0x10, AC_DISP enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_UNTOUCHED = 0xFF }; // маска счетчика минут прошедших с последней команды -#define AC_MIN_COUTER 0b00111111 // - -// настройка усреднения фильтра температуры. Это значение - взнос нового измерения -// в усредненные показания в процентах -#define OUTDOOR_FILTER_PESCENT 0.5 +// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b12 +// GK: define убрал, т.к. считаю, что сбрасывать счетчик не надо. +// #define AC_MIN_COUNTER_MASK 0b00111111 /** команда для кондиционера * @@ -537,6 +444,8 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ * Если в структуру будут введены указатели, то копирование надо будет изменить! */ +//***************************************************************************** +// TODO: presets блок кода под сохранение пресетов. После решения - убрать // данные структур содержат настройку, специально вынес в макрос #define AC_COMMAND_BASE float temp_target;\ ac_power power;\ @@ -544,6 +453,7 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_health health;\ ac_mode mode;\ ac_sleep sleep;\ + ac_ifeel iFeel;\ ac_louver louver;\ ac_fanspeed fanSpeed;\ ac_fanturbo fanTurbo;\ @@ -554,30 +464,30 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ uint8_t timer_hours;\ uint8_t timer_minutes;\ bool temp_target_matter - + // чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше // из-за такого приема нужно контролировать размер копируемых данных руками -#define AC_COMMAND_BASE_SIZE 20 - +#define AC_COMMAND_BASE_SIZE 21 + +#if defined(PRESETS_SAVING) + // структура для сохранения данных + 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 + }; +#endif +//***************************************************************************** + struct ac_command_t { -/* - ac_power power; - ac_clean clean; - ac_health health; // включение ионизатора - ac_mode mode; - ac_sleep sleep; - ac_louver louver; - ac_fanspeed fanSpeed; - ac_fanturbo fanTurbo; - ac_fanmute fanMute; - ac_display display; - ac_mildew mildew; - ac_timer timer; - uint8_t timer_hours; - uint8_t timer_minutes; - float temp_target; - bool temp_target_matter; // показывает, задана ли температура. Если false, то оставляем уже установленную -*/ AC_COMMAND_BASE; ac_health_status health_status; float temp_ambient; // внутренняя температура @@ -589,23 +499,8 @@ struct ac_command_t { uint8_t invertor_power; // мощность инвертора uint8_t pressure; // предположительно давление bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) - ac_ifeel iFeel; }; -// структура для сохранения данных -struct ac_save_command_t { - AC_COMMAND_BASE; -}; - -// номера сохранений пресетов -enum store_pos : uint8_t { - POS_MODE_AUTO = 0, - POS_MODE_COOL, - POS_MODE_DRY, - POS_MODE_HEAT, - POS_MODE_FAN, - POS_MODE_OFF}; - typedef ac_command_t ac_state_t; // текущее состояние параметров кондея можно хранить в таком же формате, как и комманды //**************************************************************************************************************************************************** @@ -613,6 +508,8 @@ typedef ac_command_t ac_state_t; // текущее состояние пара //**************************************************************************************************************************************************** + + /***************************************************************************************************************************************************** * структуры и типы для последовательности команд ***************************************************************************************************************************************************** @@ -636,16 +533,12 @@ typedef ac_command_t ac_state_t; // текущее состояние пара // максимальная длина последовательности; больше вроде бы не требовалось #define AC_SEQUENCE_MAX_LEN 0x0F -// в пакетах никогда не встречалось значение 0xFF (только в CRC), поэтому решено его использовать как признак не важного значение байта -//#define AC_SEQUENCE_ANY_BYTE 0xFF - // дефолтный таймаут входящего пакета в миллисекундах // если для входящего пакета в последовательности указан таймаут 0, то используется значение по-умолчанию // если нужный пакет не поступил в течение указанного времени, то последовательность прерывается с ошибкой -// Brokly: пришлось увеличить -#define AC_SEQUENCE_DEFAULT_TIMEOUT 580 - -enum sequence_item_type_t : uint8_t { +#define AC_SEQUENCE_DEFAULT_TIMEOUT 580 // Brokly: пришлось увеличить с 500 до 580 + + enum sequence_item_type_t : uint8_t { AC_SIT_NONE = 0x00, // пустой элемент последовательности AC_SIT_DELAY = 0x01, // пауза в последовательности на нужное количество миллисекунд AC_SIT_FUNC = 0x02 // рабочий элемент последовательности @@ -678,23 +571,25 @@ 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) + + #if defined(PRESETS_SAVING) + // массив для сохранения данных глобальных персетов + ac_save_command_t global_presets[POS_MODE_OFF+1]; + // тут будем хранить данные глобальных пресетов во флеше // ВНИМАНИЕ на данный момент 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); + + // настройка-ключ, для включения сохранения - восстановления настроек каждого + // режима работы в отдельности, то есть каждый режим работы имеет свои настройки + // температуры, шторок, скорости вентилятора, пресетов + bool _store_settings = false; + // флаги для сохранения пресетов + bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено #endif - // настройка-ключ, для включения сохранения - восстановления настроек каждого - // режима работы в отдельности, то есть каждый режим работы имеет свои настройки - // температуры, шторок, скорости вентилятора, пресетов - bool _store_settings = false; - // флаги для сохранения пресетов - bool _new_command_set = false; // флаг отправки новой команды, необходимо сохранить данные пресета, если разрешено - + // время последнего запроса статуса у кондея uint32_t _dataMillis; // периодичность обновления статуса кондея, по дефолту AC_STATES_REQUEST_INTERVAL @@ -709,8 +604,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // если тут false, то 1 в соответствующем бите включает дисплей, а 0 выключает. // если тут true, то 1 потушит дисплей, а 0 включит. bool _display_inverted = false; - - // флаг типа кондиционера инвертор - true, ON/OFF - false, начальная установка false + + // флаг типа кондиционера. инвертор - true, ON/OFF - false, начальная установка false // в таком режиме точность и скорость определения реального состояния системы для инвертора, // будет работать, но будет ниже, переменная устанавливается при первом получении большого пакета; // если эта переменная установлена, то режим работы не инверторного кондиционера будет распознаваться @@ -1047,18 +942,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _clearInPacket(); _inPacket.msec = millis(); _setStateMachineState(ACSM_RECEIVING_PACKET); - //******************************************** экспериментальная секция ************************************************************* - // пробуем сократить время ответа с помощью прямых вызовов обработчиков, а не через состояние IDLE - //_doReceivingPacketState(); - // получилось всё те же 123 мсек. Только изредка падает до 109 мсек. Странно. - // логический анализатор показал примерно то же время от начала запроса до окончания ответа. - // запрос имеет длительность 18 мсек (лог.анализатор говорит 22,5 мсек). - // ответ имеет длительность 41 мсек по лог.анализатору. - // длительность паузы между запросом и ответом порядка 60 мсек. - // Скорее всего за один вызов _doReceivingPacketState не удается загрузить весь пакет (на момент вызова не все байы поступили в буфер UART) - // и поэтому программа отдает управление ESPHome для выполнения своих задач - // Стоит ли переделать код наоборот для непрерывного выполнения всё время, пока ожидается посылка - не знаю. Может быть такой риалтайм и не нужен. - //*********************************************************************************************************************************** } else { while (_ac_serial->available() > 0) @@ -1168,8 +1051,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _clearOutPacket(); _outPacket.msec = millis(); _outPacket.header->packet_type = AC_PTYPE_PING; - _outPacket.header->ping_answer_01 = 0x01; // только в ответе на пинг этот байт равен 0x01; что означает не ясно - _outPacket.header->body_length = 8; // в ответе на пинг у нас тело 8 байт + _outPacket.header->ping_answer_01 = 0x01; // магия, детали тут: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_type_ping + _outPacket.header->body_length = 8; _outPacket.body = &(_outPacket.data[AC_HEADER_SIZE]); // заполняем тело пакета @@ -1192,15 +1075,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _startupSequenceComlete = startupSequence(); } - // изначально предполагал, что передачу пакета на отправку выполнит обработчик IDLE, но показалось, что слишком долго - // логика отправки через IDLE в том, что получение запросов может быть важнее отправки ответов и IDLE позволяет реализовать такой приоритет - // но потом решил всё же напрямую отправлять в отправку - // в этом случае пинг-ответ заканчивает отправку спустя 144 мсек после стартового байта пинг-запроса - //_setStateMachineState(ACSM_IDLE); _setStateMachineState(ACSM_SENDING_PACKET); - // решил провести эксперимент - // получилось от начала запроса до отправки ответа порядка 165 мсек., если отправка идет не сразу, а через состояние IDLE - // Если сразу отсюда отправляться в обработчик отправки, то время сокращается до 131 мсек. Основные потери идут до входа в парсер пакетов + break; } @@ -1213,7 +1089,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; } - case AC_PTYPE_INFO: { // информационный пакет; бывает 3 видов; один из них рассылается кондиционером самостоятельно раз в 10 мин. и все 3 могут быть ответом на запросы модуля + case AC_PTYPE_INFO: { // информационный пакет // смотрим тип поступившего пакета по второму байту тела _debugMsg(F("Parser: status packet received"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); switch (_inPacket.body[1]) { @@ -1260,6 +1136,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _current_ac_state.sleep = (ac_sleep)stateByte; stateByte = small_info_body->mode & AC_IFEEL_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.iFeel != (ac_ifeel)stateByte); _current_ac_state.iFeel = (ac_ifeel)stateByte; stateByte = small_info_body->status & AC_POWER_MASK; @@ -1301,7 +1178,7 @@ 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; @@ -1311,16 +1188,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _current_ac_state.temp_ambient = stateFloat; // некая температура из наружного блока, скорее всего температура испарителя - // temp = big_info_body->outdoor_temperature - 0x20; - // фильтруем простейшим фильтром OUTDOOR_FILTER_PESCENT - взнос одного измерения в процентах - { - const float koef = ((float)OUTDOOR_FILTER_PESCENT)/100; - const float antkoef = 1.0 - koef; - static float temp = big_info_body->outdoor_temperature - 0x20; - temp = temp * antkoef + koef * (big_info_body->outdoor_temperature - 0x20); - stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != temp); - _current_ac_state.temp_outdoor = temp; - } + // GK: фильтрацию тут убрал. Лучше это делать в ESPHome. Для этого у сенсора есть возможности. А тут лучше иметь чистые значения для аналлиза. + stateFloat = big_info_body->outdoor_temperature - 0x20; + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_outdoor != stateFloat); + _current_ac_state.temp_outdoor = stateFloat; // температура входящей магистрали stateFloat = big_info_body->in_temperature_int - 0x20; @@ -1351,11 +1222,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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(); + break; } @@ -1365,7 +1235,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // но я решил этот момент тут не проверять и не контролировать. // корректную установку параметров можно определить, запросив статус кондиционера сразу после получения этой команды кондея // в настоящий момент проверка сделана в механизме sequences - // TODO: если доводить до идеала, то проверку байтов 2 и 3 можно сделать и тут break; } @@ -1422,12 +1291,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { void _debugMsg(const String &msg, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = 0, ... ){ if (dbgLevel < ESPHOME_LOG_LEVEL_NONE) dbgLevel = ESPHOME_LOG_LEVEL_NONE; if (dbgLevel > ESPHOME_LOG_LEVEL_VERY_VERBOSE) dbgLevel = ESPHOME_LOG_LEVEL_VERY_VERBOSE; - + if (line == 0) line = __LINE__; // если строка не передана, берем текущую строку va_list vl; va_start(vl, line); - esp_log_vprintf_(dbgLevel, Constants::TAG, line, msg.c_str(), vl); // так тоже вроде через Ж*, только ее не видно :) + esp_log_vprintf_(dbgLevel, Constants::TAG, line, msg.c_str(), vl); va_end(vl); } @@ -1466,23 +1335,28 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // формируем данные #ifdef HOLMS + // если этот дефайн объявлен, то в лог попадут только пакеты больше указанного в дефайне размера + // при этом весь вывод будет в десятичном виде, данные будут разделены ";" + // и не будет выделения заголовков и CRC квадратными скобками 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 + // если дефайна HOLMS нет, то выводим пакеты в HEX и все подряд 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); + + memset(textBuf, 0, 11); sprintf(textBuf, "%02X", packet->data[i]); st += textBuf; @@ -1490,8 +1364,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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 @@ -1619,15 +1495,12 @@ 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; // команда указана, дополнительно внесем в пакет те параметры, которые установлены в команде - // присваиваем параметры пакета pack->msec = millis(); pack->header->start_byte = AC_PACKET_START_BYTE; @@ -1657,7 +1530,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } // обнулить счетчик минут с последней команды - pack->body[4] &= ~ AC_MIN_COUTER ; + // GK: считаю, что так делать не надо. Штатный wifi-модуль не сбрасывает счетчик минут. + // pack->body[4] &= ~ AC_MIN_COUNTER_MASK ; // вертикальные жалюзи if (cmd->louver.louver_v != AC_LOUVERV_UNTOUCHED){ @@ -1702,7 +1576,8 @@ 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; } @@ -1721,7 +1596,7 @@ 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 @@ -1814,7 +1689,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // проверяем ответ bool relevant = true; relevant = (relevant && (_inPacket.header->packet_type == AC_PTYPE_INFO)); - relevant = (relevant && (_inPacket.header->body_length == 0x18 || _inPacket.header->body_length == 0x19)); // канальник Royal Clima отвечает пакетом длиной 0x19 + relevant = (relevant && (_inPacket.header->body_length == 0x18 || _inPacket.header->body_length == 0x19)); // канальник Royal Clima отвечает пакетом длиной 0x19 relevant = (relevant && (_inPacket.body[0] == 0x01)); relevant = (relevant && (_inPacket.body[1] == AC_CMD_STATUS_BIG)); @@ -1892,7 +1767,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; @@ -1919,6 +1794,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // бинарный сенсор состония разморозки esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; + // загружает на выполнение последовательность команд на включение/выключение табло с температурой bool _displaySequence(ac_display dsp = AC_DISPLAY_ON){ // нет смысла в последовательности, если нет коннекта с кондиционером @@ -1938,67 +1814,66 @@ 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){ + + #if defined(PRESETS_SAVING) + // номер глобального пресета от режима работы + 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; - } else if(cmd->mode == AC_MODE_AUTO){ - return POS_MODE_AUTO; - } else if(cmd->mode == AC_MODE_COOL){ - return POS_MODE_COOL; - } else if(cmd->mode == AC_MODE_DRY){ - return POS_MODE_DRY; - } else if(cmd->mode == AC_MODE_FAN){ - return POS_MODE_FAN; - } else if(cmd->mode == AC_MODE_HEAT){ - return POS_MODE_HEAT; } - cmd->power = AC_POWER_OFF; - return POS_MODE_OFF; - } - - // восстановление данных из пресета - void load_preset(ac_command_t* cmd, uint8_t num_preset){ - if(num_preset < sizeof(global_presets)/sizeof(global_presets[0])){ // проверка выхода за пределы массива - if(cmd->power == global_presets[num_preset].power && cmd->mode == global_presets[num_preset].mode){ //контроль инициализации - memcpy(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE); // просто копируем из массива - _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - } else { - _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + + // восстановление данных из пресета + void load_preset(ac_command_t* cmd, uint8_t num_preset){ + if(num_preset < sizeof(global_presets)/sizeof(global_presets[0])){ // проверка выхода за пределы массива + if(cmd->power == global_presets[num_preset].power && cmd->mode == global_presets[num_preset].mode){ //контроль инициализации + memcpy(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE); // просто копируем из массива + _debugMsg(F("Preset %02d read from RAM massive."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } else { + _debugMsg(F("Preset %02d not initialized, use current settings."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } } } - } - // запись данных в массив персетов - void save_preset(ac_command_t* cmd){ - uint8_t num_preset = get_num_preset(cmd); - if(memcmp(cmd,&(global_presets[num_preset]), AC_COMMAND_BASE_SIZE) != 0){ // содержимое пресетов разное - memcpy(&(global_presets[num_preset]), cmd, AC_COMMAND_BASE_SIZE); // копируем пресет в массив - #if defined(ESP32) - _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); - if(storage.save(global_presets)){ - if(!global_preferences->sync()) // сохраняем все пресеты - _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } else { - _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); - } - #endif - } else { - _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + // запись данных в массив персетов + 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(PRESETS_SAVING) + _debugMsg(F("Save preset %02d to NVRAM."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + if(storage.save(global_presets)){ + if(!global_preferences->sync()) // сохраняем все пресеты + _debugMsg(F("Sync NVRAM error ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } else { + _debugMsg(F("Save presets to flash ERROR ! (load result: %02d)"), ESPHOME_LOG_LEVEL_ERROR, __LINE__, load_presets_result); + } + #endif + } else { + _debugMsg(F("Preset %02d has not been changed, Saving canceled."), ESPHOME_LOG_LEVEL_WARN, __LINE__, num_preset); + } } - } - + #endif + public: // инициализация объекта void initAC(esphome::uart::UARTComponent *parent = nullptr){ _dataMillis = millis(); _clearInPacket(); _clearOutPacket(); - _clearPacket(&_outTestPacket); - _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - _outTestPacket.header->wifi = AC_PACKET_ANSWER; _setStateMachineState(ACSM_IDLE); _ac_serial = parent; @@ -2022,9 +1897,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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_defrost_state(binary_sensor::BinarySensor *defrost_state_sensor) { sensor_defrost_ = defrost_state_sensor; } 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; }; @@ -2033,9 +1909,10 @@ 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__); + if(_is_invertor){ // анализ режима для инвертора, точнее потому что использует показания мощности инвертора static uint32_t timerInv = 0; if(_current_ac_state.invertor_power == 0){ // инвертор выключен @@ -2108,33 +1985,37 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } } + _debugMsg(F("Action mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->action); + + /*************************** POWER & MODE ***************************/ if (_current_ac_state.power == AC_POWER_ON){ switch (_current_ac_state.mode) { case AC_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_HEAT_COOL; // по факту режим, названный в AUX как AUTO, является режимом HEAT_COOL - break; + // по факту режим, названный в AUX как AUTO, является режимом HEAT_COOL + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; case AC_MODE_COOL: this->mode = climate::CLIMATE_MODE_COOL; - break; + break; case AC_MODE_DRY: this->mode = climate::CLIMATE_MODE_DRY; - break; + break; case AC_MODE_HEAT: this->mode = climate::CLIMATE_MODE_HEAT; - break; + break; case AC_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; - break; + break; default: _debugMsg(F("Warning: unknown air conditioner mode."), ESPHOME_LOG_LEVEL_WARN, __LINE__); - break; + break; } } else { this->mode = climate::CLIMATE_MODE_OFF; @@ -2143,94 +2024,139 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("Climate mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->mode); /*************************** FAN SPEED ***************************/ - if(_current_ac_state.power == AC_POWER_ON){ - switch (_current_ac_state.fanSpeed) { - case AC_FANSPEED_HIGH: - this->fan_mode = climate::CLIMATE_FAN_HIGH; - break; - - case AC_FANSPEED_MEDIUM: - this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + this->fan_mode = climate::CLIMATE_FAN_OFF; + switch (_current_ac_state.fanSpeed) { + case AC_FANSPEED_HIGH: + this->fan_mode = climate::CLIMATE_FAN_HIGH; break; - case AC_FANSPEED_LOW: - this->fan_mode = climate::CLIMATE_FAN_LOW; + case AC_FANSPEED_MEDIUM: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; break; - case AC_FANSPEED_AUTO: - this->fan_mode = climate::CLIMATE_FAN_AUTO; + case AC_FANSPEED_LOW: + this->fan_mode = climate::CLIMATE_FAN_LOW; break; - default: - this->custom_fan_mode = Constants::MUTE; + case AC_FANSPEED_AUTO: + this->fan_mode = climate::CLIMATE_FAN_AUTO; break; - } - /*************************** TURBO FAN MODE ***************************/ - switch (_current_ac_state.fanTurbo) { - case AC_FANTURBO_ON: - this->custom_fan_mode = Constants::TURBO; + + default: + _debugMsg(F("Warning: unknown fan speed."), ESPHOME_LOG_LEVEL_WARN, __LINE__); 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->custom_fan_mode = Constants::MUTE; } _debugMsg(F("Climate fan mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->fan_mode); + + /*************************** TURBO FAN MODE ***************************/ + // TURBO работает в режимах FAN, COOL, HEAT, HEAT_COOL + // в режиме DRY изменение скорости вентилятора никак не влияло на его скорость, может сплит просто не вышел еще на режим? Надо попробовать долгую работу в этом режиме. + 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. В режимах HEAT, COOL, HEAT_COOL не работает. DRY не проверял. + // TODO: проверку на это несовместимые режимы пока выпилили, т.к. нет уверенности, что это поведение одинаково для всех + 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); //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ - + /*************************** iFEEL CUSTOM PRESET ***************************/ + // режим поддержки температуры в районе пульта, работает только при включенном конедее + if( _current_ac_state.iFeel == AC_IFEEL_ON && + _current_ac_state.power == AC_POWER_ON ) { + + this->custom_preset = Constants::FEEL; + + } else if ( this->custom_preset == Constants::FEEL ) { + + // AC_IFEEL_OFF + // только в том случае, если до этого пресет был установлен + this->custom_preset = (std::string)""; + + } + + _debugMsg(F("Climate iFEEL preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.iFeel); + + /*************************** HEALTH CUSTOM PRESET ***************************/ + // режим работы ионизатора + if( _current_ac_state.health == AC_HEALTH_ON && + _current_ac_state.power == AC_POWER_ON ) { + + this->custom_preset = Constants::HEALTH; + + } else if ( this->custom_preset == Constants::HEALTH ) { + + // AC_HEALTH_OFF + // только в том случае, если до этого пресет был установлен + 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 часов при отключении режима - не понятно. - if(_current_ac_state.sleep == AC_SLEEP_ON && - _current_ac_state.power == AC_POWER_ON ) { - this->preset = climate::CLIMATE_PRESET_SLEEP; - } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { - this->preset = climate::CLIMATE_PRESET_NONE; - } + if( _current_ac_state.sleep == AC_SLEEP_ON && + _current_ac_state.power == AC_POWER_ON ) { + + this->preset = climate::CLIMATE_PRESET_SLEEP; + + } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { + + // AC_SLEEP_OFF + // только в том случае, если до этого пресет был установлен + this->preset = climate::CLIMATE_PRESET_NONE; - _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); - - /*************************** HEALTH CUSTOM PRESET ***************************/ - // режим работы ионизатора - if(_current_ac_state.health == AC_HEALTH_ON && - _current_ac_state.power == AC_POWER_ON ) { - this->custom_preset = Constants::HEALTH; - } else if (this->custom_preset == Constants::HEALTH) { - this->custom_preset = (std::string)""; } - _debugMsg(F("Climate HEALTH preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.health); + _debugMsg(F("Climate preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->preset); /*************************** CLEAN CUSTOM PRESET ***************************/ // режим очистки кондиционера, включается (или должен включаться) при AC_POWER_OFF - if(_current_ac_state.clean == AC_CLEAN_ON && - _current_ac_state.power == AC_POWER_OFF ) { + if( _current_ac_state.clean == AC_CLEAN_ON && + _current_ac_state.power == AC_POWER_OFF ) { + this->custom_preset = Constants::CLEAN; + } else if (this->custom_preset == Constants::CLEAN) { + + // AC_CLEAN_OFF + // только в том случае, если до этого пресет был установлен this->custom_preset = (std::string)""; + } - + _debugMsg(F("Climate CLEAN preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.clean); - + /*************************** ANTIFUNGUS CUSTOM PRESET ***************************/ // пресет просушки кондиционера после выключения // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. @@ -2243,45 +2169,53 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // при выключении кондея, я не наблюдаю. На пульте горит пиктограмма этого режима, но просушки // я не вижу. После выключения , с активированым режимом Anti-FUNGUS, кондей сразу закрывает хлебало // и затыкается. - if(_current_ac_state.mildew == AC_MILDEW_ON && - _current_ac_state.power == AC_POWER_OFF ) { - this->custom_preset = Constants::ANTIFUNGUS; - } else if (this->custom_preset == Constants::ANTIFUNGUS) { - this->custom_preset = (std::string)""; + // + // GK: оставил возможность включения функции в работающем состоянии, т.к. установка флага должна быть в работающем состоянии, + // а сама функция отработает при выключении сплита. + // У Brokly возможно какие-то особенности кондея. + 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 ***************************/ - - if( _current_ac_state.power == AC_POWER_OFF){ //ЕСЛИ КОНДЕЙ ВЫКЛЮЧЕН - this->swing_mode = climate::CLIMATE_SWING_OFF; - } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN && - _current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ - this->swing_mode = climate::CLIMATE_SWING_BOTH; - } else if (_current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT){ - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if (_current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN){ - this->swing_mode = climate::CLIMATE_SWING_VERTICAL; - } else { - this->swing_mode = climate::CLIMATE_SWING_OFF; - } + this->swing_mode = climate::CLIMATE_SWING_OFF; + if( _current_ac_state.power == AC_POWER_ON) { + if ( _current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT && _current_ac_state.louver.louver_v == AC_LOUVERV_OFF ){ + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } else if ( _current_ac_state.louver.louver_h == AC_LOUVERH_OFF && _current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN ){ + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else if ( _current_ac_state.louver.louver_h == AC_LOUVERH_SWING_LEFTRIGHT && _current_ac_state.louver.louver_v == AC_LOUVERV_SWING_UPDOWN ){ + this->swing_mode = climate::CLIMATE_SWING_BOTH; + } + } _debugMsg(F("Climate swing mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, this->swing_mode); /*************************** TEMPERATURE ***************************/ - if(_current_ac_state.mode == AC_MODE_FAN || _current_ac_state.power == AC_POWER_OFF){ + // в режиме вентилятора и в выключенном состоянии будем показывать текущую температуру this->target_temperature = _current_ac_state.temp_ambient; } else if (_current_ac_state.mode == AC_MODE_AUTO ){ - this->target_temperature = 25; + this->target_temperature = 25; // в AUTO зашита температура 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 ***************************/ /*********************************************************************/ @@ -2307,6 +2241,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // флаг режима разморозки if (sensor_defrost_ != nullptr) sensor_defrost_->publish_state(_current_ac_state.defrost); + // состояние дисплея if (sensor_display_ != nullptr) { switch (_current_ac_state.display) { @@ -2316,7 +2251,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()) { @@ -2324,11 +2259,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } else { sensor_display_->publish_state(false); } - break; + break; default: // могут быть и другие состояния, поэтому так - break; + break; } } } @@ -2338,9 +2273,13 @@ 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", 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, " [x] Show action: %s", TRUEFALSE(this->get_show_action())); + ESP_LOGCONFIG(Constants::TAG, " [x] Display inverted: %s", TRUEFALSE(this->get_display_inverted())); + + #if defined(PRESETS_SAVING) + ESP_LOGCONFIG(Constants::TAG, " [x] Save settings %s", TRUEFALSE(this->get_store_settings())); + #endif + ESP_LOGCONFIG(Constants::TAG, " [?] Is invertor %s", millis() > _update_period + 1000 ? YESNO(_is_invertor): "pending..."); if ((this->sensor_indoor_temperature_) != nullptr) { ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Indoor Temperature"), (this->sensor_indoor_temperature_)->get_name().c_str()); @@ -2360,7 +2299,7 @@ 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()) { @@ -2468,7 +2407,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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()) { @@ -2482,360 +2421,390 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } this->dump_traits_(Constants::TAG); + } // вызывается пользователем из интерфейса ESPHome или Home Assistant void control(const esphome::climate::ClimateCall &call) override { bool hasCommand = false; - static ac_command_t cmd; + ac_command_t cmd; _clearCommand(&cmd); // не забываем очищать, а то будет мусор // User requested mode change if (call.get_mode().has_value()) { ClimateMode mode = *call.get_mode(); - // Send mode to hardware + switch (mode) { case climate::CLIMATE_MODE_OFF: hasCommand = true; cmd.power = AC_POWER_OFF; - load_preset(&cmd, POS_MODE_OFF); - cmd.temp_target = _current_ac_state.temp_ambient; // просто от нехрен делать - //this->mode = mode; + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_OFF); + #endif + + this->mode = mode; break; case climate::CLIMATE_MODE_COOL: hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_COOL; - load_preset(&cmd, POS_MODE_COOL); - //this->mode = mode; + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_COOL); + #endif + + this->mode = mode; break; case climate::CLIMATE_MODE_HEAT: hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_HEAT; - load_preset(&cmd, POS_MODE_HEAT); - //this->mode = mode; + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_HEAT); + #endif + + this->mode = mode; break; case climate::CLIMATE_MODE_HEAT_COOL: hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_AUTO; - load_preset(&cmd, POS_MODE_AUTO); + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_AUTO); + #endif + cmd.temp_target = 25; // зависимость от режима HEAT_COOL cmd.temp_target_matter = true; cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL - //this->mode = mode; + this->mode = mode; break; case climate::CLIMATE_MODE_FAN_ONLY: hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_FAN; - load_preset(&cmd, POS_MODE_FAN); + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_FAN); + #endif + cmd.temp_target = _current_ac_state.temp_ambient; // зависимость от режима FAN cmd.temp_target_matter = true; - cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN + // GK: в режиме FAN работает TURBO, так что отключать не нужно! + //cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима FAN cmd.sleep = AC_SLEEP_OFF; - if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ + // GK: для меня AUTO = HIGH. Скорее всего сплит сам меняет скорость. Поэтому ниже закомментировал + /* if(cmd.fanSpeed == AC_FANSPEED_AUTO || _current_ac_state.fanSpeed == AC_FANSPEED_AUTO){ cmd.fanSpeed = AC_FANSPEED_LOW; // зависимость от режима FAN - } - //this->mode = mode; + } */ + this->mode = mode; break; case climate::CLIMATE_MODE_DRY: hasCommand = true; cmd.power = AC_POWER_ON; cmd.mode = AC_MODE_DRY; - load_preset(&cmd, POS_MODE_DRY); + + #if defined(PRESETS_SAVING) + load_preset(&cmd, POS_MODE_DRY); + #endif + cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима DRY cmd.sleep = AC_SLEEP_OFF; // зависимость от режима DRY - //this->mode = mode; + this->mode = mode; break; - case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) + // другие возможные значения (чтоб не забыть) + //case climate::CLIMATE_MODE_AUTO: // этот режим в будущем можно будет использовать для автоматического пресета (ПИД-регулятора, например) default: break; } - //this->mode = mode; + } // User requested fan_mode change - if(_current_ac_state.power == AC_POWER_ON || cmd.power == AC_POWER_ON) { // пресеты для включенного кондея - // User requested swing_mode change - if (call.get_swing_mode().has_value()) { - ClimateSwingMode swingmode = *call.get_swing_mode(); - // Send fan mode to hardware - switch (swingmode) { - // The protocol allows other combinations for SWING. - // For example "turn the louvers to the desired position or "spread to the sides" / "concentrate in the center". - // But the ROVEX IR-remote does not provide this features. Therefore this features haven't been tested. - // May be suitable for other models of AUX-based ACs. - case climate::CLIMATE_SWING_OFF: - cmd.louver.louver_h = AC_LOUVERH_OFF; - cmd.louver.louver_v = AC_LOUVERV_OFF; - hasCommand = true; - //this->swing_mode = swingmode; - break; + if (call.get_fan_mode().has_value()) { + ClimateFanMode fanmode = *call.get_fan_mode(); - 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; + switch (fanmode) { + case climate::CLIMATE_FAN_AUTO: + hasCommand = true; + cmd.fanSpeed = AC_FANSPEED_AUTO; + cmd.fanTurbo = AC_FANTURBO_OFF; + cmd.fanMute = AC_FANMUTE_OFF; + this->fan_mode = fanmode; 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; + + case climate::CLIMATE_FAN_LOW: + hasCommand = true; + cmd.fanSpeed = AC_FANSPEED_LOW; + cmd.fanTurbo = AC_FANTURBO_OFF; + cmd.fanMute = AC_FANMUTE_OFF; + this->fan_mode = fanmode; 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; + + case climate::CLIMATE_FAN_MEDIUM: + hasCommand = true; + cmd.fanSpeed = AC_FANSPEED_MEDIUM; + 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; + 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; - } - //this->swing_mode = swingmode; - } - - if (call.get_target_temperature().has_value()) { - if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_FAN) || - (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_FAN)){ // блокировка ввода температуры в режиме FAN - this->target_temperature = _current_ac_state.temp_ambient; - this->publish_state(); - } else if((cmd.mode !=AC_MODE_UNTOUCHED && cmd.mode == AC_MODE_AUTO) || - (cmd.mode ==AC_MODE_UNTOUCHED && _current_ac_state.mode == AC_MODE_AUTO)){ // блокировка ввода температуры в режиме АВТО - this->target_temperature = 25; - this->publish_state(); - } else { - // User requested target temperature change - float temp = *call.get_target_temperature(); - // Send target temp to climate - if (temp > Constants::AC_MAX_TEMPERATURE) temp = Constants::AC_MAX_TEMPERATURE; - if (temp < Constants::AC_MIN_TEMPERATURE) temp = Constants::AC_MIN_TEMPERATURE; - if(cmd.temp_target != temp){ - cmd.temp_target = temp; - hasCommand = true; - cmd.temp_target_matter = true; - } - } } - if (call.get_fan_mode().has_value()) { - ClimateFanMode fanmode = *call.get_fan_mode(); - // Send fan mode to hardware - switch (fanmode) { - case climate::CLIMATE_FAN_AUTO: - if(cmd.mode != AC_MODE_FAN){ // при вентиляции нет такого режима - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_AUTO; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - //this->fan_mode = fanmode; - } - break; - - case climate::CLIMATE_FAN_LOW: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_LOW; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - //this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_MEDIUM: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_MEDIUM; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - //this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_HIGH: - hasCommand = true; - cmd.fanSpeed = AC_FANSPEED_HIGH; - // changing fan speed cancels fan TURBO and MUTE modes for ROVEX air conditioners - cmd.fanTurbo = AC_FANTURBO_OFF; - cmd.fanMute = AC_FANMUTE_OFF; - //this->fan_mode = fanmode; - break; - - case climate::CLIMATE_FAN_ON: - case climate::CLIMATE_FAN_OFF: - case climate::CLIMATE_FAN_MIDDLE: - case climate::CLIMATE_FAN_FOCUS: - case climate::CLIMATE_FAN_DIFFUSE: - default: - break; - } - //this->fan_mode = fanmode; + } else if (call.get_custom_fan_mode().has_value()) { + std::string customfanmode = *call.get_custom_fan_mode(); - } 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. + if (customfanmode == Constants::TURBO) { + // TURBO fan mode is suitable in COOL and HEAT modes. // Other modes don't accept TURBO fan mode. - // May be other AUX-based air conditioners do the same. - if ( cmd.mode == AC_MODE_COOL || cmd.mode == AC_MODE_HEAT || - _current_ac_state.mode == AC_MODE_COOL || _current_ac_state.mode == AC_MODE_HEAT) { - + /* + 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.fanTurbo = AC_FANTURBO_ON; + cmd.fanTurbo = AC_FANTURBO_ON; cmd.fanMute = AC_FANMUTE_OFF; // зависимость от fanturbo - //this->custom_fan_mode = customfanmode; + this->custom_fan_mode = customfanmode; + /* } else { _debugMsg(F("TURBO fan mode is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } - } 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 || - // _current_ac_state.mode == AC_MODE_FAN) { + //if ( cmd.mode == AC_MODE_FAN + // or _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; + this->custom_fan_mode = customfanmode; //} else { // _debugMsg(F("MUTE fan mode is suitable in FAN mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); //} - } - //this->custom_fan_mode = customfanmode; } + } - // Пользователь выбрал пресет - 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){ + // Пользователь выбрал пресет + if (call.get_preset().has_value()) { + ClimatePreset preset = *call.get_preset(); - hasCommand = true; - cmd.sleep = AC_SLEEP_ON; + switch (preset) { + case climate::CLIMATE_PRESET_SLEEP: + // Ночной режим (SLEEP). + // По инструкциям комбинируется только с режимами COOL и HEAT. Автоматически выключается через 7 часов. + // Brokly: вроде как работает еще и с AUTO и DRY + // COOL: температура +1 градус через час, еще через час дополнительные +1 градус, дальше не меняется. + // HEAT: температура -2 градуса через час, еще через час дополнительные -2 градуса, дальше не меняется. + // Восстанавливается ли температура через 7 часов при отключении режима - не понятно. + if ( cmd.mode == AC_MODE_COOL + or cmd.mode == AC_MODE_HEAT + or cmd.mode == AC_MODE_DRY + or cmd.mode == AC_MODE_AUTO + or _current_ac_state.mode == AC_MODE_COOL + or _current_ac_state.mode == AC_MODE_HEAT + or _current_ac_state.mode == AC_MODE_DRY + or _current_ac_state.mode == AC_MODE_AUTO) { + + hasCommand = true; + cmd.sleep = AC_SLEEP_ON; cmd.health = AC_HEALTH_OFF; // для логики пресетов cmd.health_status = AC_HEALTH_STATUS_OFF; - //this->preset = preset; - } else { - _debugMsg(F("SLEEP preset is suitable in COOL,HEAT and AUTO modes only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } + this->preset = preset; + } else { + _debugMsg(F("SLEEP preset is suitable in COOL and HEAT modes only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + } break; - case climate::CLIMATE_PRESET_NONE: - // выбран пустой пресет, сбрасываем все настройки - hasCommand = true; - cmd.health = AC_HEALTH_OFF; // для логики пресетов - cmd.health_status = AC_HEALTH_STATUS_OFF; - cmd.sleep = AC_SLEEP_OFF; // для логики пресетов - //this->preset = preset; - _debugMsg(F("Clear all power ON presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + case climate::CLIMATE_PRESET_NONE: + // выбран пустой пресет, сбрасываем все настройки + hasCommand = true; + cmd.health = AC_HEALTH_OFF; + //cmd.health_status = AC_HEALTH_STATUS_OFF; // GK: не нужно ставить, т.к. этот флаг устанавливается самим сплитом + cmd.sleep = AC_SLEEP_OFF; + cmd.mildew = AC_MILDEW_OFF; + cmd.clean = AC_CLEAN_OFF; + cmd.iFeel = AC_IFEEL_OFF; // хоть и не реализован, но отрубим. А то потом забудем указать. + this->preset = preset; + + _debugMsg(F("Clear all builtin presets."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); break; - default: - // никакие другие встроенные пресеты не поддерживаются + default: + // никакие другие встроенные пресеты не поддерживаются + _debugMsg(F("Preset %02X is unsupported."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, preset); break; - } - //this->preset = preset; - } else if (call.get_custom_preset().has_value()) { - std::string custom_preset = *call.get_custom_preset(); - if (custom_preset == Constants::HEALTH) { + } + + } else if (call.get_custom_preset().has_value()) { + std::string custom_preset = *call.get_custom_preset(); + + if (custom_preset == 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; + cmd.mildew = AC_MILDEW_OFF; // для логики пресетов + this->custom_preset = custom_preset; + + } else { + _debugMsg(F("CLEAN preset is suitable in POWER_OFF mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + } + + } else if (custom_preset == Constants::FEEL) { + _debugMsg(F("iFEEL preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); + // TODO: надо подумать, как заставить этот режим работать без пульта + //hasCommand = true; + //this->custom_preset = custom_preset; + + } else if ( custom_preset == Constants::HEALTH ) { + + if ( cmd.power == AC_POWER_ON || + _current_ac_state.power == AC_POWER_ON ) { + hasCommand = true; cmd.health = AC_HEALTH_ON; - cmd.health_status = AC_HEALTH_STATUS_ON; + //cmd.health_status = AC_HEALTH_STATUS_ON; // GK: статус кондей сам поднимает 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 ){ - + + 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){ + + } 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__); - } - //this->custom_preset = custom_preset; - } - } else if(_current_ac_state.power == AC_POWER_OFF || cmd.power == AC_POWER_OFF){ // функции при выключеном питании - - if (call.get_target_temperature().has_value()) { // блокировка изменения температуры в выключеном состоянии - this->target_temperature = _current_ac_state.temp_ambient; - this->publish_state(); - } - - if (call.get_preset().has_value()) { // пользователь выбрал пустой пресет - ClimatePreset preset = *call.get_preset(); - switch (preset) { - case climate::CLIMATE_PRESET_SLEEP: - _debugMsg(F("SLEEP preset is suitable in POWER ON mode only."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - case climate::CLIMATE_PRESET_NONE: - // выбран пустой пресет, сбрасываем все настройки - hasCommand = true; - cmd.clean = AC_CLEAN_OFF; // для логики пресетов - cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - //this->preset = preset; - _debugMsg(F("Clear all 'Power OFF' presets"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - break; - default: - // никакие другие встроенные пресеты не поддерживаются - break; + this->custom_preset = custom_preset; + + } else { + _debugMsg(F("HEALTH preset is suitable in POWER_ON mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } - //this->preset = preset; - } else { - std::string custom_preset = *call.get_custom_preset(); - if (call.get_custom_preset().has_value()) { - if (custom_preset == Constants::CLEAN) { - // режим очистки кондиционера при AC_POWER_OFF - hasCommand = true; - cmd.clean = AC_CLEAN_ON; - cmd.mildew = AC_MILDEW_OFF; // для логики пресетов - //this->custom_preset = custom_preset; - } else if (custom_preset == Constants::ANTIFUNGUS) { - // Brokly: - // включение-выключение функции "Антиплесень". - // у меня пульт отправляет 5 посылок и на включение и на выключение, но реагирует на эту кнопку - // только в режиме POWER_OFF - - // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. - // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. - // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. - cmd.mildew = AC_MILDEW_ON; - cmd.clean = AC_CLEAN_OFF; // для логики пресетов - hasCommand = true; - //this->custom_preset = custom_preset; - } else if (custom_preset == Constants::HEALTH) { - _debugMsg(F("HEALTH is not supported in POWER OFF mode."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); - } - } - //this->custom_preset = custom_preset; + + } else if (custom_preset == Constants::ANTIFUNGUS) { + // включение-выключение функции "Антиплесень". + // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. + // Уличный блок при этом гудит и тарахтит. Возможно, прогревается теплообменник для высыхания. + // Через некоторое время внешний блок замолкает и сплит закрывает жалюзи. + + // Brokly: + // включение-выключение функции "Антиплесень". + // у меня пульт отправляет 5 посылок и на включение и на выключение, но реагирует на эту кнопку + // только в режиме POWER_OFF + + // TODO: надо уточнить, в каких режимах штатно включается этот режим у кондиционера + cmd.mildew = AC_MILDEW_ON; + cmd.clean = AC_CLEAN_OFF; // для логики пресетов + + hasCommand = true; + this->custom_preset = custom_preset; + //_debugMsg(F("ANTIFUNGUS preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); + } + } + + // User requested swing_mode change + if (call.get_swing_mode().has_value()) { + ClimateSwingMode swingmode = *call.get_swing_mode(); + + 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; + } + + } + + // User requested target temperature change + if (call.get_target_temperature().has_value()) { + // выставлять температуру в режиме FAN не нужно + if ( cmd.mode != AC_MODE_FAN && _current_ac_state.mode != AC_MODE_FAN ) { + hasCommand = true; + 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 = _store_settings; // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка + + #if defined(PRESETS_SAVING) + // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка + _new_command_set = _store_settings; + #endif } } @@ -2844,7 +2813,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_two_point_target_temperature(false); // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature + + // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature + traits.set_supports_two_point_target_temperature(false); // tells the frontend what range of temperatures the climate device should display (gauge min/max values) traits.set_visual_min_temperature(Constants::AC_MIN_TEMPERATURE); @@ -2871,10 +2842,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); //traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); - /* *************** TODO: надо сделать информирование о текущем режиме, сплит поддерживает *************** - * смотри climate::ClimateAction - */ - // if the climate device supports reporting the active current action of the device with the action property. traits.set_supports_action(this->_show_action); return traits; @@ -3065,34 +3032,44 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } // отправляет сплиту заданный набор байт - // Перед отправкой проверяет пакет на корректность структуры. CRC16 рассчитывает самостоятельно и перезаписывает. + // Перед отправкой: + // устанавливает первый байт в 0xBB + // проверяет, чтобы длина тела пакета в заголовке не превышала длину буфера + // рассчитывает и записывает в конец пакета CRC 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 (data.size() == 0) { + _debugMsg(F("sendTestPacket: no data to send."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + 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) { + // всё, что не влезет в буфер - игнорируем + if (i >= AC_BUFFER_SIZE) { + _debugMsg(F("sendTestPacket: buffer size = %02d, data length = %02d. Extra data was omitted."), ESPHOME_LOG_LEVEL_ERROR, __LINE__, AC_BUFFER_SIZE, data.size()); + break; + } + // что влезает - копируем в буфер _outTestPacket.data[i] = n; i++; } - // на всякий случай указываем правильные некоторые байты + // на всякий случай указываем правильные некоторые байты: + // - установим стартовый байт _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; - //_outTestPacket.header->wifi = AC_PACKET_ANSWER; + // - установим длину тела, если она больше возможной для нашего буфера + if (_outTestPacket.header->body_length > (AC_BUFFER_SIZE - AC_HEADER_SIZE - 2)) _outTestPacket.header->body_length = AC_BUFFER_SIZE - AC_HEADER_SIZE - 2; _outTestPacket.msec = millis(); _outTestPacket.body = &(_outTestPacket.data[AC_HEADER_SIZE]); @@ -3102,7 +3079,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _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__); + _debugMsg(F("sendTestPacket: test packet loaded:"), ESPHOME_LOG_LEVEL_WARN, __LINE__); _debugPrintPacket(&_outTestPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); // ниже блок добавления отправки пакета в последовательность команд @@ -3134,39 +3111,48 @@ 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; } void set_custom_presets(const std::set &presets) { this->_supported_custom_presets = presets; } void set_custom_fan_modes(const std::set &modes) { this->_supported_custom_fan_modes = modes; } - uint8_t load_presets_result = 0xFF; - void setup() override { - #if defined(ESP32) + #if defined(PRESETS_SAVING) + void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } + bool get_store_settings() { return this->_store_settings; } + + uint8_t load_presets_result = 0xFF; + #endif + + void setup() override { + + #if defined(PRESETS_SAVING) load_presets_result = storage.load(global_presets); // читаем все пресеты из флеша _debugMsg(F("Preset base read from NVRAM, result %02d."), ESPHOME_LOG_LEVEL_WARN, __LINE__, load_presets_result); + + // TODO: Может это надо вынести за пределы дефайна пресетов? + // если это всё же нужно при инициализации объекта, то надо закинуть в initAC() + this->preset = climate::CLIMATE_PRESET_NONE; + this->custom_preset = (std::string)""; + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->fan_mode = climate::CLIMATE_FAN_LOW; + this->custom_fan_mode = (std::string)""; #endif - this->preset = climate::CLIMATE_PRESET_NONE; - this->custom_preset = (std::string)""; - this->mode = climate::CLIMATE_MODE_OFF; - this->action = climate::CLIMATE_ACTION_IDLE; - this->fan_mode = climate::CLIMATE_FAN_LOW; - this->custom_fan_mode = (std::string)""; }; void loop() override { if (!get_hw_initialized()) return; - // контролируем сохранение пресета - if(_new_command_set){ //нужно сохранить пресет - _new_command_set = false; - save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов - } + #if defined(PRESETS_SAVING) + // контролируем сохранение пресета + if(_new_command_set){ //нужно сохранить пресет + _new_command_set = false; + save_preset((ac_command_t *)&_current_ac_state); // переносим текущие данные в массив пресетов + } + #endif - // отрабатываем состояния конечного автомата + /// отрабатываем состояния конечного автомата switch (_ac_state) { case ACSM_RECEIVING_PACKET: // находимся в процессе получения пакета, никакие отправки в этом состоянии невозможны @@ -3195,11 +3181,12 @@ 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 +} // namespace esphome From 6272dd872e0aac7301d72324f3947d17671015c3 Mon Sep 17 00:00:00 2001 From: Brokly Date: Sun, 29 May 2022 16:35:10 +0300 Subject: [PATCH 56/74] =?UTF-8?q?=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20Traits,=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D1=8F=20DestTemp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/aux_ac.h | 194 ++++++++++++++++++++--------------- components/aux_ac/climate.py | 22 ++-- 2 files changed, 121 insertions(+), 95 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 6afeb8c..ecdcb12 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -278,7 +278,7 @@ struct packet_big_info_body_t { // байт 14 тела (байт 22 пакета) // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b22 - uint8_t strange_temperature_int; // от режима не зависит, растет при включении инвертора; температура двигателя? + uint8_t compressor_temperature_int; // от режима не зависит, растет при включении инвертора; температура двигателя? // байт 15 тела (байт 23 пакета) // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b23 @@ -453,7 +453,6 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ ac_health health;\ ac_mode mode;\ ac_sleep sleep;\ - ac_ifeel iFeel;\ ac_louver louver;\ ac_fanspeed fanSpeed;\ ac_fanturbo fanTurbo;\ @@ -467,7 +466,7 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // чистый размер этой структуры 20 байт, скорее всего из-за выравнивания, она будет больше // из-за такого приема нужно контролировать размер копируемых данных руками -#define AC_COMMAND_BASE_SIZE 21 +#define AC_COMMAND_BASE_SIZE 20 #if defined(PRESETS_SAVING) // структура для сохранения данных @@ -494,11 +493,11 @@ struct ac_command_t { int8_t temp_outdoor; // внешняя температура int8_t temp_inbound; // температура входящая int8_t temp_outbound; // температура исходящая - int8_t temp_strange; // непонятная температура, понаблюдаем + int8_t temp_compressor; // непонятная температура, понаблюдаем ac_realFan realFanSpeed; // текущая скорость вентилятора uint8_t invertor_power; // мощность инвертора - uint8_t pressure; // предположительно давление bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) + ac_ifeel iFeel; }; typedef ac_command_t ac_state_t; // текущее состояние параметров кондея можно хранить в таком же формате, как и комманды @@ -618,6 +617,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { std::set _supported_presets{}; std::set _supported_custom_presets{}; std::set _supported_custom_fan_modes{}; + + // The capabilities of the climate device + // Шаблон параметров отображения виджета + esphome::climate::ClimateTraits _traits; // состояние конечного автомата acsm_state _ac_state = ACSM_IDLE; @@ -660,6 +663,18 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // флаг успешного выполнения стартовой последовательности команд bool _startupSequenceComlete = false; + + // нормализация показаний температуры, приведение в диапазон + float _temp_target_normalise(float temp){ + auto traits = this->get_traits(); + float temp_min = traits.get_visual_min_temperature(); + float temp_max = traits.get_visual_max_temperature(); + if (temp < temp_min) temp = temp_min; + if (temp > temp_max) temp = temp_max; + if (temp < Constants::AC_MIN_TEMPERATURE) temp = Constants::AC_MIN_TEMPERATURE; + if (temp > Constants::AC_MAX_TEMPERATURE) temp = Constants::AC_MAX_TEMPERATURE; + return temp; + } // очистка последовательности команд void _clearSequence(){ @@ -835,7 +850,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd->temp_outdoor = 0; cmd->temp_inbound = 0; cmd->temp_outbound = 0; - cmd->temp_strange = 0; + cmd->temp_compressor = 0; cmd->realFanSpeed = AC_REAL_FAN_UNTOUCHED; }; @@ -1204,9 +1219,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _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->compressor_temperature_int - 0x20; + stateChangedFlag = stateChangedFlag || (_current_ac_state.temp_compressor != stateFloat); + _current_ac_state.temp_compressor = stateFloat; // реальная скорость проперлера stateFloat = big_info_body->realFanSpeed; @@ -1515,9 +1530,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // целевая температура кондиционера if (cmd->temp_target_matter){ // устраняем выход за границы диапазона (это ограничение самого кондиционера) - if (cmd->temp_target < Constants::AC_MIN_TEMPERATURE) cmd->temp_target = Constants::AC_MIN_TEMPERATURE; - if (cmd->temp_target > Constants::AC_MAX_TEMPERATURE) cmd->temp_target = Constants::AC_MAX_TEMPERATURE; - + cmd->temp_target = _temp_target_normalise(cmd->temp_target); // целая часть температуры pack->body[2] = (pack->body[2] & ~AC_TEMP_TARGET_INT_PART_MASK) | (((uint8_t)(cmd->temp_target) - 8) << 3); @@ -1786,7 +1799,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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_compressor_temperature_ =nullptr; // текущая мощность компрессора esphome::sensor::Sensor *sensor_invertor_power_ = nullptr; // бинарный сенсор, отображающий состояние дисплея @@ -1896,7 +1909,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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_compressor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_compressor_temperature_ = temperature_sensor; } void set_defrost_state(binary_sensor::BinarySensor *defrost_state_sensor) { sensor_defrost_ = defrost_state_sensor; } 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; } @@ -2233,8 +2246,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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_compressor_temperature_ != nullptr) + sensor_compressor_temperature_->publish_state(_current_ac_state.temp_compressor); // мощность инвертора if (sensor_invertor_power_ != nullptr) sensor_invertor_power_->publish_state(_current_ac_state.invertor_power); @@ -2357,21 +2370,21 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } - 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()); + if ((this->sensor_compressor_temperature_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Strange Temperature"), (this->sensor_compressor_temperature_)->get_name().c_str()); + if (!(this->sensor_compressor_temperature_)->get_device_class().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_compressor_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()); + ESP_LOGCONFIG(Constants::TAG, "%s State Class: '%s'", " ", state_class_to_string((this->sensor_compressor_temperature_)->get_state_class()).c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Unit of Measurement: '%s'", " ", (this->sensor_compressor_temperature_)->get_unit_of_measurement().c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_compressor_temperature_)->get_accuracy_decimals()); + if (!(this->sensor_compressor_temperature_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_compressor_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_compressor_temperature_)->unique_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_compressor_temperature_)->unique_id().c_str()); } - if ((this->sensor_strange_temperature_)->get_force_update()) { + if ((this->sensor_compressor_temperature_)->get_force_update()) { ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); } } @@ -2788,11 +2801,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // выставлять температуру в режиме FAN не нужно if ( cmd.mode != AC_MODE_FAN && _current_ac_state.mode != AC_MODE_FAN ) { hasCommand = true; - 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 = _temp_target_normalise(*call.get_target_temperature());// Send target temp to climate cmd.temp_target_matter = true; } } @@ -2808,43 +2817,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } } + // как оказалось сюда обращаются каждый раз для получения любого параметра + // по этому имеет смысл держать готовый объект esphome::climate::ClimateTraits traits() override { - // The capabilities of the climate device - auto traits = climate::ClimateTraits(); - - traits.set_supports_current_temperature(true); - - // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature - traits.set_supports_two_point_target_temperature(false); - - // tells the frontend what range of temperatures the climate device should display (gauge min/max values) - traits.set_visual_min_temperature(Constants::AC_MIN_TEMPERATURE); - traits.set_visual_max_temperature(Constants::AC_MAX_TEMPERATURE); - // the step with which to increase/decrease target temperature. This also affects with how many decimal places the temperature is shown. - traits.set_visual_temperature_step(Constants::AC_TEMPERATURE_STEP); - - traits.set_supported_modes(this->_supported_modes); - traits.set_supported_swing_modes(this->_supported_swing_modes); - traits.set_supported_presets(this->_supported_presets); - traits.set_supported_custom_presets(this->_supported_custom_presets); - traits.set_supported_custom_fan_modes(this->_supported_custom_fan_modes); - - /* + MINIMAL SET */ - traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF); - traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM); - traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH); - traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF); - //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL); - //traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_BOTH); - traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); - //traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); - - traits.set_supports_action(this->_show_action); - - return traits; + return _traits; } // запрос маленького пакета статуса кондиционера @@ -3111,11 +3087,21 @@ 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_supported_modes(const std::set &modes) { this->_supported_modes = modes; } - void set_supported_swing_modes(const std::set &modes) { this->_supported_swing_modes = modes; } - void set_supported_presets(const std::set &presets) { this->_supported_presets = presets; } - void set_custom_presets(const std::set &presets) { this->_supported_custom_presets = presets; } - void set_custom_fan_modes(const std::set &modes) { this->_supported_custom_fan_modes = modes; } + // возможно функции get и не нужны, но вроде как должны быть + void set_supported_modes(const std::set &modes) { this->_supported_modes = modes;} + std::setget_supported_modes(){return this->_supported_modes;} + + void set_supported_swing_modes(const std::set &modes) { this->_supported_swing_modes = modes;} + std::set get_supported_swing_modes(){return this->_supported_swing_modes;} + + void set_supported_presets(const std::set &presets) { this->_supported_presets = presets;} + const std::set& get_supported_presets(){return this->_supported_presets;} + + void set_custom_presets(const std::set &presets) { this->_supported_custom_presets = presets;} + const std::set& get_supported_custom_presets(){return this->_supported_custom_presets;} + + void set_custom_fan_modes(const std::set &modes) { this->_supported_custom_fan_modes = modes;} + const std::set& get_supported_custom_fan_modes(){return this->_supported_custom_fan_modes;} #if defined(PRESETS_SAVING) void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } @@ -3129,16 +3115,56 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { #if defined(PRESETS_SAVING) load_presets_result = storage.load(global_presets); // читаем все пресеты из флеша _debugMsg(F("Preset base read from NVRAM, result %02d."), ESPHOME_LOG_LEVEL_WARN, __LINE__, load_presets_result); - - // TODO: Может это надо вынести за пределы дефайна пресетов? - // если это всё же нужно при инициализации объекта, то надо закинуть в initAC() - this->preset = climate::CLIMATE_PRESET_NONE; - this->custom_preset = (std::string)""; - this->mode = climate::CLIMATE_MODE_OFF; - this->action = climate::CLIMATE_ACTION_IDLE; - this->fan_mode = climate::CLIMATE_FAN_LOW; - this->custom_fan_mode = (std::string)""; #endif + // TODO: Может это надо вынести за пределы дефайна пресетов? + // если это всё же нужно при инициализации объекта, то надо закинуть в initAC() + this->preset = climate::CLIMATE_PRESET_NONE; + this->custom_preset = (std::string)""; + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->fan_mode = climate::CLIMATE_FAN_LOW; + this->custom_fan_mode = (std::string)""; + + // заполнение шаблона параметров отображения виджета + _traits.set_supports_current_temperature(true); + _traits.set_supports_two_point_target_temperature(false); // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature + + _traits.set_supported_modes(this->_supported_modes); + _traits.set_supported_swing_modes(this->_supported_swing_modes); + _traits.set_supported_presets(this->_supported_presets); + _traits.set_supported_custom_presets(this->_supported_custom_presets); + _traits.set_supported_custom_fan_modes(this->_supported_custom_fan_modes); + + // tells the frontend what range of temperatures the climate device should display (gauge min/max values) + _traits.set_visual_min_temperature(Constants::AC_MIN_TEMPERATURE); + _traits.set_visual_max_temperature(Constants::AC_MAX_TEMPERATURE); + // the step with which to increase/decrease target temperature. This also affects with how many decimal places the temperature is shown. + _traits.set_visual_temperature_step(Constants::AC_TEMPERATURE_STEP); + + /* + MINIMAL SET */ + _traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF); + _traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY); + _traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); + _traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); + _traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM); + _traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH); + _traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF); + //_traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL); + //_traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_BOTH); + _traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); + //_traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); + + // if the climate device supports reporting the active current action of the device with the action property. + _traits.set_supports_action(this->_show_action); + + // нужно инициализировать эти данные ! + this->preset = climate::CLIMATE_PRESET_NONE; + this->custom_preset = (std::string)""; + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->fan_mode = climate::CLIMATE_FAN_LOW; + this->custom_fan_mode = (std::string)""; + }; void loop() override { @@ -3189,4 +3215,4 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { }; } // namespace aux_ac -} // namespace esphome +} // namespace esphome \ No newline at end of file diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 58a9ab2..a754284 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -50,15 +50,15 @@ 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_COMPRESSOR_TEMPERATURE = 'compressor_temperature' +ICON_COMPRESSOR_TEMPERATURE = 'mdi:thermometer-lines' CONF_DISPLAY_STATE = 'display_state' CONF_INVERTOR_POWER = 'invertor_power' CONF_DEFROST_STATE = 'defrost_state' ICON_DEFROST = "mdi:snowflake-melt" CONF_DISPLAY_INVERTED = 'display_inverted' ICON_DISPLAY = "mdi:clock-digital" -CONF_STORE_SETTINGS = 'store_settings' +#CONF_STORE_SETTINGS = 'store_settings' aux_ac_ns = cg.esphome_ns.namespace("aux_ac") @@ -123,7 +123,7 @@ 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_STORE_SETTINGS, default="false"): cv.boolean, + #cv.Optional(CONF_STORE_SETTINGS, default="false"): cv.boolean, cv.Optional(CONF_DEFROST_STATE, default="false"): cv.boolean, cv.Optional(CONF_INVERTOR_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, @@ -161,7 +161,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INBOUND_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, icon=ICON_INBOUND_TEMPERATURE, - accuracy_decimals=1, + accuracy_decimals=0, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ).extend( @@ -180,9 +180,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, } ), - cv.Optional(CONF_STRANGE_TEMPERATURE): sensor.sensor_schema( + cv.Optional(CONF_COMPRESSOR_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, - icon=ICON_STRANGE_TEMPERATURE, + icon=ICON_COMPRESSOR_TEMPERATURE, accuracy_decimals=0, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, @@ -251,10 +251,10 @@ async def to_code(config): 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] + if CONF_COMPRESSOR_TEMPERATURE in config: + conf = config[CONF_COMPRESSOR_TEMPERATURE] sens = await sensor.new_sensor(conf) - cg.add(var.set_strange_temperature_sensor(sens)) + cg.add(var.set_compressor_temperature_sensor(sens)) if CONF_DISPLAY_STATE in config: conf = config[CONF_DISPLAY_STATE] @@ -274,7 +274,7 @@ async def to_code(config): cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) cg.add(var.set_show_action(config[CONF_SHOW_ACTION])) cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED])) - cg.add(var.set_store_settings(config[CONF_STORE_SETTINGS])) + #cg.add(var.set_store_settings(config[CONF_STORE_SETTINGS])) if CONF_SUPPORTED_MODES in config: cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) if CONF_SUPPORTED_SWING_MODES in config: From 21ccae4bce3d198569f449f43a777ad49052c49e Mon Sep 17 00:00:00 2001 From: Brokly Date: Tue, 31 May 2022 12:46:36 +0300 Subject: [PATCH 57/74] Update .gitignore --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 66c9f45..4754cd9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ **/secrets.yaml **/livingroom_ac/ **/kitchen_ac/ -/examples/*/*.h +**/examples/*/*.h **/tests/test_* **/__pycache__ -**/private/ \ No newline at end of file +**/private/ From 415d9a89906a8b1d03aef7cf454c7e85d97c3e74 Mon Sep 17 00:00:00 2001 From: Brokly Date: Tue, 31 May 2022 12:49:50 +0300 Subject: [PATCH 58/74] Update .gitignore --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4754cd9..1c4bbeb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,11 @@ **/secrets.yaml **/livingroom_ac/ **/kitchen_ac/ -**/examples/*/*.h +**/examples **/tests/test_* **/__pycache__ **/private/ +**/docs/ +**/enclosure/ +**/images/ +**/tests/ From 2980092269fdccd5eb31bc6749d58ab7f13fad0c Mon Sep 17 00:00:00 2001 From: Brokly Date: Tue, 31 May 2022 22:32:44 +0300 Subject: [PATCH 59/74] =?UTF-8?q?=D0=92=D1=82=D0=BE=D1=80=D0=BE=D0=B9=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=20=D0=B2=D1=8B=D0=BF=D0=B8=D0=BB=D0=B8=D0=BB?= =?UTF-8?q?=20iFeel,=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=81=D1=8F=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/aux_ac.h | 51 ++---------------------------------- components/aux_ac/climate.py | 3 --- 2 files changed, 2 insertions(+), 52 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index ecdcb12..5be4012 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -47,7 +47,6 @@ public: static const std::string MUTE; static const std::string TURBO; static const std::string CLEAN; - static const std::string FEEL; static const std::string HEALTH; static const std::string ANTIFUNGUS; @@ -72,7 +71,6 @@ const std::string Constants::TURBO = "turbo"; // custom presets const std::string Constants::CLEAN = "Clean"; -const std::string Constants::FEEL = "Feel"; const std::string Constants::HEALTH = "Health"; const std::string Constants::ANTIFUNGUS = "Antifungus"; @@ -389,10 +387,6 @@ enum ac_mode : uint8_t { AC_MODE_AUTO = 0x00, AC_MODE_COOL = 0x20, AC_MODE_DRY = #define AC_SLEEP_MASK 0b00000100 enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTOUCHED = 0xFF }; -// функция iFeel - поддерживате температуру по датчику в пульте ДУ, а не во внутреннем блоке кондиционера -#define AC_IFEEL_MASK 0b00001000 -enum ac_ifeel : uint8_t { AC_IFEEL_OFF = 0x00, AC_IFEEL_ON = 0x08, AC_IFEEL_UNTOUCHED = 0xFF }; - // Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но должна быть такая возможность на уровне железа. // TODO: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V #define AC_LOUVERV_MASK 0b00000111 @@ -493,11 +487,10 @@ struct ac_command_t { int8_t temp_outdoor; // внешняя температура int8_t temp_inbound; // температура входящая int8_t temp_outbound; // температура исходящая - int8_t temp_compressor; // непонятная температура, понаблюдаем + int8_t temp_compressor; // температура компрессора ac_realFan realFanSpeed; // текущая скорость вентилятора uint8_t invertor_power; // мощность инвертора bool defrost; // режим разморозки внешнего блока (накопление тепла + прогрев испарителя) - ac_ifeel iFeel; }; typedef ac_command_t ac_state_t; // текущее состояние параметров кондея можно хранить в таком же формате, как и комманды @@ -834,7 +827,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd->fanTurbo = AC_FANTURBO_UNTOUCHED; cmd->health = AC_HEALTH_UNTOUCHED; cmd->health_status = AC_HEALTH_STATUS_UNTOUCHED; - cmd->iFeel = AC_IFEEL_UNTOUCHED; cmd->louver.louver_h = AC_LOUVERH_UNTOUCHED; cmd->louver.louver_v = AC_LOUVERV_UNTOUCHED; cmd->mildew = AC_MILDEW_UNTOUCHED; @@ -1150,10 +1142,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { stateChangedFlag = stateChangedFlag || (_current_ac_state.sleep != (ac_sleep)stateByte); _current_ac_state.sleep = (ac_sleep)stateByte; - stateByte = small_info_body->mode & AC_IFEEL_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.iFeel != (ac_ifeel)stateByte); - _current_ac_state.iFeel = (ac_ifeel)stateByte; - stateByte = small_info_body->status & AC_POWER_MASK; stateChangedFlag = stateChangedFlag || (_current_ac_state.power != (ac_power)stateByte); _current_ac_state.power = (ac_power)stateByte; @@ -1578,9 +1566,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (cmd->sleep != AC_SLEEP_UNTOUCHED){ pack->body[7] = (pack->body[7] & ~AC_SLEEP_MASK) | cmd->sleep; } - if (cmd->iFeel != AC_IFEEL_UNTOUCHED){ - pack->body[7] = (pack->body[7] & ~AC_IFEEL_MASK) | cmd->iFeel; - } // питание вкл/выкл if (cmd->power != AC_POWER_UNTOUCHED){ @@ -2099,23 +2084,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("Climate fan MUTE mode: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.fanMute); //======================== ОТОБРАЖЕНИЕ ПРЕСЕТОВ ================================ - /*************************** iFEEL CUSTOM PRESET ***************************/ - // режим поддержки температуры в районе пульта, работает только при включенном конедее - if( _current_ac_state.iFeel == AC_IFEEL_ON && - _current_ac_state.power == AC_POWER_ON ) { - - this->custom_preset = Constants::FEEL; - - } else if ( this->custom_preset == Constants::FEEL ) { - - // AC_IFEEL_OFF - // только в том случае, если до этого пресет был установлен - this->custom_preset = (std::string)""; - - } - - _debugMsg(F("Climate iFEEL preset: %i"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _current_ac_state.iFeel); - /*************************** HEALTH CUSTOM PRESET ***************************/ // режим работы ионизатора if( _current_ac_state.health == AC_HEALTH_ON && @@ -2371,7 +2339,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } if ((this->sensor_compressor_temperature_) != nullptr) { - ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Strange Temperature"), (this->sensor_compressor_temperature_)->get_name().c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Compressor Temperature"), (this->sensor_compressor_temperature_)->get_name().c_str()); if (!(this->sensor_compressor_temperature_)->get_device_class().empty()) { ESP_LOGCONFIG(Constants::TAG, "%s Device Class: '%s'", " ", (this->sensor_compressor_temperature_)->get_device_class().c_str()); } @@ -2667,7 +2635,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd.sleep = AC_SLEEP_OFF; cmd.mildew = AC_MILDEW_OFF; cmd.clean = AC_CLEAN_OFF; - cmd.iFeel = AC_IFEEL_OFF; // хоть и не реализован, но отрубим. А то потом забудем указать. this->preset = preset; _debugMsg(F("Clear all builtin presets."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); @@ -2696,12 +2663,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _debugMsg(F("CLEAN preset is suitable in POWER_OFF mode only."), ESPHOME_LOG_LEVEL_WARN, __LINE__); } - } else if (custom_preset == Constants::FEEL) { - _debugMsg(F("iFEEL preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); - // TODO: надо подумать, как заставить этот режим работать без пульта - //hasCommand = true; - //this->custom_preset = custom_preset; - } else if ( custom_preset == Constants::HEALTH ) { if ( cmd.power == AC_POWER_ON || @@ -3157,14 +3118,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // if the climate device supports reporting the active current action of the device with the action property. _traits.set_supports_action(this->_show_action); - // нужно инициализировать эти данные ! - this->preset = climate::CLIMATE_PRESET_NONE; - this->custom_preset = (std::string)""; - this->mode = climate::CLIMATE_MODE_OFF; - this->action = climate::CLIMATE_ACTION_IDLE; - this->fan_mode = climate::CLIMATE_FAN_LOW; - this->custom_fan_mode = (std::string)""; - }; void loop() override { diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index a754284..2a3070e 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -39,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' From 14e448d348efec295bba60dad6e6ed48b35d8e58 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Wed, 1 Jun 2022 14:26:03 +0300 Subject: [PATCH 60/74] test manual partial PR --- .gitignore | 8 +- ac_common.yaml | 123 +++++++++++++++++++++++++++++ examples/simple/aux_ac_simple.yaml | 4 +- 3 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 ac_common.yaml diff --git a/.gitignore b/.gitignore index 1c4bbeb..66c9f45 100644 --- a/.gitignore +++ b/.gitignore @@ -11,11 +11,7 @@ **/secrets.yaml **/livingroom_ac/ **/kitchen_ac/ -**/examples +/examples/*/*.h **/tests/test_* **/__pycache__ -**/private/ -**/docs/ -**/enclosure/ -**/images/ -**/tests/ +**/private/ \ No newline at end of file diff --git a/ac_common.yaml b/ac_common.yaml new file mode 100644 index 0000000..911ef52 --- /dev/null +++ b/ac_common.yaml @@ -0,0 +1,123 @@ +# DON'T COMPILE THIS FILE +# This file contains common settings for all air conditioners of your house +external_components: + - source: github://GrKoR/esphome_aux_ac_component + components: [ aux_ac ] + refresh: 0s + + esphome: + name: $devicename + platform: ESP8266 + board: esp12e + + wifi: + ssid: !secret wifi_ssid + password: !secret wifi_pass + manual_ip: + static_ip: ${wifi_ip} + gateway: !secret wifi_gateway + subnet: !secret wifi_subnet + ap: + ssid: ${upper_devicename} Hotspot + password: !secret wifi_ap_pass + use_address: ${wifi_ota_ip} + + captive_portal: + debug: + + logger: + level: DEBUG + baud_rate: 0 + # set hardware_uart to UART1 and comment out baud_rate above in case of boot crashes + # it is suitable if you need hardware loggin + # hardware_uart: UART1 + + api: + password: !secret api_pass + + ota: + password: !secret ota_pass + + web_server: + port: 80 + auth: + username: !secret web_server_user + password: !secret web_server_password + + # UART0 configuration for AUX air conditioner communication + uart: + id: ac_uart_bus + tx_pin: GPIO1 + rx_pin: GPIO3 + 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: true + indoor_temperature: + name: ${upper_devicename} Indoor Temperature + id: ${devicename}_indoor_temp + internal: false + display_state: + name: $upper_devicename Display State + id: ${devicename}_display_state + internal: false + visual: + min_temperature: 16 + max_temperature: 32 + temperature_step: 0.5 + 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: + # just wifi signal strength for debug purpose only + - 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(${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 + \ No newline at end of file diff --git a/examples/simple/aux_ac_simple.yaml b/examples/simple/aux_ac_simple.yaml index 3da24b1..df12e23 100644 --- a/examples/simple/aux_ac_simple.yaml +++ b/examples/simple/aux_ac_simple.yaml @@ -1,5 +1,5 @@ external_components: - - source: github://Brokly/esphome_aux_ac_component + - source: github://GrKoR/esphome_aux_ac_component components: [ aux_ac ] refresh: 0s @@ -43,4 +43,4 @@ uart: climate: - platform: aux_ac - name: "AC Name" + name: "AC Name" \ No newline at end of file From 38b7b0d687fca14ba666e1016210871b72445a47 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Wed, 1 Jun 2022 14:26:49 +0300 Subject: [PATCH 61/74] partial 2 --- ac_common.yaml => examples/advanced/ac_common.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ac_common.yaml => examples/advanced/ac_common.yaml (100%) diff --git a/ac_common.yaml b/examples/advanced/ac_common.yaml similarity index 100% rename from ac_common.yaml rename to examples/advanced/ac_common.yaml From 989717d603a3c37fe7fe917eeff0a010c467fdea Mon Sep 17 00:00:00 2001 From: GrKoR Date: Wed, 1 Jun 2022 14:30:33 +0300 Subject: [PATCH 62/74] partial 3 --- examples/advanced/ac-energolux-bern.yaml | 148 ----------- examples/advanced/ac_common.yaml | 239 +++++++++--------- .../{ac-kitchen-bern.yaml => ac_kitchen.yaml} | 5 +- ...ivingroom-bern.yaml => ac_livingroom.yaml} | 5 +- 4 files changed, 123 insertions(+), 274 deletions(-) delete mode 100644 examples/advanced/ac-energolux-bern.yaml rename examples/advanced/{ac-kitchen-bern.yaml => ac_kitchen.yaml} (86%) rename examples/advanced/{ac-livingroom-bern.yaml => ac_livingroom.yaml} (85%) diff --git a/examples/advanced/ac-energolux-bern.yaml b/examples/advanced/ac-energolux-bern.yaml deleted file mode 100644 index d850cb3..0000000 --- a/examples/advanced/ac-energolux-bern.yaml +++ /dev/null @@ -1,148 +0,0 @@ -external_components: - - source: - type: git - url: https://github.com/Brokly/esphome_aux_ac_component - #type: local - #path: my_components - components: [ aux_ac ] - refresh: 0s - -esphome: - name: $devicename - platform: ESP8266 - board: esp12e - -#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 - baud_rate: 0 - -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 - tx_pin: GPIO1 - rx_pin: GPIO3 - 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 - store_settings: true - indoor_temperature: - name: ${upper_devicename} Indoor Temperature - id: ${low_devicename}_indoor_temp - internal: false - outdoor_temperature: - name: ${upper_devicename} Outdoor Temperature - id: ${low_devicename}_outdoor_temp - internal: false - outbound_temperature: - name: ${upper_devicename} Colant Outbound Temperature - id: ${low_devicename}_outbound_temp - internal: false - inbound_temperature: - name: ${upper_devicename} Colant Inbound Temperature - id: ${low_devicename}_inbound_temp - internal: false - strange_temperature: - name: ${upper_devicename} 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 - - 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 diff --git a/examples/advanced/ac_common.yaml b/examples/advanced/ac_common.yaml index 911ef52..e012151 100644 --- a/examples/advanced/ac_common.yaml +++ b/examples/advanced/ac_common.yaml @@ -1,123 +1,122 @@ # DON'T COMPILE THIS FILE # This file contains common settings for all air conditioners of your house external_components: - - source: github://GrKoR/esphome_aux_ac_component - components: [ aux_ac ] - refresh: 0s - - esphome: - name: $devicename - platform: ESP8266 - board: esp12e - - wifi: - ssid: !secret wifi_ssid - password: !secret wifi_pass - manual_ip: - static_ip: ${wifi_ip} - gateway: !secret wifi_gateway - subnet: !secret wifi_subnet - ap: - ssid: ${upper_devicename} Hotspot - password: !secret wifi_ap_pass - use_address: ${wifi_ota_ip} - - captive_portal: - debug: - - logger: - level: DEBUG - baud_rate: 0 - # set hardware_uart to UART1 and comment out baud_rate above in case of boot crashes - # it is suitable if you need hardware loggin - # hardware_uart: UART1 - - api: - password: !secret api_pass - - ota: - password: !secret ota_pass - - web_server: - port: 80 - auth: - username: !secret web_server_user - password: !secret web_server_password - - # UART0 configuration for AUX air conditioner communication - uart: - id: ac_uart_bus - tx_pin: GPIO1 - rx_pin: GPIO3 - 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: true - indoor_temperature: - name: ${upper_devicename} Indoor Temperature - id: ${devicename}_indoor_temp - internal: false - display_state: - name: $upper_devicename Display State - id: ${devicename}_display_state - internal: false - visual: - min_temperature: 16 - max_temperature: 32 - temperature_step: 0.5 - 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: - # just wifi signal strength for debug purpose only - - 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(${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 - \ No newline at end of file + - source: github://GrKoR/esphome_aux_ac_component + components: [ aux_ac ] + refresh: 0s + +esphome: + name: $devicename + platform: ESP8266 + board: esp12e + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_pass + manual_ip: + static_ip: ${wifi_ip} + gateway: !secret wifi_gateway + subnet: !secret wifi_subnet + ap: + ssid: ${upper_devicename} Hotspot + password: !secret wifi_ap_pass + use_address: ${wifi_ota_ip} + +captive_portal: +debug: + +logger: + level: DEBUG + baud_rate: 0 + # set hardware_uart to UART1 and comment out baud_rate above in case of boot crashes + # it is suitable if you need hardware loggin + # hardware_uart: UART1 + +api: + password: !secret api_pass + +ota: + password: !secret ota_pass + +web_server: + port: 80 + auth: + username: !secret web_server_user + password: !secret web_server_password + +# UART0 configuration for AUX air conditioner communication +uart: + id: ac_uart_bus + tx_pin: GPIO1 + rx_pin: GPIO3 + 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: true + indoor_temperature: + name: ${upper_devicename} Indoor Temperature + id: ${devicename}_indoor_temp + internal: false + display_state: + name: $upper_devicename Display State + id: ${devicename}_display_state + internal: false + visual: + min_temperature: 16 + max_temperature: 32 + temperature_step: 0.5 + 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: + # just wifi signal strength for debug purpose only + - 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(${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 diff --git a/examples/advanced/ac-kitchen-bern.yaml b/examples/advanced/ac_kitchen.yaml similarity index 86% rename from examples/advanced/ac-kitchen-bern.yaml rename to examples/advanced/ac_kitchen.yaml index 867ead3..91ead20 100644 --- a/examples/advanced/ac-kitchen-bern.yaml +++ b/examples/advanced/ac_kitchen.yaml @@ -6,8 +6,7 @@ #=================================================================================== substitutions: - devicename: kitchen-ac - low_devicename: kitchen_ac + devicename: kitchen_ac upper_devicename: Kitchen AC # use different wifi_ip and wifi_ota_ip in case of esp ip-address change @@ -15,4 +14,4 @@ substitutions: wifi_ip: !secret wifi_ip_kitchen wifi_ota_ip: !secret wifi_ota_ip_kitchen -<<: !include ac-energolux-bern.yaml +<<: !include ac_common.yaml \ No newline at end of file diff --git a/examples/advanced/ac-livingroom-bern.yaml b/examples/advanced/ac_livingroom.yaml similarity index 85% rename from examples/advanced/ac-livingroom-bern.yaml rename to examples/advanced/ac_livingroom.yaml index f874727..08f985b 100644 --- a/examples/advanced/ac-livingroom-bern.yaml +++ b/examples/advanced/ac_livingroom.yaml @@ -6,8 +6,7 @@ #=================================================================================== substitutions: - devicename: livingroom-ac - low_devicename: livingroom_ac + devicename: livingroom_ac upper_devicename: Livingroom AC # use different wifi_ip and wifi_ota_ip in case of esp ip-address change @@ -15,4 +14,4 @@ substitutions: wifi_ip: !secret wifi_ip_livingroom wifi_ota_ip: !secret wifi_ota_ip_livingroom -<<: !include ac-energolux-bern.yaml +<<: !include ac_common.yaml \ No newline at end of file From adadd71124e1c3389626478b9e8d9649e7a143f0 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Wed, 1 Jun 2022 14:55:25 +0300 Subject: [PATCH 63/74] looks like it is complete --- components/aux_ac/aux_ac.h | 29 +++++++++++++---------------- components/aux_ac/climate.py | 3 --- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index cafef41..a58364f 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -1886,6 +1886,15 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // выполнена ли уже стартовая последовательность команд (сбор информации о статусе кондея) _startupSequenceComlete = false; + + // первоначальная инициализация + // TODO: вроде бы введено Brokly, но было в setup(). Надо узнать, зачем оно нам вообще? + this->preset = climate::CLIMATE_PRESET_NONE; + this->custom_preset = (std::string)""; + this->mode = climate::CLIMATE_MODE_OFF; + this->action = climate::CLIMATE_ACTION_IDLE; + this->fan_mode = climate::CLIMATE_FAN_LOW; + this->custom_fan_mode = (std::string)""; }; float get_setup_priority() const override { return esphome::setup_priority::DATA; } @@ -3071,29 +3080,16 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { uint8_t load_presets_result = 0xFF; #endif - #if defined(PRESETS_SAVING) - void set_store_settings(bool store_settings) { this->_store_settings = store_settings; } - bool get_store_settings() { return this->_store_settings; } - - uint8_t load_presets_result = 0xFF; - #endif - void setup() override { #if defined(PRESETS_SAVING) 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 - // TODO: Может это надо вынести за пределы дефайна пресетов? - // если это всё же нужно при инициализации объекта, то надо закинуть в initAC() - this->preset = climate::CLIMATE_PRESET_NONE; - this->custom_preset = (std::string)""; - this->mode = climate::CLIMATE_MODE_OFF; - this->action = climate::CLIMATE_ACTION_IDLE; - this->fan_mode = climate::CLIMATE_FAN_LOW; - this->custom_fan_mode = (std::string)""; - + // заполнение шаблона параметров отображения виджета + // GK: всё же похоже правильнее это делать тут, а не в initAC() + // initAC() в формируемом питоном коде вызывается до вызова aux_ac.set_supported_***() с установленными пользователем в конфиге параметрами _traits.set_supports_current_temperature(true); _traits.set_supports_two_point_target_temperature(false); // if the climate device's target temperature should be split in target_temperature_low and target_temperature_high instead of just the single target_temperature @@ -3104,6 +3100,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _traits.set_supported_custom_fan_modes(this->_supported_custom_fan_modes); // tells the frontend what range of temperatures the climate device should display (gauge min/max values) + // TODO: GK: а вот здесь похоже неправильно. Похоже, так мы не сможем выставить в конфиге свой диапазон температур - всегда будет от AC_MIN_TEMPERATURE до AC_MAX_TEMPERATURE _traits.set_visual_min_temperature(Constants::AC_MIN_TEMPERATURE); _traits.set_visual_max_temperature(Constants::AC_MAX_TEMPERATURE); // the step with which to increase/decrease target temperature. This also affects with how many decimal places the temperature is shown. diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 39ec210..6096ebb 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -55,7 +55,6 @@ CONF_DEFROST_STATE = 'defrost_state' ICON_DEFROST = "mdi:snowflake-melt" CONF_DISPLAY_INVERTED = 'display_inverted' ICON_DISPLAY = "mdi:clock-digital" -#CONF_STORE_SETTINGS = 'store_settings' aux_ac_ns = cg.esphome_ns.namespace("aux_ac") @@ -120,7 +119,6 @@ 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_STORE_SETTINGS, default="false"): cv.boolean, cv.Optional(CONF_DEFROST_STATE, default="false"): cv.boolean, cv.Optional(CONF_INVERTOR_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, @@ -271,7 +269,6 @@ async def to_code(config): cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) cg.add(var.set_show_action(config[CONF_SHOW_ACTION])) cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED])) - #cg.add(var.set_store_settings(config[CONF_STORE_SETTINGS])) if CONF_SUPPORTED_MODES in config: cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) if CONF_SUPPORTED_SWING_MODES in config: From f5471a54213581ecc61ad80aa32ef30d851630d4 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 2 Jun 2022 00:24:28 +0300 Subject: [PATCH 64/74] =?UTF-8?q?=D0=A3=D1=81=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D1=88=D0=B5=D0=BD=D1=81=D1=82=D0=B2=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20mqtt=20Fixes=20#39=20+=20some=20minor=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/aux_ac/aux_ac.h | 24 +++++++++++++++++++++++- components/aux_ac/climate.py | 19 +++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index a58364f..b0e7b4d 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -12,6 +12,7 @@ #include "esphome/components/uart/uart.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" #include "esphome/core/helpers.h" // весь функционал сохранения пресетов прячу под дефайн @@ -1791,6 +1792,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { esphome::binary_sensor::BinarySensor *sensor_display_ = nullptr; // бинарный сенсор состония разморозки esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; + // текстовый сенсор, отображающий текущий режим работы сплита + esphome::text_sensor::TextSensor *sensor_state_reporter_ = nullptr; // загружает на выполнение последовательность команд на включение/выключение табло с температурой @@ -1907,6 +1910,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { void set_defrost_state(binary_sensor::BinarySensor *defrost_state_sensor) { sensor_defrost_ = defrost_state_sensor; } 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; } + void set_state_reporter_sensor(text_sensor::TextSensor *state_reporter_sensor) { sensor_state_reporter_ = state_reporter_sensor; } bool get_hw_initialized(){ return _hw_initialized; }; bool get_has_connection(){ return _has_connection; }; @@ -2209,6 +2213,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*********************************************************************/ /*************************** PUBLISH STATE ***************************/ /*********************************************************************/ + this->publish_all_states(); + } + + // публикуем все состояния сенсоров и сплита + void publish_all_states(){ this->publish_state(); // температура в комнате if (sensor_indoor_temperature_ != nullptr) @@ -2232,6 +2241,19 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (sensor_defrost_ != nullptr) sensor_defrost_->publish_state(_current_ac_state.defrost); + // сенсор состояния сплита + if (sensor_state_reporter_ != nullptr) { + std::string state_str = ""; + if (this->preset == climate::CLIMATE_PRESET_SLEEP) { + state_str += "SLEEP"; + } else if (this->custom_preset.has_value() && this->custom_preset.value().length() > 0) { + state_str += this->custom_preset.value().c_str(); + } else { + state_str += "NONE"; + } + sensor_state_reporter_->publish_state(state_str.c_str()); + } + // состояние дисплея if (sensor_display_ != nullptr) { switch (_current_ac_state.display) { @@ -2778,7 +2800,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (hasCommand) { commandSequence(&cmd); - this->publish_state(); // Publish updated state + this->publish_all_states(); // Publish updated state #if defined(PRESETS_SAVING) // флаг отправки новой команды, для процедуры сохранения пресетов, если есть настройка diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 6096ebb..1bc846a 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -1,7 +1,7 @@ import logging import esphome.config_validation as cv import esphome.codegen as cg -from esphome.components import climate, uart, sensor, binary_sensor +from esphome.components import climate, uart, sensor, binary_sensor, text_sensor from esphome import automation from esphome.automation import maybe_simple_id from esphome.const import ( @@ -37,7 +37,7 @@ _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@GrKoR"] DEPENDENCIES = ["climate", "uart"] -AUTO_LOAD = ["sensor", "binary_sensor"] +AUTO_LOAD = ["sensor", "binary_sensor", "text_sensor"] CONF_SHOW_ACTION = 'show_action' CONF_INDOOR_TEMPERATURE = 'indoor_temperature' @@ -55,6 +55,8 @@ CONF_DEFROST_STATE = 'defrost_state' ICON_DEFROST = "mdi:snowflake-melt" CONF_DISPLAY_INVERTED = 'display_inverted' ICON_DISPLAY = "mdi:clock-digital" +CONF_STATE_REPORTER = "state_reporter" +ICON_STATE_REPORTER = "mdi:format-list-group" aux_ac_ns = cg.esphome_ns.namespace("aux_ac") @@ -203,6 +205,14 @@ CONFIG_SCHEMA = cv.All( } ), + cv.Optional(CONF_STATE_REPORTER): text_sensor.text_sensor_schema( + icon=ICON_STATE_REPORTER + ).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), @@ -266,6 +276,11 @@ async def to_code(config): sens = await sensor.new_sensor(conf) cg.add(var.set_invertor_power_sensor(sens)) + if CONF_STATE_REPORTER in config: + conf = config[CONF_STATE_REPORTER] + sens = await text_sensor.new_text_sensor(conf) + cg.add(var.set_state_reporter_sensor(sens)) + cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) cg.add(var.set_show_action(config[CONF_SHOW_ACTION])) cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED])) From 5a06f1bf151c30c6a0847c449e9dceacfe49a1cc Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 2 Jun 2022 00:28:53 +0300 Subject: [PATCH 65/74] example update --- examples/advanced/ac_common.yaml | 33 +++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/examples/advanced/ac_common.yaml b/examples/advanced/ac_common.yaml index e012151..705fa26 100644 --- a/examples/advanced/ac_common.yaml +++ b/examples/advanced/ac_common.yaml @@ -69,9 +69,37 @@ climate: id: ${devicename}_indoor_temp internal: false display_state: - name: $upper_devicename Display State + name: ${upper_devicename} Display State id: ${devicename}_display_state internal: false + outdoor_temperature: + name: ${upper_devicename} Outdoor Temperature + id: ${devicename}_outdoor_temp + internal: false + outbound_temperature: + name: ${upper_devicename} Colant Outbound Temperature + id: ${devicename}_outbound_temp + internal: false + inbound_temperature: + name: ${upper_devicename} Colant Inbound Temperature + id: ${devicename}_inbound_temp + internal: false + compressor_temperature: + name: ${upper_devicename} Compressor Temperature + id: ${devicename}_strange_temp + internal: false + defrost_state: + name: ${upper_devicename} Defrost State + id: ${devicename}_defrost_state + internal: false + invertor_power: + name: ${upper_devicename} Invertor Power + id: ${devicename}_invertor_power + internal: false + state_reporter: + name: ${upper_devicename} State Reporter + id: ${devicename}_state_reporter + internal: false visual: min_temperature: 16 max_temperature: 32 @@ -89,7 +117,6 @@ climate: - SLEEP custom_presets: - CLEAN - - FEEL - HEALTH - ANTIFUNGUS supported_swing_modes: @@ -109,7 +136,7 @@ sensor: switch: - platform: template - name: $upper_devicename Display + name: ${upper_devicename} Display lambda: |- if (id(${devicename}_display_state).state) { return true; From 262b3bf69a86a15f03cd3d40e82330c63b05197f Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 2 Jun 2022 01:08:33 +0300 Subject: [PATCH 66/74] state => preset --- components/aux_ac/aux_ac.h | 8 ++++---- components/aux_ac/climate.py | 14 +++++++------- examples/advanced/ac_common.yaml | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index b0e7b4d..2c17922 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -1793,7 +1793,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // бинарный сенсор состония разморозки esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; // текстовый сенсор, отображающий текущий режим работы сплита - esphome::text_sensor::TextSensor *sensor_state_reporter_ = nullptr; + esphome::text_sensor::TextSensor *sensor_preset_reporter_ = nullptr; // загружает на выполнение последовательность команд на включение/выключение табло с температурой @@ -1910,7 +1910,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { void set_defrost_state(binary_sensor::BinarySensor *defrost_state_sensor) { sensor_defrost_ = defrost_state_sensor; } 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; } - void set_state_reporter_sensor(text_sensor::TextSensor *state_reporter_sensor) { sensor_state_reporter_ = state_reporter_sensor; } + void set_preset_reporter_sensor(text_sensor::TextSensor *preset_reporter_sensor) { sensor_preset_reporter_ = preset_reporter_sensor; } bool get_hw_initialized(){ return _hw_initialized; }; bool get_has_connection(){ return _has_connection; }; @@ -2242,7 +2242,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { sensor_defrost_->publish_state(_current_ac_state.defrost); // сенсор состояния сплита - if (sensor_state_reporter_ != nullptr) { + if (sensor_preset_reporter_ != nullptr) { std::string state_str = ""; if (this->preset == climate::CLIMATE_PRESET_SLEEP) { state_str += "SLEEP"; @@ -2251,7 +2251,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } else { state_str += "NONE"; } - sensor_state_reporter_->publish_state(state_str.c_str()); + sensor_preset_reporter_->publish_state(state_str.c_str()); } // состояние дисплея diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 1bc846a..1a6e0cf 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -55,8 +55,8 @@ CONF_DEFROST_STATE = 'defrost_state' ICON_DEFROST = "mdi:snowflake-melt" CONF_DISPLAY_INVERTED = 'display_inverted' ICON_DISPLAY = "mdi:clock-digital" -CONF_STATE_REPORTER = "state_reporter" -ICON_STATE_REPORTER = "mdi:format-list-group" +CONF_PRESET_REPORTER = "preset_reporter" +ICON_PRESET_REPORTER = "mdi:format-list-group" aux_ac_ns = cg.esphome_ns.namespace("aux_ac") @@ -205,8 +205,8 @@ CONFIG_SCHEMA = cv.All( } ), - cv.Optional(CONF_STATE_REPORTER): text_sensor.text_sensor_schema( - icon=ICON_STATE_REPORTER + cv.Optional(CONF_PRESET_REPORTER): text_sensor.text_sensor_schema( + icon=ICON_PRESET_REPORTER ).extend( { cv.Optional(CONF_INTERNAL, default="true"): cv.boolean @@ -276,10 +276,10 @@ async def to_code(config): sens = await sensor.new_sensor(conf) cg.add(var.set_invertor_power_sensor(sens)) - if CONF_STATE_REPORTER in config: - conf = config[CONF_STATE_REPORTER] + if CONF_PRESET_REPORTER in config: + conf = config[CONF_PRESET_REPORTER] sens = await text_sensor.new_text_sensor(conf) - cg.add(var.set_state_reporter_sensor(sens)) + cg.add(var.set_preset_reporter_sensor(sens)) cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) cg.add(var.set_show_action(config[CONF_SHOW_ACTION])) diff --git a/examples/advanced/ac_common.yaml b/examples/advanced/ac_common.yaml index 705fa26..ca1d46e 100644 --- a/examples/advanced/ac_common.yaml +++ b/examples/advanced/ac_common.yaml @@ -96,9 +96,9 @@ climate: name: ${upper_devicename} Invertor Power id: ${devicename}_invertor_power internal: false - state_reporter: - name: ${upper_devicename} State Reporter - id: ${devicename}_state_reporter + preset_reporter: + name: ${upper_devicename} Preset Reporter + id: ${devicename}_preset_reporter internal: false visual: min_temperature: 16 From fc2fd99cec4f7f2125ffe9630c16432e89df77c7 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 2 Jun 2022 18:48:49 +0300 Subject: [PATCH 67/74] new actions: set vertical louver position --- components/aux_ac/automation.h | 91 ++++++++++++++++++ components/aux_ac/aux_ac.h | 46 ++++++++- components/aux_ac/climate.py | 73 +++++++++++--- tests/test-ext-esp32.yaml | 59 +++++++++--- tests/test-ext-for-engineer.yaml | 61 +++++++++--- tests/test-local-arflow-dir.yaml | 159 +++++++++++++++++++++++++++++++ tests/test-local.yaml | 58 ++++++++--- tests/test-minimal.yaml | 8 +- 8 files changed, 497 insertions(+), 58 deletions(-) create mode 100644 tests/test-local-arflow-dir.yaml diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index a62fb27..3af1b52 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -7,6 +7,7 @@ namespace esphome { namespace aux_ac { +// **************************************** DISPLAY ACTIONS **************************************** template class AirConDisplayOffAction : public Action { @@ -31,6 +32,96 @@ namespace aux_ac { AirCon *ac_; }; + + +// **************************************** VERTICAL LOUVER ACTIONS **************************************** + template + class AirConVLouverSwingAction : public Action + { + public: + explicit AirConVLouverSwingAction(AirCon *ac) : ac_(ac) {} + + void play(Ts... x) override { this->ac_->setVLouverSwingSequence(); } + + protected: + AirCon *ac_; + }; + + template + class AirConVLouverStopAction : public Action + { + public: + explicit AirConVLouverStopAction(AirCon *ac) : ac_(ac) {} + + void play(Ts... x) override { this->ac_->setVLouverStopSequence(); } + + protected: + AirCon *ac_; + }; + + template + class AirConVLouverTopAction : public Action + { + public: + explicit AirConVLouverTopAction(AirCon *ac) : ac_(ac) {} + + void play(Ts... x) override { this->ac_->setVLouverTopSequence(); } + + protected: + AirCon *ac_; + }; + + template + class AirConVLouverMiddleAboveAction : public Action + { + public: + explicit AirConVLouverMiddleAboveAction(AirCon *ac) : ac_(ac) {} + + void play(Ts... x) override { this->ac_->setVLouverMiddleAboveSequence(); } + + protected: + AirCon *ac_; + }; + + template + class AirConVLouverMiddleAction : public Action + { + public: + explicit AirConVLouverMiddleAction(AirCon *ac) : ac_(ac) {} + + void play(Ts... x) override { this->ac_->setVLouverMiddleSequence(); } + + protected: + AirCon *ac_; + }; + + template + class AirConVLouverMiddleBelowAction : public Action + { + public: + explicit AirConVLouverMiddleBelowAction(AirCon *ac) : ac_(ac) {} + + void play(Ts... x) override { this->ac_->setVLouverMiddleBelowSequence(); } + + protected: + AirCon *ac_; + }; + + template + class AirConVLouverBottomAction : public Action + { + public: + explicit AirConVLouverBottomAction(AirCon *ac) : ac_(ac) {} + + void play(Ts... x) override { this->ac_->setVLouverBottomSequence(); } + + protected: + AirCon *ac_; + }; + + + +// **************************************** SEND TEST PACKET ACTION **************************************** template class AirConSendTestPacketAction : public Action { diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 2c17922..ab0bca7 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -391,7 +391,16 @@ enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTO // Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но должна быть такая возможность на уровне железа. // TODO: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V #define AC_LOUVERV_MASK 0b00000111 -enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_UPDOWN = 0x00, AC_LOUVERV_OFF = 0x07, AC_LOUVERV_UNTOUCHED = 0xFF }; +enum ac_louver_V : uint8_t { + AC_LOUVERV_SWING_UPDOWN = 0x00, + AC_LOUVERV_SWING_TOP = 0x01, + AC_LOUVERV_SWING_MIDDLE_ABOVE = 0x02, + AC_LOUVERV_SWING_MIDDLE = 0x03, + AC_LOUVERV_SWING_MIDDLE_BELOW = 0x04, + AC_LOUVERV_SWING_BOTTOM = 0x05, + AC_LOUVERV_OFF = 0x07, + AC_LOUVERV_UNTOUCHED = 0xFF +}; // Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но должна быть такая возможность на уровне железа. // TODO: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H @@ -2945,12 +2954,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************************** set params request ***********************************************/ if (!_addSequenceFuncStep(&AirCon::sq_requestDoCommand, cmd)) { - _debugMsg(F("commandSequence: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugMsg(F("commandSequence: request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); return false; } /*************************************** set params control ***********************************************/ if (!_addSequenceFuncStep(&AirCon::sq_controlDoCommand)) { - _debugMsg(F("commandSequence: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugMsg(F("commandSequence: control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); return false; } /**************************************************************************************/ @@ -3070,6 +3079,37 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return true; } + // устанавливает жалюзи в нужное положение + bool setVLouverSequence(const ac_louver_V vLouver){ + // нет смысла в последовательности, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("setVLouverSequence: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + if (vLouver == AC_LOUVERV_UNTOUCHED) return false; // выходим, чтобы не тратить время + + if ((vLouver > AC_LOUVERV_OFF) || (vLouver == 0x06)) return false; // нет таких команд + + // формируем команду + ac_command_t cmd; + _clearCommand(&cmd); // не забываем очищать, а то будет мусор + cmd.louver.louver_v = vLouver; + // добавляем команду в последовательность + if (!commandSequence(&cmd)) return false; + + _debugMsg(F("setVLouverSequence: loaded (power = %02X)"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, vLouver); + return true; + } + + // установка жалюзи в определенные положения + bool setVLouverSwingSequence() { return setVLouverSequence(AC_LOUVERV_SWING_UPDOWN); } + bool setVLouverStopSequence() { return setVLouverSequence(AC_LOUVERV_OFF); } + bool setVLouverTopSequence() { return setVLouverSequence(AC_LOUVERV_SWING_TOP); } + bool setVLouverMiddleAboveSequence() { return setVLouverSequence(AC_LOUVERV_SWING_MIDDLE_ABOVE); } + bool setVLouverMiddleSequence() { return setVLouverSequence(AC_LOUVERV_SWING_MIDDLE); } + bool setVLouverMiddleBelowSequence() { return setVLouverSequence(AC_LOUVERV_SWING_MIDDLE_BELOW); } + bool setVLouverBottomSequence() { return setVLouverSequence(AC_LOUVERV_SWING_BOTTOM); } + void set_period(uint32_t ms) { this->_update_period = ms; } uint32_t get_period() { return this->_update_period; } diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 1a6e0cf..a54362e 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -65,6 +65,13 @@ Capabilities = aux_ac_ns.namespace("Constants") AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action) AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action) +AirConVLouverSwingAction = aux_ac_ns.class_("AirConVLouverSwingAction", automation.Action) +AirConVLouverStopAction = aux_ac_ns.class_("AirConVLouverStopAction", automation.Action) +AirConVLouverTopAction = aux_ac_ns.class_("AirConVLouverTopAction", automation.Action) +AirConVLouverMiddleAboveAction = aux_ac_ns.class_("AirConVLouverMiddleAboveAction", automation.Action) +AirConVLouverMiddleAction = aux_ac_ns.class_("AirConVLouverMiddleAction", automation.Action) +AirConVLouverMiddleBelowAction = aux_ac_ns.class_("AirConVLouverMiddleBelowAction", automation.Action) +AirConVLouverBottomAction = aux_ac_ns.class_("AirConVLouverBottomAction", automation.Action) AirConSendTestPacketAction = aux_ac_ns.class_("AirConSendTestPacketAction", automation.Action) ALLOWED_CLIMATE_MODES = { @@ -121,7 +128,6 @@ 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, @@ -188,32 +194,27 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, } ), - cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema( - icon=ICON_DISPLAY + icon=ICON_DISPLAY, ).extend( { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, } ), - cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema( - icon=ICON_DEFROST + icon=ICON_DEFROST, ).extend( { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, } ), - cv.Optional(CONF_PRESET_REPORTER): text_sensor.text_sensor_schema( - icon=ICON_PRESET_REPORTER + icon=ICON_PRESET_REPORTER, ).extend( { - cv.Optional(CONF_INTERNAL, default="true"): cv.boolean + 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), @@ -265,7 +266,7 @@ async def to_code(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) @@ -314,13 +315,48 @@ async def display_on_to_code(config, action_id, template_arg, args): return cg.new_Pvariable(action_id, template_arg, paren) -SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id( +VLOUVER_ACTION_SCHEMA = maybe_simple_id( { cv.Required(CONF_ID): cv.use_id(AirCon), - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), } ) +@automation.register_action("aux_ac.vlouver_stop", AirConVLouverStopAction, VLOUVER_ACTION_SCHEMA) +async def vlouver_stop_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.vlouver_swing", AirConVLouverSwingAction, VLOUVER_ACTION_SCHEMA) +async def vlouver_swing_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.vlouver_top", AirConVLouverTopAction, VLOUVER_ACTION_SCHEMA) +async def vlouver_top_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.vlouver_middle_above", AirConVLouverMiddleAboveAction, VLOUVER_ACTION_SCHEMA) +async def vlouver_middle_above_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.vlouver_middle", AirConVLouverMiddleAction, VLOUVER_ACTION_SCHEMA) +async def vlouver_middle_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.vlouver_middle_below", AirConVLouverMiddleBelowAction, VLOUVER_ACTION_SCHEMA) +async def vlouver_middle_below_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.vlouver_bottom", AirConVLouverBottomAction, VLOUVER_ACTION_SCHEMA) +async def vlouver_bottom_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) + # ********************************************************************************************************* # ВАЖНО! Только для инженеров! @@ -328,6 +364,13 @@ SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id( # кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает. # Вы действуете на свой страх и риск. # ********************************************************************************************************* +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), + } +) + @automation.register_action( "aux_ac.send_packet", AirConSendTestPacketAction, diff --git a/tests/test-ext-esp32.yaml b/tests/test-ext-esp32.yaml index 5b443fa..f8ba75a 100644 --- a/tests/test-ext-esp32.yaml +++ b/tests/test-ext-esp32.yaml @@ -3,9 +3,12 @@ external_components: components: [ aux_ac ] refresh: 0s - +substitutions: + devicename: test_aux_ac_ext_esp32 + upper_devicename: Test AUX + esphome: - name: test_aux_ac_ext_esp32 + name: $devicename platform: ESP32 board: nodemcu-32s @@ -51,21 +54,54 @@ sensor: climate: - platform: aux_ac - name: "AC Name" + name: $upper_devicename id: aux_id uart_id: ac_uart_bus - period: 7s # период опроса состояния сплита, по дефолту 7 сек - show_action: true # надо ли показывать текущий режим работы: при HEAT_COOL mode сплит может греть (HEAT), охлаждать (COOL) или бездействовать (IDLE) - indoor_temperature: # сенсор, показывающий температуру воздуха на внутреннем блоке кондиционера; имеет все те же параметры, как и любой сенсор ESPHome - name: AC Indoor Temperature - id: ac_indoor_temp - internal: true # сенсор установлен как внутренний по дефолту (не попадёт в Home Assistant) + period: 7s + show_action: true + display_inverted: true + indoor_temperature: + name: $upper_devicename Indoor Temperature + id: ${devicename}_indoor_temp + internal: false + display_state: + name: $upper_devicename Display State + id: ${devicename}_display_state + internal: false + outdoor_temperature: + name: $upper_devicename Outdoor Temperature + id: ${devicename}_outdoor_temp + internal: false + outbound_temperature: + name: $upper_devicename Colant Outbound Temperature + id: ${devicename}_outbound_temp + internal: false + inbound_temperature: + name: $upper_devicename Colant Inbound Temperature + id: ${devicename}_inbound_temp + internal: false + compressor_temperature: + name: $upper_devicename Compressor Temperature + id: ${devicename}_strange_temp + internal: false + defrost_state: + name: $upper_devicename Defrost State + id: ${devicename}_defrost_state + internal: false + invertor_power: + name: $upper_devicename Invertor Power + id: ${devicename}_invertor_power + internal: false + preset_reporter: + name: $upper_devicename Preset Reporter + id: ${devicename}_preset_reporter + internal: false visual: min_temperature: 16 max_temperature: 32 temperature_step: 0.5 supported_modes: - - HEAT_COOL # не AUTO, так как только нагревает и остужает. В доках на ESPHome говорится, что AUTO - это если у устройства есть календарь и какие-то установки по расписанию. + - HEAT_COOL - COOL - HEAT - DRY @@ -77,10 +113,9 @@ climate: - SLEEP custom_presets: - CLEAN - - FEEL - HEALTH - ANTIFUNGUS supported_swing_modes: - VERTICAL - HORIZONTAL - - BOTH \ No newline at end of file + - BOTH diff --git a/tests/test-ext-for-engineer.yaml b/tests/test-ext-for-engineer.yaml index 4561a52..e747569 100644 --- a/tests/test-ext-for-engineer.yaml +++ b/tests/test-ext-for-engineer.yaml @@ -3,8 +3,12 @@ external_components: components: [ aux_ac ] refresh: 0s +substitutions: + devicename: test_aux_ac_ext_engeneer + upper_devicename: Test AUX + esphome: - name: test_aux_ac_ext + name: $devicename platform: ESP8266 board: esp12e @@ -69,26 +73,54 @@ sensor: climate: - platform: aux_ac - name: "AC Name" + name: $upper_devicename id: aux_id uart_id: ac_uart_bus - period: 7s # период опроса состояния сплита, по дефолту 7 сек - show_action: true # надо ли показывать текущий режим работы: при HEAT_COOL mode сплит может греть (HEAT), охлаждать (COOL) или бездействовать (IDLE) - display_inverted: true # как отрабатывать вкл/выкл дисплея: у Rovex "1" выключает дисплей, у многих других "1" дисплей включает - indoor_temperature: # сенсор, показывающий температуру воздуха на внутреннем блоке кондиционера; имеет все те же параметры, как и любой сенсор ESPHome - name: AC Indoor Temperature - id: ac_indoor_temp - internal: false # сенсор установлен как внутренний по дефолту (не попадёт в Home Assistant) + period: 7s + show_action: true + display_inverted: true + indoor_temperature: + name: $upper_devicename Indoor Temperature + id: ${devicename}_indoor_temp + internal: false display_state: - name: AC Display State - id: ac_display_state - internal: false # сенсор установлен как внутренний по дефолту (не попадёт в Home Assistant) + name: $upper_devicename Display State + id: ${devicename}_display_state + internal: false + outdoor_temperature: + name: $upper_devicename Outdoor Temperature + id: ${devicename}_outdoor_temp + internal: false + outbound_temperature: + name: $upper_devicename Colant Outbound Temperature + id: ${devicename}_outbound_temp + internal: false + inbound_temperature: + name: $upper_devicename Colant Inbound Temperature + id: ${devicename}_inbound_temp + internal: false + compressor_temperature: + name: $upper_devicename Compressor Temperature + id: ${devicename}_strange_temp + internal: false + defrost_state: + name: $upper_devicename Defrost State + id: ${devicename}_defrost_state + internal: false + invertor_power: + name: $upper_devicename Invertor Power + id: ${devicename}_invertor_power + internal: false + preset_reporter: + name: $upper_devicename Preset Reporter + id: ${devicename}_preset_reporter + internal: false visual: min_temperature: 16 max_temperature: 32 temperature_step: 0.5 supported_modes: - - HEAT_COOL # не AUTO, так как только нагревает и остужает. В доках на ESPHome говорится, что AUTO - это если у устройства есть календарь и какие-то установки по расписанию. + - HEAT_COOL - COOL - HEAT - DRY @@ -100,7 +132,6 @@ climate: - SLEEP custom_presets: - CLEAN - - FEEL - HEALTH - ANTIFUNGUS supported_swing_modes: @@ -113,7 +144,7 @@ switch: - platform: template name: AC Display lambda: |- - if (id(ac_display_state).state) { + if (id(${devicename}_display_state).state) { return true; } else { return false; diff --git a/tests/test-local-arflow-dir.yaml b/tests/test-local-arflow-dir.yaml new file mode 100644 index 0000000..9487912 --- /dev/null +++ b/tests/test-local-arflow-dir.yaml @@ -0,0 +1,159 @@ +external_components: + - source: + type: local + path: ..\components + +substitutions: + devicename: test_local_airflow_dir + upper_devicename: Test AUX + +esphome: + name: $devicename + platform: ESP8266 + board: esp12e + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_pass + manual_ip: + static_ip: !secret wifi_ip + gateway: !secret wifi_gateway + subnet: !secret wifi_subnet + dns1: 8.8.8.8 + dns2: 1.1.1.1 + reboot_timeout: 0s + ap: + ssid: $upper_devicename Fallback Hotspot + password: !secret wifi_ap_pass + +logger: + level: DEBUG + baud_rate: 0 + +api: + password: !secret api_pass + reboot_timeout: 0s + +ota: + password: !secret ota_pass + +web_server: + port: 80 + +uart: + id: ac_uart_bus + tx_pin: GPIO1 + rx_pin: GPIO3 + baud_rate: 4800 + data_bits: 8 + parity: EVEN + stop_bits: 1 + + +sensor: + - platform: uptime + name: Uptime Sensor + + +climate: + - platform: aux_ac + name: $upper_devicename + id: aux_id + uart_id: ac_uart_bus + period: 7s + show_action: true + display_inverted: true + + +button: + - platform: template + name: ${upper_devicename} VLouver Stop + icon: "mdi:circle-small" + on_press: + - aux_ac.vlouver_stop: aux_id + + - platform: template + name: ${upper_devicename} VLouver Swing + icon: "mdi:pan-vertical" + on_press: + - aux_ac.vlouver_swing: aux_id + + - platform: template + name: ${upper_devicename} VLouver Top + icon: "mdi:pan-up" + on_press: + - aux_ac.vlouver_top: aux_id + + - platform: template + name: ${upper_devicename} VLouver Middle Above + icon: "mdi:pan-top-left" + on_press: + - aux_ac.vlouver_middle_above: aux_id + + - platform: template + name: ${upper_devicename} VLouver Middle + icon: "mdi:pan-left" + on_press: + - aux_ac.vlouver_middle: aux_id + + - platform: template + name: ${upper_devicename} VLouver Middle Below + icon: "mdi:pan-bottom-left" + on_press: + - aux_ac.vlouver_middle_below: aux_id + + - platform: template + name: ${upper_devicename} VLouver Bottom + icon: "mdi:pan-down" + on_press: + - aux_ac.vlouver_bottom: aux_id + + +number: + - platform: template + name: ${upper_devicename} Vertical Louver + id: ${devicename}_vlouver + icon: "mdi:circle-small" + mode: "slider" + min_value: 0 + max_value: 6 + step: 1 + set_action: + then: + - lambda: !lambda |- + auto icon = ""; + if (x == 6) x = 7; // делаем так, чтобы выключение отрабатывать корректно + + switch ( static_cast(x) ) { + case 0: // vertical swing + icon = "mdi:pan-vertical"; + break; + + case 1: // top position + icon = "mdi:pan-up"; + break; + + case 2: // middle above position + icon = "mdi:pan-top-left"; + break; + + case 3: // middle position + icon = "mdi:pan-left"; + break; + + case 4: // middle below position + icon = "mdi:pan-bottom-left"; + break; + + case 5: // bottom position + icon = "mdi:pan-down"; + break; + + case 7: // stop vertical louver + default: + icon = "mdi:circle-small"; + break; + } + + id(${devicename}_vlouver).set_icon(icon); + id(aux_id).setVLouverSequence( static_cast(x) ); diff --git a/tests/test-local.yaml b/tests/test-local.yaml index 04a71e4..216befb 100644 --- a/tests/test-local.yaml +++ b/tests/test-local.yaml @@ -3,8 +3,12 @@ external_components: type: local path: ..\components +substitutions: + devicename: test_local_airflow_dir + upper_devicename: Test AUX + esphome: - name: test_aux_ac_local + name: $devicename platform: ESP8266 board: esp12e @@ -51,21 +55,54 @@ sensor: climate: - platform: aux_ac - name: "AC Name" + name: $upper_devicename id: aux_id uart_id: ac_uart_bus - period: 7s # период опроса состояния сплита, по дефолту 7 сек - show_action: true # надо ли показывать текущий режим работы: при HEAT_COOL mode сплит может греть (HEAT), охлаждать (COOL) или бездействовать (IDLE) - indoor_temperature: # сенсор, показывающий температуру воздуха на внутреннем блоке кондиционера; имеет все те же параметры, как и любой сенсор ESPHome - name: AC Indoor Temperature - id: ac_indoor_temp - internal: true # сенсор установлен как внутренний по дефолту (не попадёт в Home Assistant) + period: 7s + show_action: true + display_inverted: true + indoor_temperature: + name: $upper_devicename Indoor Temperature + id: ${devicename}_indoor_temp + internal: false + display_state: + name: $upper_devicename Display State + id: ${devicename}_display_state + internal: false + outdoor_temperature: + name: $upper_devicename Outdoor Temperature + id: ${devicename}_outdoor_temp + internal: false + outbound_temperature: + name: $upper_devicename Colant Outbound Temperature + id: ${devicename}_outbound_temp + internal: false + inbound_temperature: + name: $upper_devicename Colant Inbound Temperature + id: ${devicename}_inbound_temp + internal: false + compressor_temperature: + name: $upper_devicename Compressor Temperature + id: ${devicename}_strange_temp + internal: false + defrost_state: + name: $upper_devicename Defrost State + id: ${devicename}_defrost_state + internal: false + invertor_power: + name: $upper_devicename Invertor Power + id: ${devicename}_invertor_power + internal: false + preset_reporter: + name: $upper_devicename Preset Reporter + id: ${devicename}_preset_reporter + internal: false visual: min_temperature: 16 max_temperature: 32 temperature_step: 0.5 supported_modes: - - HEAT_COOL # не AUTO, так как только нагревает и остужает. В доках на ESPHome говорится, что AUTO - это если у устройства есть календарь и какие-то установки по расписанию. + - HEAT_COOL - COOL - HEAT - DRY @@ -77,10 +114,9 @@ climate: - SLEEP custom_presets: - CLEAN - - FEEL - HEALTH - ANTIFUNGUS supported_swing_modes: - VERTICAL - HORIZONTAL - - BOTH \ No newline at end of file + - BOTH diff --git a/tests/test-minimal.yaml b/tests/test-minimal.yaml index 975e2ed..651ccca 100644 --- a/tests/test-minimal.yaml +++ b/tests/test-minimal.yaml @@ -6,8 +6,12 @@ external_components: #components: [ aux_ac ] #refresh: 0s +substitutions: + devicename: test_local_minimal + upper_devicename: Test AUX + esphome: - name: test_aux_ac_minimal + name: $devicename platform: ESP8266 board: esp12e @@ -41,4 +45,4 @@ uart: climate: - platform: aux_ac - name: "AC Name" \ No newline at end of file + name: $upper_devicename \ No newline at end of file From 0500eae87b372f3c4132b5b2dce24577b809ff20 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 2 Jun 2022 22:06:10 +0300 Subject: [PATCH 68/74] actions for louvers vertical position + docs --- README-EN.md | 123 +++++++++++++++++++++++++++++-- README.md | 119 ++++++++++++++++++++++++++++-- examples/advanced/ac_common.yaml | 59 +++++++++++++++ tests/test-local-arflow-dir.yaml | 34 --------- 4 files changed, 286 insertions(+), 49 deletions(-) diff --git a/README-EN.md b/README-EN.md index 3ec6ba5..ddb78a8 100644 --- a/README-EN.md +++ b/README-EN.md @@ -85,19 +85,48 @@ climate: uart_id: ac_uart_bus period: 7s show_action: true - display_inverted: false + display_inverted: true indoor_temperature: name: AC Indoor Temperature id: ac_indoor_temp - internal: true + accuracy_decimals: 1 + internal: false + outdoor_temperature: + name: AC Outdoor Temperature + id: ac_outdoor_temp + internal: false + outbound_temperature: + name: AC Colant Outbound Temperature + id: ac_outbound_temp + internal: false + inbound_temperature: + name: AC Colant Inbound Temperature + id: ac_inbound_temp + internal: false + compressor_temperature: + name: AC Compressor Temperature + id: ac_strange_temp + internal: false display_state: - name: AC Display - id: ac_display + name: AC Display State + id: ac_display_state + internal: false + defrost_state: + name: AC Defrost State + id: ac_defrost_state + internal: false + invertor_power: + name: AC Invertor Power + id: ac_invertor_power + internal: false + preset_reporter: + name: AC Preset Reporter + id: ac_preset_reporter internal: false visual: min_temperature: 16 max_temperature: 32 - temperature_step: 0.5 + temperature_step: 1 supported_modes: - HEAT_COOL - COOL @@ -111,7 +140,6 @@ climate: - SLEEP custom_presets: - CLEAN - - FEEL - HEALTH - ANTIFUNGUS supported_swing_modes: @@ -131,20 +159,28 @@ climate: - COOLING: AC is cooling the air. The same thing will be in HEAT or COOL modes, with the only difference of the list of actions (IDLE + HEATING or IDLE + COOLING). - **display_inverted** (*Optional*, boolean, default ``false``): It configures display driver logic level. As it turned out in the issue [#31](https://github.com/GrKoR/esphome_aux_ac_component/issues/31), different models of conditioners manage display different way. Rovex ACs powers off display by bit `1` in command packet and power it on by bit `0`. Many other conditioners do this vice versa. -- **indoor_temperature** (*Optional*): The information for the air temperature sensor +- **indoor_temperature** (*Optional*): Parameters of the room air temperature sensor. - **name** (**Required**, string): The name for the temperature sensor. - **id** (*Optional*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Set the ID of this sensor for use in lambdas. - **internal** (*Optional*, boolean): Mark this component as internal. Internal components will not be exposed to the frontend (like Home Assistant). As opposed to default [Sensor](https://esphome.io/components/sensor/index.html#base-sensor-configuration) behaviour this variable is **always true** except in cases where the user has set it directly. - All other options from [Sensor](https://esphome.io/components/sensor/index.html#base-sensor-configuration). +- **outdoor_temperature** (*Optional*): Parameters of the outdoor temperature sensor. Thay are the same as the **indoor_temperature** (see description above). +- **inbound_temperature** (*Optional*): Parameters of the coolant inbound temperature sensor. Thay are the same as the **indoor_temperature** (see description above). +- **outbound_temperature** (*Optional*): Parameters of the coolant outbound temperature sensor. Thay are the same as the **indoor_temperature** (see description above). +- **compressor_temperature** (*Optional*): Parameters of the compressor temperature sensor. Thay are the same as the **indoor_temperature** (see description above). - **display_state** (*Optional*): The information for the HVAC display state sensor (is display ON or OFF) - **name** (**Required**, string): The name for the display state sensor. - **id** (*Optional*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Set the ID of this sensor for use in lambdas. - **internal** (*Optional*, boolean): Mark this component as internal. Internal components will not be exposed to the frontend (like Home Assistant). As opposed to default [Binary Sensor](https://esphome.io/components/binary_sensor/index.html#base-binary-sensor-configuration) behaviour this variable is **always true** except in cases where the user has set it directly. - All other options from [Binary Sensor](https://esphome.io/components/binary_sensor/index.html#base-binary-sensor-configuration). +- **defrost_state** (*Optional*): The information for the HVAC defrost function state sensor (is it ON or OFF). All settings are the same as for the **display_state** (see description above). +- **invertor_power** (*Optional*): The information for the invertor power sensor. All settings are the same as for the **display_state** (see description above). +- **preset_reporter** (*Optional*): Parameters of text sensor with current preset. All settings are the same as for the **display_state** (see description above). +ESPHome Climate devices are not report their active presets (from **supported_presets** and **custom_presets** lists) to MQTT. In case you are using mqtt and want to receive information about active preset you should declare this sensor in your yaml. - **supported_modes** (*Optional*, list): List of supported modes. Possible values are: ``HEAT_COOL``, ``COOL``, ``HEAT``, ``DRY``, ``FAN_ONLY``. Please note: some manufacturers call AUTO mode instead of HEAT_COOL. Defaults to ``FAN_ONLY``. - **custom_fan_modes** (*Optional*, list): List of supported custom fan modes. Possible values are: ``MUTE``, ``TURBO``. No custom fan modes by default. - **supported_presets** (*Optional*, list): List of supported presets. Possible values are: ``SLEEP``. No presets by default. -- **custom_presets** (*Optional*, list): List of supported custom presets. Possible values are: ``CLEAN``, ``FEEL``, ``HEALTH``, ``ANTIFUNGUS``. Please note: presets ``FEEL``, ``HEALTH`` and ``ANTIFUNGUS`` have not been implemented yet. No custom presets by default. +- **custom_presets** (*Optional*, list): List of supported custom presets. Possible values are: ``CLEAN``, ``HEALTH``, ``ANTIFUNGUS``. No custom presets by default. - **supported_swing_modes** (*Optional*, list): List of supported swing modes. Possible values are: ``VERTICAL``, ``HORIZONTAL``, ``BOTH``. No swing modes by default. - All other options from [Climate](https://esphome.io/components/climate/index.html#base-climate-configuration). @@ -169,6 +205,77 @@ on_...: ``` - **aux_id** (**Requared**, string): ID of `aux_ac` component. +### ``aux_ac.vlouver_stop`` ### +This action stops vertical swing of louvers. + +```yaml +on_...: + then: + - aux_ac.vlouver_stop: aux_id +``` +- **aux_id** (**Requared**, string): ID of `aux_ac` component. + +### ``aux_ac.vlouver_swing`` ### +This action starts vertical swing of louvers. + +```yaml +on_...: + then: + - aux_ac.vlouver_swing: aux_id +``` +- **aux_id** (**Requared**, string): ID of `aux_ac` component. + +### ``aux_ac.vlouver_top`` ### +This action moves HVAC louvers to the topmost position. + +```yaml +on_...: + then: + - aux_ac.vlouver_top: aux_id +``` +- **aux_id** (**Requared**, string): ID of `aux_ac` component. + +### ``aux_ac.vlouver_middle_above`` ### +This action moves HVAC louvers to the position one step under the topmost. + +```yaml +on_...: + then: + - aux_ac.vlouver_middle_above: aux_id +``` +- **aux_id** (**Requared**, string): ID of `aux_ac` component. + +### ``aux_ac.vlouver_middle`` ### +This action moves HVAC louvers to the middle position. + +```yaml +on_...: + then: + - aux_ac.vlouver_middle: aux_id +``` +- **aux_id** (**Requared**, string): ID of `aux_ac` component. + +### ``aux_ac.vlouver_middle_below`` ### +This action moves HVAC louvers to the position one step under the middle position. + +```yaml +on_...: + then: + - aux_ac.vlouver_middle_below: aux_id +``` +- **aux_id** (**Requared**, string): ID of `aux_ac` component. + +### ``aux_ac.vlouver_bottom`` ### +This action moves HVAC louvers to the lowest position. + +```yaml +on_...: + then: + - aux_ac.vlouver_bottom: aux_id +``` +- **aux_id** (**Requared**, string): ID of `aux_ac` component. + + ## Simple example ## The source code of this example is located in the [aux_ac_simple.yaml](https://github.com/GrKoR/esphome_aux_ac_component/blob/master/examples/simple/aux_ac_simple.yaml) file. diff --git a/README.md b/README.md index e9daefd..899632a 100644 --- a/README.md +++ b/README.md @@ -87,19 +87,48 @@ climate: uart_id: ac_uart_bus period: 7s show_action: true - display_inverted: false + display_inverted: true indoor_temperature: name: AC Indoor Temperature id: ac_indoor_temp - internal: true + accuracy_decimals: 1 + internal: false + outdoor_temperature: + name: AC Outdoor Temperature + id: ac_outdoor_temp + internal: false + outbound_temperature: + name: AC Colant Outbound Temperature + id: ac_outbound_temp + internal: false + inbound_temperature: + name: AC Colant Inbound Temperature + id: ac_inbound_temp + internal: false + compressor_temperature: + name: AC Compressor Temperature + id: ac_strange_temp + internal: false display_state: - name: AC Display - id: ac_display + name: AC Display State + id: ac_display_state + internal: false + defrost_state: + name: AC Defrost State + id: ac_defrost_state + internal: false + invertor_power: + name: AC Invertor Power + id: ac_invertor_power + internal: false + preset_reporter: + name: AC Preset Reporter + id: ac_preset_reporter internal: false visual: min_temperature: 16 max_temperature: 32 - temperature_step: 0.5 + temperature_step: 1 supported_modes: - HEAT_COOL - COOL @@ -113,7 +142,6 @@ climate: - SLEEP custom_presets: - CLEAN - - FEEL - HEALTH - ANTIFUNGUS supported_swing_modes: @@ -138,15 +166,23 @@ climate: - **id** (*Опциональный*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Можно указать свой ID для датчика для использования в лямбдах. - **internal** (*Опциональный*, логическое): Пометить данный датчик как внутренний. Внутренний датчик не будет передаваться во фронтэнд (такой как Home Assistant). В противоположность стандартному поведению [сенсоров](https://esphome.io/components/sensor/index.html#base-sensor-configuration) этот параметр для датчика в кондиционере **всегда выставлен в true** за исключением случаев, когда пользователь не установил его в `false`. То есть по умолчанию значение сенсора не будет передаваться во фронтенд даже если указано `name` для сенсора. - Все остальные параметры [сенсора](https://esphome.io/components/sensor/index.html#base-sensor-configuration) ESPHome. +- **outdoor_temperature** (*Опциональный*): Параметры создаваемого датчика уличной температуры воздуха, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_temperature** (см. выше). +- **inbound_temperature** (*Опциональный*): Параметры создаваемого датчика температуры на подаче теплоносителя, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_temperature** (см. выше). +- **outbound_temperature** (*Опциональный*): Параметры создаваемого датчика температуры на обратке теплоносителя, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_temperature** (см. выше). +- **compressor_temperature** (*Опциональный*): Параметры создаваемого датчика температуры компрессора, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_temperature** (см. выше). - **display_state** (*Опциональный*): Параметры создаваемого датчика дисплея (включен или выключен), если такой датчик нужен. - **name** (**Обязательный**, строка): Имя датчика дисплея. - **id** (*Опциональный*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Можно указать свой ID для датчика для использования в лямбдах. - **internal** (*Опциональный*, логическое): Пометить данный датчик как внутренний. Внутренний датчик не будет передаваться во фронтэнд (такой как Home Assistant). В противоположность стандартному поведению [бинарных сенсоров](https://esphome.io/components/binary_sensor/index.html#base-binary-sensor-configuration) этот параметр для датчика в кондиционере **всегда выставлен в true** за исключением случаев, когда пользователь не установил его в `false`. То есть по умолчанию значение сенсора не будет передаваться во фронтенд даже если указано `name` для сенсора. - Все остальные параметры [бинарного сенсора](https://esphome.io/components/binary_sensor/index.html#base-binary-sensor-configuration) ESPHome. +- **defrost_state** (*Опциональный*): Параметры создаваемого датчика состояния разморозки (включена или выключена), если такой датчик нужен. Параметры аналогичны датчику дисплея **display_state**. +- **invertor_power** (*Опциональный*): Параметры создаваемого датчика мощности инвертора, если такой датчик нужен. Параметры аналогичны датчику дисплея **display_state**. +- **preset_reporter** (*Опциональный*): Параметры создаваемого текстового датчика текущего активного пресета. Параметры аналогичны датчику дисплея **display_state**. +Климатические устройства ESPHome не отправляют по MQTT активный пресет (см. **supported_presets** и **custom_presets**), в котором работает устройство. Если вы используете MQTT и хотите получать информацию о пресетах, то пропишите этот датчик в конфигурации. - **supported_modes** (*Опциональный*, список): Список поддерживаемых режимов работы. Возможные значения: ``HEAT_COOL``, ``COOL``, ``HEAT``, ``DRY``, ``FAN_ONLY``. Обратите внимание: некоторые производители кондиционеров указывают на пульте режим AUTO, хотя по факту этот режим не работает по расписанию и только лишь поддерживает целевую температуру. Такой режим в ESPHome называется HEAT_COOL. По умолчанию список содержит только значение ``FAN_ONLY``. - **custom_fan_modes** (*Опциональный*, список): Список поддерживаемых дополнительных режимов вентилятора. Возможные значения: ``MUTE``, ``TURBO``. По умолчанию никакие дополнительные режимы не установлены. - **supported_presets** (*Опциональный*, список): Список поддерживаемых базовых функций кондиционера. Возможные значения: ``SLEEP``. По умолчанию никакие базовые функции не установлены. -- **custom_presets** (*Опциональный*, список): Список поддерживаемых дополнительных функций кондиционера. Возможные значения: ``CLEAN``, ``FEEL``, ``HEALTH``, ``ANTIFUNGUS``. Обратите внимание: функции ``FEEL``, ``HEALTH`` и ``ANTIFUNGUS`` пока не в компоненте реализованы. По умолчанию никакие дополнительные функции не установлены. +- **custom_presets** (*Опциональный*, список): Список поддерживаемых дополнительных функций кондиционера. Возможные значения: ``CLEAN``, ``HEALTH``, ``ANTIFUNGUS``. По умолчанию никакие дополнительные функции не установлены. - **supported_swing_modes** (*Опциональный*, список): Список поддерживаемых режимов качания шторки. Возможные значения: ``VERTICAL``, ``HORIZONTAL``, ``BOTH``. По умолчанию устанавливается, что качание шторки кондиционером не поддерживается. - Все остальные параметры [климатического устройства](https://esphome.io/components/climate/index.html#base-climate-configuration) ESPHome. @@ -171,6 +207,75 @@ on_...: ``` - **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. +### ``aux_ac.vlouver_stop`` ### +Остановка вертикального движения жалюзи кондиционера. Если жалюзи качались в вертикальном направлении, то можно их остановить в нужном положении. + +```yaml +on_...: + then: + - aux_ac.vlouver_stop: aux_id +``` +- **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. + +### ``aux_ac.vlouver_swing`` ### +Включение вертикального качания жалюзи кондиционера. + +```yaml +on_...: + then: + - aux_ac.vlouver_swing: aux_id +``` +- **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. + +### ``aux_ac.vlouver_top`` ### +Установка жалюзи в самое верхнее положение. + +```yaml +on_...: + then: + - aux_ac.vlouver_top: aux_id +``` +- **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. + +### ``aux_ac.vlouver_middle_above`` ### +Установка жалюзи во второе сверху положение. Это положение между верхним и средним. + +```yaml +on_...: + then: + - aux_ac.vlouver_middle_above: aux_id +``` +- **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. + +### ``aux_ac.vlouver_middle`` ### +Установка жалюзи в среднее положение. + +```yaml +on_...: + then: + - aux_ac.vlouver_middle: aux_id +``` +- **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. + +### ``aux_ac.vlouver_middle_below`` ### +Установка жалюзи в положение ниже среднего. + +```yaml +on_...: + then: + - aux_ac.vlouver_middle_below: aux_id +``` +- **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. + +### ``aux_ac.vlouver_bottom`` ### +Установка жалюзи в самое нижнее положение. + +```yaml +on_...: + then: + - aux_ac.vlouver_bottom: aux_id +``` +- **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. diff --git a/examples/advanced/ac_common.yaml b/examples/advanced/ac_common.yaml index ca1d46e..a6e9f57 100644 --- a/examples/advanced/ac_common.yaml +++ b/examples/advanced/ac_common.yaml @@ -147,3 +147,62 @@ switch: - aux_ac.display_on: aux_id turn_off_action: - aux_ac.display_off: aux_id + +button: + - platform: template + name: ${upper_devicename} VLouver Stop + icon: "mdi:circle-small" + on_press: + - aux_ac.vlouver_stop: aux_id + + - platform: template + name: ${upper_devicename} VLouver Swing + icon: "mdi:pan-vertical" + on_press: + - aux_ac.vlouver_swing: aux_id + + - platform: template + name: ${upper_devicename} VLouver Top + icon: "mdi:pan-up" + on_press: + - aux_ac.vlouver_top: aux_id + + - platform: template + name: ${upper_devicename} VLouver Middle Above + icon: "mdi:pan-top-left" + on_press: + - aux_ac.vlouver_middle_above: aux_id + + - platform: template + name: ${upper_devicename} VLouver Middle + icon: "mdi:pan-left" + on_press: + - aux_ac.vlouver_middle: aux_id + + - platform: template + name: ${upper_devicename} VLouver Middle Below + icon: "mdi:pan-bottom-left" + on_press: + - aux_ac.vlouver_middle_below: aux_id + + - platform: template + name: ${upper_devicename} VLouver Bottom + icon: "mdi:pan-down" + on_press: + - aux_ac.vlouver_bottom: aux_id + + +number: + - platform: template + name: ${upper_devicename} Vertical Louver + id: ${devicename}_vlouver + icon: "mdi:circle-small" + mode: "slider" + min_value: 0 + max_value: 6 + step: 1 + set_action: + then: + - lambda: !lambda |- + if (x == 6) x = 7; // 6 is incorrect louver position, 7 is stopped louver + id(aux_id).setVLouverSequence( static_cast(x) ); \ No newline at end of file diff --git a/tests/test-local-arflow-dir.yaml b/tests/test-local-arflow-dir.yaml index 9487912..3025e87 100644 --- a/tests/test-local-arflow-dir.yaml +++ b/tests/test-local-arflow-dir.yaml @@ -121,39 +121,5 @@ number: set_action: then: - lambda: !lambda |- - auto icon = ""; if (x == 6) x = 7; // делаем так, чтобы выключение отрабатывать корректно - - switch ( static_cast(x) ) { - case 0: // vertical swing - icon = "mdi:pan-vertical"; - break; - - case 1: // top position - icon = "mdi:pan-up"; - break; - - case 2: // middle above position - icon = "mdi:pan-top-left"; - break; - - case 3: // middle position - icon = "mdi:pan-left"; - break; - - case 4: // middle below position - icon = "mdi:pan-bottom-left"; - break; - - case 5: // bottom position - icon = "mdi:pan-down"; - break; - - case 7: // stop vertical louver - default: - icon = "mdi:circle-small"; - break; - } - - id(${devicename}_vlouver).set_icon(icon); id(aux_id).setVLouverSequence( static_cast(x) ); From 62615cae3442dc6a6090849ee1e5023f35002758 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Wed, 8 Jun 2022 23:46:02 +0300 Subject: [PATCH 69/74] Fix AUTO (HEAT_COOL) mode: many ACs can change target T in this mode. --- components/aux_ac/aux_ac.h | 11 +++- tests/ac_send_packet_for_engineer.py | 82 +++++++++------------------- 2 files changed, 36 insertions(+), 57 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index ab0bca7..9af02e7 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -2089,7 +2089,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** MUTE FAN MODE ***************************/ // MUTE работает в режиме FAN. В режимах HEAT, COOL, HEAT_COOL не работает. DRY не проверял. - // TODO: проверку на это несовместимые режимы пока выпилили, т.к. нет уверенности, что это поведение одинаково для всех + // TODO: проверку на несовместимые режимы пока выпилили, т.к. нет уверенности, что это поведение одинаково для всех switch (_current_ac_state.fanMute) { case AC_FANMUTE_ON: //if (_current_ac_state.mode == AC_MODE_FAN) { @@ -2208,8 +2208,15 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if(_current_ac_state.mode == AC_MODE_FAN || _current_ac_state.power == AC_POWER_OFF){ // в режиме вентилятора и в выключенном состоянии будем показывать текущую температуру this->target_temperature = _current_ac_state.temp_ambient; + /* + * принудительная установка целевой температуры для режима AUTO (HEAT_COOL) осознанно выпилена. + * как выяснилось, многие сплиты умеют задавать целевую температуру в этом режиме + * но не все. Кто не умеет, возвращает правильную температуру после установки режима. + * Так что проверка в коде не требуется + */ /* } else if (_current_ac_state.mode == AC_MODE_AUTO ){ this->target_temperature = 25; // в AUTO зашита температура 25 градусов + */ } else { this->target_temperature = _current_ac_state.temp_target; } @@ -2501,9 +2508,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { load_preset(&cmd, POS_MODE_AUTO); #endif + /* принудительная установка температуры в этом режиме осознанно выпилена cmd.temp_target = 25; // зависимость от режима HEAT_COOL cmd.temp_target_matter = true; cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от режима HEAT_COOL + */ this->mode = mode; break; diff --git a/tests/ac_send_packet_for_engineer.py b/tests/ac_send_packet_for_engineer.py index afd4380..ce26215 100644 --- a/tests/ac_send_packet_for_engineer.py +++ b/tests/ac_send_packet_for_engineer.py @@ -21,8 +21,6 @@ def createParser (): parent_group.add_argument ('--help', '-h', action='help', help='show this help message and exit') parent_group.add_argument ('-i', '--ip', nargs=1, required=True, help='IP address of the esphome device') parent_group.add_argument ('-p', '--pwd', nargs=1, required=True, help='native API password for the esphome device') - parent_group.add_argument ('-n', '--name', nargs=1, default=['noname'], help='name of this devace in the log') - parent_group.add_argument ('-l', '--logfile', nargs=1, default=['%4d-%02d-%02d %02d-%02d-%02d log.csv' % time.localtime()[0:6]], help='log file name') return parser async def main(): @@ -36,51 +34,6 @@ async def main(): print(api.api_version) - def log_AC(isAirConLog): - parts = re.search("(\d{10}): (\[\S{2}\]) \[([0-9A-F ]{23})\]\s?((?:[0-9A-F]{2}\s*)*) \[([0-9A-F ]{5})\]", isAirConLog.group(1)) - packString = '\n' + namespace.name[0] - packString += ";" + "%4d-%02d-%02d %02d:%02d:%02d" % time.localtime()[0:6] - """millis of message""" - packString += ";" + parts.group(1) - """direction""" - packString += ";" + parts.group(2) - """header""" - packString += ";" + ';'.join(parts.group(3).split(" ")) - """body (may be void)""" - if len(parts.group(4)) > 0: - packString += ";" + ';'.join(parts.group(4).split(" ")) - """crc""" - packString += ";" + ';'.join(parts.group(5).split(" ")) - print(packString) - with open(namespace.logfile[0], 'a+') as file: - file.write( packString ) - - def log_Dallas(isDallasLog): - parts = re.search("'([\w ]+)': Got Temperature=([-]?\d+\.\d+)°C", isDallasLog.group(1)) - packString = '\n' + parts.group(1) - packString += ";" + "%4d-%02d-%02d %02d:%02d:%02d" % time.localtime()[0:6] - """millis of message always empty""" - packString += ";" - """direction""" - packString += ";[<=]" - """additional data flag""" - packString += ";AA" - """dallas temperature""" - packString += ";" + parts.group(2) - print(packString) - with open(namespace.logfile[0], 'a+') as file: - file.write( packString ) - - def log_callback(log): - """Print the log for AirCon""" - isAirConLog = re.search("\[AirCon:\d+\]: (.+\])", log.message.decode('utf-8')) - if isAirConLog: - log_AC(isAirConLog) - if namespace.logdallas: - isDallasLog = re.search("\[dallas.sensor:\d+\]: (.+C)", log.message.decode('utf-8')) - if isDallasLog: - log_Dallas(isDallasLog) - async def display_off(): await api.execute_service( service, @@ -104,7 +57,7 @@ async def main(): service, data={ # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x19, 0x60, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00], + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x87, 0xE0, 0x2F, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00], } ) @@ -113,7 +66,25 @@ async def main(): service, data={ # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x19, 0x60, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x87, 0xE0, 0x2F, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + } + ) + + async def ac_get11_01(): + await api.execute_service( + service, + data={ + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x02, 0x00, 0x11, 0x01], + } + ) + + async def ac_get11_00(): + await api.execute_service( + service, + data={ + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x02, 0x00, 0x11, 0x00], } ) @@ -135,12 +106,6 @@ async def main(): } ) - # Subscribe to the log - # await api.subscribe_logs(log_callback, LOG_LEVEL_DEBUG) - - # print(await api.device_info()) - # print(f"%s" % (await api.list_entities_services(),)) - # key надо искать в выводе list_entities_services service = aioesphomeapi.UserService( name="send_data", @@ -150,6 +115,11 @@ async def main(): ], ) + time.sleep(7) + await ac_get11_00() + time.sleep(7) + await ac_get11_01() + #await ac_set_vlouver( 0b10010000 ) # swing on #await ac_set_vlouver( 0b10010111 ) # swing off #await ac_set_vlouver( 0b10010001 ) # 1 @@ -204,7 +174,7 @@ async def main(): parser = createParser() namespace = parser.parse_args() -print(namespace.name[0], namespace.ip[0]) +print("IP: ", namespace.ip[0]) loop = asyncio.get_event_loop() From 66a3e864692bcdc95f6da459cce269282a305131 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Wed, 8 Jun 2022 23:50:02 +0300 Subject: [PATCH 70/74] test file renamed --- tests/{test-local-arflow-dir.yaml => test-local-airflow-dir.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test-local-arflow-dir.yaml => test-local-airflow-dir.yaml} (100%) diff --git a/tests/test-local-arflow-dir.yaml b/tests/test-local-airflow-dir.yaml similarity index 100% rename from tests/test-local-arflow-dir.yaml rename to tests/test-local-airflow-dir.yaml From 3d5efb3d97ea0348c96cf2f3b54d9543f51479ff Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 9 Jun 2022 00:02:31 +0300 Subject: [PATCH 71/74] clear some TODOs --- components/aux_ac/aux_ac.h | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 9af02e7..d318ad0 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -95,10 +95,10 @@ enum acsm_state : uint8_t { // структура пакета описана тут: // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_structure #define AC_HEADER_SIZE 8 -//#define AC_MAX_BODY_SIZE 24 // TODO: нигде не используется, можно удалить + // стандартно длина пакета не более 34 байт // но встретилось исключение Royal Clima (как минимум, модель CO-D xxHNI) - у них 35 байт -// пожтому буффер увеличен +// поэтому буффер увеличен #define AC_BUFFER_SIZE 35 /** @@ -289,7 +289,6 @@ struct packet_big_info_body_t { // байт 17 тела (байт 25 пакета) // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b25 - //TODO: в описание протокола требуется пояснение от Brokly uint8_t zero11; // не расшифрован, подробнее в описании. // байт 18 тела (байт 26 пакета) @@ -298,7 +297,6 @@ struct packet_big_info_body_t { // байт 19 тела (байт 27 пакета) // https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b27 - //TODO: в описание протокола требуется пояснение от Brokly uint8_t zero13; // не расшифрован, подробнее в описании. // байт 20 тела (байт 28 пакета) @@ -389,7 +387,6 @@ enum ac_mode : uint8_t { AC_MODE_AUTO = 0x00, AC_MODE_COOL = 0x20, AC_MODE_DRY = enum ac_sleep : uint8_t { AC_SLEEP_OFF = 0x00, AC_SLEEP_ON = 0x04, AC_SLEEP_UNTOUCHED = 0xFF }; // Вертикальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но должна быть такая возможность на уровне железа. -// TODO: надо протестировать значения 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 для ac_louver_V #define AC_LOUVERV_MASK 0b00000111 enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_UPDOWN = 0x00, @@ -398,12 +395,13 @@ enum ac_louver_V : uint8_t { AC_LOUVERV_SWING_MIDDLE = 0x03, AC_LOUVERV_SWING_MIDDLE_BELOW = 0x04, AC_LOUVERV_SWING_BOTTOM = 0x05, + // 0x06 ничего не даёт, протестировано AC_LOUVERV_OFF = 0x07, AC_LOUVERV_UNTOUCHED = 0xFF }; // Горизонтальные жалюзи. В протоколе зашита возможность двигать ими по всякому, но должна быть такая возможность на уровне железа. -// TODO: надо протестировать значения 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 для ac_louver_H +// горизонтальные жалюзи выставлять в определенное положение не вышло, протестировано. #define AC_LOUVERH_MASK 0b11100000 enum ac_louver_H : uint8_t { AC_LOUVERH_SWING_LEFTRIGHT = 0x00, AC_LOUVERH_OFF = 0xE0, AC_LOUVERH_UNTOUCHED = 0xFF }; @@ -449,8 +447,8 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ */ //***************************************************************************** -// TODO: presets блок кода под сохранение пресетов. После решения - убрать -// данные структур содержат настройку, специально вынес в макрос +// структура для сохранения настроек, специально вынесено в макрос, чтобы использовать в нескольких местах +// сделано Brokly для того, чтобы поведение wifi-модуля походило на ИК-пульт (для каждого режима сохранялись свои настройки температуры и прочего) #define AC_COMMAND_BASE float temp_target;\ ac_power power;\ ac_clean clean;\ @@ -1900,7 +1898,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _startupSequenceComlete = false; // первоначальная инициализация - // TODO: вроде бы введено Brokly, но было в setup(). Надо узнать, зачем оно нам вообще? this->preset = climate::CLIMATE_PRESET_NONE; this->custom_preset = (std::string)""; this->mode = climate::CLIMATE_MODE_OFF; @@ -2089,7 +2086,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************** MUTE FAN MODE ***************************/ // MUTE работает в режиме FAN. В режимах HEAT, COOL, HEAT_COOL не работает. DRY не проверял. - // TODO: проверку на несовместимые режимы пока выпилили, т.к. нет уверенности, что это поведение одинаково для всех + // проверку на несовместимые режимы выпилили, т.к. нет уверенности, что это поведение одинаково для всех switch (_current_ac_state.fanMute) { case AC_FANMUTE_ON: //if (_current_ac_state.mode == AC_MODE_FAN) { @@ -2762,7 +2759,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; this->custom_preset = custom_preset; - //_debugMsg(F("ANTIFUNGUS preset has not been implemented yet."), ESPHOME_LOG_LEVEL_INFO, __LINE__); } } From f53b91cd18671d02f2cf532af3c1797e998ff6dc Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 9 Jun 2022 00:13:37 +0300 Subject: [PATCH 72/74] added TODO for actions --- components/aux_ac/aux_ac.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index d318ad0..d78ec54 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -1930,6 +1930,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { void stateChanged(){ _debugMsg(F("State changed, let's publish it."), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + // TODO: сейчас экшины рассчётные и могут не отражать реального положения дел. + // В протоколе расшифрованы байты, позволяющие выводить реальный экшн. Требуется исправить. if(_is_invertor){ // анализ режима для инвертора, точнее потому что использует показания мощности инвертора static uint32_t timerInv = 0; if(_current_ac_state.invertor_power == 0){ // инвертор выключен From 2805b68fcbe9b66a2ddcfea8378f67d2d1c69618 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 9 Jun 2022 00:21:37 +0300 Subject: [PATCH 73/74] python: delete some comments --- components/aux_ac/climate.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index a54362e..651c720 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -15,16 +15,12 @@ from esphome.const import ( 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 ( From b6e9e37b2ef84ce0a30ebddbafe69c378894be37 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 9 Jun 2022 00:25:10 +0300 Subject: [PATCH 74/74] readme updates --- README-EN.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README-EN.md b/README-EN.md index ddb78a8..53434a5 100644 --- a/README-EN.md +++ b/README-EN.md @@ -85,7 +85,7 @@ climate: uart_id: ac_uart_bus period: 7s show_action: true - display_inverted: true + display_inverted: false indoor_temperature: name: AC Indoor Temperature id: ac_indoor_temp diff --git a/README.md b/README.md index 899632a..91428d3 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ climate: uart_id: ac_uart_bus period: 7s show_action: true - display_inverted: true + display_inverted: false indoor_temperature: name: AC Indoor Temperature id: ac_indoor_temp