From d0abbade1ecae715a0729bdcab4e65b8efb7e30c Mon Sep 17 00:00:00 2001 From: GrKoR Date: Sat, 7 May 2022 16:12:07 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B1=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B?= =?UTF-8?q?=20=D0=B4=D0=B8=D1=81=D0=BF=D0=BB=D0=B5=D1=8F=20Fixes=20#31?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-EN.md | 5 ++-- README.md | 5 ++-- components/aux_ac/aux_ac.h | 39 +++++++++++++++++++++++++------- components/aux_ac/climate.py | 3 +++ examples/advanced/ac_common.yaml | 1 + tests/test-ext.yaml | 1 + 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/README-EN.md b/README-EN.md index 2a0ca72..a3d8633 100644 --- a/README-EN.md +++ b/README-EN.md @@ -123,12 +123,13 @@ climate: - **name** (**Required**, string): The name of the climate device. At least one of `id` or `name` is required! - **id** (*Optional*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Manually specify the ID used for code generation. At least one of `id` or `name` is required! - **uart_id** (*Optional*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Manually specify the ID of the [UART Bus](https://esphome.io/components/uart.html) if you want to use multiple UART buses. -- **period** (*Optional*, [time](https://esphome.io/guides/configuration-types.html#config-time)): Period between status requests to the AC. Defaults to ``7s``. `Aux_ac` will receive the new air conditioner status only after a regular request, even if you change the settings of AC using IR-remote. -- **show_action** (*Optional*, boolean): Whether to show current action of the device (experimental). For example in the HEAT-COOL mode AC hardware may be in one of the following actions: +- **period** (*Optional*, [time](https://esphome.io/guides/configuration-types.html#config-time), default ``7s``): Period between status requests to the AC. `Aux_ac` will receive the new air conditioner status only after a regular request, even if you change the settings of AC using IR-remote. +- **show_action** (*Optional*, boolean, default ``true``): Whether to show current action of the device (experimental). For example in the HEAT-COOL mode AC hardware may be in one of the following actions: - HEATING: AC is heating the air in the room; - IDLE: AC is working in the FAN mode cause the target temperature is reached; - 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 - **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. diff --git a/README.md b/README.md index 26dc263..cf6c2d8 100644 --- a/README.md +++ b/README.md @@ -125,12 +125,13 @@ climate: - **name** (**Обязательный**, строка): Имя кондиционера. Как минимум один из параметров `id` или `name` должен быть указан! - **id** (*Опциональный*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Укажите идентификатор кондиционера чтобы обращаться к нему из кода. Как минимум один из параметров `id` или `name` должен быть указан! - **uart_id** (*Опциональный*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Укажите ID [шины UART](https://esphome.io/components/uart.html), к которой подключен кондиционер. Если сконфигурирована одна шина, то компонент подключит её автоматически. Если шин несколько, то лучше указать вручную. -- **period** (*Опциональный*, [время](https://esphome.io/guides/configuration-types.html#config-time)): Период между запросами статуса кондиционера. По умолчанию ``7s``. `Aux_ac` получает новое состояние кондиционера только после регулярного запроса, потому что сам кондиционер об изменении параметров своеё работы не уведомляет. Поэтому нужно запрашивать его, вдруг пользователь установил иной режим работы с помощью ИК-пульта. -- **show_action** (*Опциональный*, логическое): Показывать ли текущую задачу кондиционера (экспериментальная функция). Например, в режиме HEAT-COOL кондиционер может выполнять одну из следующих задач: +- **period** (*Опциональный*, [время](https://esphome.io/guides/configuration-types.html#config-time), по умолчанию ``7s``): Период между запросами статуса кондиционера. `Aux_ac` получает новое состояние кондиционера только после регулярного запроса, потому что сам кондиционер об изменении параметров своеё работы не уведомляет. Поэтому нужно запрашивать его, вдруг пользователь установил иной режим работы с помощью ИК-пульта. +- **show_action** (*Опциональный*, логическое, по умолчанию ``true``): Показывать ли текущую задачу кондиционера (экспериментальная функция). Например, в режиме HEAT-COOL кондиционер может выполнять одну из следующих задач: - НАГРЕВ: нагревает воздух в комнате; - ПРОСТОЙ: кондиционер работает в режиме вентилятора для перемешивания воздуха в комнате, поскольку целевая температура уже достигнута; - ОХЛАЖДЕНИЕ: кондиционер охлаждает воздух в комнате. Аналогично будут отображаться действия кондиционера и для режимов ОТОПЛЕНИЕ и ОХЛАЖДЕНИЕ. Единственная разница будет в количестве действий: ПРОСТОЙ+НАГРЕВ для режима отопления и ПРОСТОЙ+ОХЛАЖДЕНИЕ для режима охлаждения комнаты. +- **display_inverted** (*Опциональный*, логическое, по умолчанию ``false``): Настраивает способ управления дисплеем. Как выяснилось (issue [#31](https://github.com/GrKoR/esphome_aux_ac_component/issues/31)), включение-выключение дисплея обрабатывается кондиционерами по разному. Кондиционеры Rovex включают дисплей по `0` в соответствующем бите команды и выключают по биту `1`. Многие другие модели кондиционеров поступают наоборот. - **indoor_temperature** (*Опциональный*): Параметры создаваемого датчика температуры воздуха, если такой датчик нужен - **name** (**Обязательный**, строка): Имя датчика температуры. - **id** (*Опциональный*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Можно указать свой ID для датчика для использования в лямбдах. diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 4de777d..c4fe321 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -469,6 +469,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // по дефолту показываем bool _show_action = true; + // как отрабатывается включание-выключение дисплея. + // если тут false, то 1 в соответствующем бите включает дисплей, а 0 выключает. + // если тут true, то 1 потушит дисплей, а 0 включит. + bool _display_inverted = false; + // поддерживаемые кондиционером опции std::set _supported_modes{}; std::set _supported_swing_modes{}; @@ -1858,11 +1863,19 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { if (sensor_display_ != nullptr) switch (_current_ac_state.display) { case AC_DISPLAY_ON: - sensor_display_->publish_state(true); + if (this->get_display_inverted()) { + sensor_display_->publish_state(false); + } else { + sensor_display_->publish_state(true); + } break; case AC_DISPLAY_OFF: - sensor_display_->publish_state(false); + if (this->get_display_inverted()) { + sensor_display_->publish_state(true); + } else { + sensor_display_->publish_state(false); + } break; default: @@ -1877,6 +1890,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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"); 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()) { @@ -2407,18 +2421,27 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // выключает экран bool displayOffSequence(){ - return displaySequence(AC_DISPLAY_OFF); + ac_display dsp = AC_DISPLAY_OFF; + if (this->get_display_inverted()) dsp = AC_DISPLAY_ON; + return displaySequence(dsp); } // включает экран bool displayOnSequence(){ - return displaySequence(AC_DISPLAY_ON); + ac_display dsp = AC_DISPLAY_ON; + if (this->get_display_inverted()) dsp = AC_DISPLAY_OFF; + return displaySequence(dsp); } - 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_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_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; } diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index c781771..532b08a 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -35,6 +35,7 @@ CONF_SHOW_ACTION = 'show_action' CONF_INDOOR_TEMPERATURE = 'indoor_temperature' CONF_DISPLAY_STATE = 'display_state' +CONF_DISPLAY_INVERTED = 'display_inverted' ICON_DISPLAY = "mdi:numeric" aux_ac_ns = cg.esphome_ns.namespace("aux_ac") @@ -89,6 +90,7 @@ CONFIG_SCHEMA = cv.All( 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_INDOOR_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, icon=ICON_THERMOMETER, @@ -141,6 +143,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])) if CONF_SUPPORTED_MODES in config: cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) if CONF_SUPPORTED_SWING_MODES in config: diff --git a/examples/advanced/ac_common.yaml b/examples/advanced/ac_common.yaml index b10d636..dcf8048 100644 --- a/examples/advanced/ac_common.yaml +++ b/examples/advanced/ac_common.yaml @@ -63,6 +63,7 @@ climate: uart_id: ac_uart_bus period: 7s show_action: true + display_inverted: true indoor_temperature: name: ${upper_devicename} AC Indoor Temperature id: ${devicename}_indoor_temp diff --git a/tests/test-ext.yaml b/tests/test-ext.yaml index 80a5c23..1211d76 100644 --- a/tests/test-ext.yaml +++ b/tests/test-ext.yaml @@ -56,6 +56,7 @@ climate: 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 From e0d5be36c09a6dcb792fe40cb13f8c53b5d426c8 Mon Sep 17 00:00:00 2001 From: GrKoR Date: Sat, 7 May 2022 16:58:00 +0300 Subject: [PATCH 2/3] bug fixes --- README-EN.md | 1 + README.md | 1 + components/aux_ac/aux_ac.h | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README-EN.md b/README-EN.md index a3d8633..3ec6ba5 100644 --- a/README-EN.md +++ b/README-EN.md @@ -85,6 +85,7 @@ climate: uart_id: ac_uart_bus period: 7s show_action: true + display_inverted: false indoor_temperature: name: AC Indoor Temperature id: ac_indoor_temp diff --git a/README.md b/README.md index cf6c2d8..e9daefd 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ climate: uart_id: ac_uart_bus period: 7s show_action: true + display_inverted: false indoor_temperature: name: AC Indoor Temperature id: ac_indoor_temp diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index c4fe321..a8da958 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -49,7 +49,7 @@ public: static const uint32_t AC_STATES_REQUEST_INTERVAL; }; -const std::string Constants::AC_FIRMWARE_VERSION = "0.2.1"; +const std::string Constants::AC_FIRMWARE_VERSION = "0.2.2"; const char *const Constants::TAG = "AirCon"; const std::string Constants::MUTE = "mute"; const std::string Constants::TURBO = "turbo"; @@ -353,7 +353,7 @@ enum ac_fanmute : uint8_t { AC_FANMUTE_OFF = 0x00, AC_FANMUTE_ON = 0x80, AC_FANM // включение-выключение дисплея на корпусе внутреннего блока #define AC_DISPLAY_MASK 0b00010000 -enum ac_display : uint8_t { AC_DISPLAY_ON = 0x00, AC_DISPLAY_OFF = 0x10, AC_DISPLAY_UNTOUCHED = 0xFF }; +enum ac_display : uint8_t { AC_DISPLAY_OFF = 0x00, AC_DISPLAY_ON = 0x10, AC_DISPLAY_UNTOUCHED = 0xFF }; // включение-выключение функции "Антиплесень". // По факту: после выключения сплита он оставляет минут на 5 открытые жалюзи и глушит вентилятор. Уличный блок при этом гудит и тарахтит. From d8ae60ba842b5cb6f1c302edc40637694d25d89f Mon Sep 17 00:00:00 2001 From: GrKoR Date: Sat, 7 May 2022 18:38:23 +0300 Subject: [PATCH 3/3] display control fixed --- components/aux_ac/automation.h | 4 +-- components/aux_ac/aux_ac.h | 64 ++++++++++++++++++---------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index c41aea3..d8d9565 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -13,7 +13,7 @@ namespace aux_ac { public: explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {} - void play(Ts... x) override { this->ac_->displaySequence(AC_DISPLAY_OFF); } + void play(Ts... x) override { this->ac_->displayOffSequence(); } protected: AirCon *ac_; @@ -25,7 +25,7 @@ namespace aux_ac { public: explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {} - void play(Ts... x) override { this->ac_->displaySequence(AC_DISPLAY_ON); } + void play(Ts... x) override { this->ac_->displayOnSequence(); } protected: AirCon *ac_; diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index a8da958..fc40063 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -581,7 +581,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return true; } - // додбавляет в последовательность шаг с задержкой + // добавляет в последовательность шаг с задержкой bool _addSequenceDelayStep(uint16_t timeout){ return this->_addSequenceStep(AC_SIT_DELAY, nullptr, nullptr, timeout); } @@ -723,9 +723,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } // копирует пакет из одной структуры в другую с корректным переносом указателей на заголовки и т.п. - void _copyPacket(packet_t *dest, packet_t *src){ - if (dest == nullptr) return; - if (src == nullptr) return; + 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; @@ -733,6 +733,8 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { 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; } // устанавливает состояние конечного автомата @@ -782,7 +784,6 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return; }; - if (this->peek() == AC_PACKET_START_BYTE) { // если во входящий пакет что-то уже загружено, значит это какие-то ошибочные данные или неизвестные пакеты // надо эту инфу вывалить в лог @@ -1584,6 +1585,27 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // бинарный сенсор, отображающий состояние дисплея esphome::binary_sensor::BinarySensor *sensor_display_ = 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; + } + public: // инициализация объекта void initAC(esphome::uart::UARTComponent *parent = nullptr){ @@ -2273,12 +2295,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************************** getBigInfo request ***********************************************/ if (!_addSequenceFuncStep(&AirCon::sq_requestBigStatus)) { - _debugMsg(F("getStatusSmall: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugMsg(F("getStatusBig: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); return false; } /*************************************** getBigInfo control ***********************************************/ if (!_addSequenceFuncStep(&AirCon::sq_controlBigStatus)) { - _debugMsg(F("getStatusSmall: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugMsg(F("getStatusBig: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); return false; } /**************************************************************************************/ @@ -2359,12 +2381,12 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { /*************************************** set params request ***********************************************/ if (!_addSequenceFuncStep(&AirCon::sq_requestDoCommand, cmd)) { - _debugMsg(F("getStatusSmall: getBigInfo request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _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("getStatusSmall: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugMsg(F("commandSequence: getBigInfo control sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); return false; } /**************************************************************************************/ @@ -2399,38 +2421,18 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return true; } - // загружает на выполнение последовательность команд на включение/выключение табло с температурой - 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; - } - // выключает экран bool displayOffSequence(){ ac_display dsp = AC_DISPLAY_OFF; if (this->get_display_inverted()) dsp = AC_DISPLAY_ON; - return displaySequence(dsp); + return _displaySequence(dsp); } // включает экран bool displayOnSequence(){ ac_display dsp = AC_DISPLAY_ON; if (this->get_display_inverted()) dsp = AC_DISPLAY_OFF; - return displaySequence(dsp); + return _displaySequence(dsp); } void set_period(uint32_t ms) { this->_update_period = ms; }