From bea59ada1c4d69b875b392c71a9b9b2e4e3ce592 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Thu, 26 May 2022 19:36:38 +0300 Subject: [PATCH] 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();