diff --git a/README-EN.md b/README-EN.md index 53434a5..68d7ce2 100644 --- a/README-EN.md +++ b/README-EN.md @@ -123,6 +123,10 @@ climate: name: AC Preset Reporter id: ac_preset_reporter internal: false + vlouver_state: + name: AC Vertical Louvers State + id: ac_vlouver_state + internal: false visual: min_temperature: 16 max_temperature: 32 @@ -176,7 +180,8 @@ climate: - **defrost_state** (*Optional*): The information for the HVAC defrost function state sensor (is it ON or OFF). All settings are the same as for the **display_state** (see description above). - **invertor_power** (*Optional*): The information for the invertor power sensor. All settings are the same as for the **display_state** (see description above). - **preset_reporter** (*Optional*): Parameters of text sensor with current preset. All settings are the same as for the **display_state** (see description above). -ESPHome Climate devices are not report their active presets (from **supported_presets** and **custom_presets** lists) to MQTT. In case you are using mqtt and want to receive information about active preset you should declare this sensor in your yaml. + ESPHome Climate devices are not report their active presets (from **supported_presets** and **custom_presets** lists) to MQTT. In case you are using mqtt and want to receive information about active preset you should declare this sensor in your yaml. +- **vlouver_state** (*Optional*): Parameters of vertical louvers state sensor. All settings are the same as for the **display_state** (see description above). The state of the vertical louvers is encoded by the integer value (see [aux_ac.vlouver_set action](#aux_ac_._vlouver_set) below). - **supported_modes** (*Optional*, list): List of supported modes. Possible values are: ``HEAT_COOL``, ``COOL``, ``HEAT``, ``DRY``, ``FAN_ONLY``. Please note: some manufacturers call AUTO mode instead of HEAT_COOL. Defaults to ``FAN_ONLY``. - **custom_fan_modes** (*Optional*, list): List of supported custom fan modes. Possible values are: ``MUTE``, ``TURBO``. No custom fan modes by default. - **supported_presets** (*Optional*, list): List of supported presets. Possible values are: ``SLEEP``. No presets by default. @@ -205,6 +210,28 @@ on_...: ``` - **aux_id** (**Requared**, string): ID of `aux_ac` component. +### ``aux_ac.vlouver_set`` ### +This action moves HVAC vertical louvers to the specified position. + +The position is encoded by the following values: +- `0`: the vertical louvers are in `SWING` mode (they are moving up and down); +- `1`: the louvers are stopped in a user position; +- `2`: the louvers are in the topmost position; +- `3`: the louvers are in one step above middle position; +- `4`: the louvers are in the middle position; +- `5`: the louvers are in one step below middle position; +- `6`: the louvers are in the lowest position. + +```yaml +on_...: + then: + - aux_ac.vlouver_set: + id: aux_id + position: 3 # moves the louvers to the middle position +``` +- **aux_id** (**Requared**, string): ID of `aux_ac` component. +- **position** (**Requared**, integer): position of the vertical louvers. + ### ``aux_ac.vlouver_stop`` ### This action stops vertical swing of louvers. diff --git a/README.md b/README.md index c9a1cfd..43e5be2 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,10 @@ climate: name: AC Preset Reporter id: ac_preset_reporter internal: false + vlouver_state: + name: AC Vertical Louvers State + id: ac_vlouver_state + internal: false visual: min_temperature: 16 max_temperature: 32 @@ -185,7 +189,8 @@ climate: - **defrost_state** (*Опциональный*): Параметры создаваемого датчика состояния разморозки (включена или выключена), если такой датчик нужен. Параметры аналогичны датчику дисплея **display_state**. - **invertor_power** (*Опциональный*): Параметры создаваемого датчика мощности инвертора, если такой датчик нужен. Параметры аналогичны датчику дисплея **display_state**. - **preset_reporter** (*Опциональный*): Параметры создаваемого текстового датчика текущего активного пресета. Параметры аналогичны датчику дисплея **display_state**. -Климатические устройства ESPHome не отправляют по MQTT активный пресет (см. **supported_presets** и **custom_presets**), в котором работает устройство. Если вы используете MQTT и хотите получать информацию о пресетах, то пропишите этот датчик в конфигурации. + Климатические устройства ESPHome не отправляют по MQTT активный пресет (см. **supported_presets** и **custom_presets**), в котором работает устройство. Если вы используете MQTT и хотите получать информацию о пресетах, то пропишите этот датчик в конфигурации. +- **vlouver_state** (*Опциональный*): Параметры создаваемого сенсора состояния вертикальных жалюзи. Параметры аналогичны датчику дисплея **display_state**. Состояние желюзи кодируется целочисленными значениями (подробнее смотри [aux_ac.vlouver_set action](#aux_ac_._vlouver_set) ниже). - **supported_modes** (*Опциональный*, список): Список поддерживаемых режимов работы. Возможные значения: ``HEAT_COOL``, ``COOL``, ``HEAT``, ``DRY``, ``FAN_ONLY``. Обратите внимание: некоторые производители кондиционеров указывают на пульте режим AUTO, хотя по факту этот режим не работает по расписанию и только лишь поддерживает целевую температуру. Такой режим в ESPHome называется HEAT_COOL. По умолчанию список содержит только значение ``FAN_ONLY``. - **custom_fan_modes** (*Опциональный*, список): Список поддерживаемых дополнительных режимов вентилятора. Возможные значения: ``MUTE``, ``TURBO``. По умолчанию никакие дополнительные режимы не установлены. - **supported_presets** (*Опциональный*, список): Список поддерживаемых базовых функций кондиционера. Возможные значения: ``SLEEP``. По умолчанию никакие базовые функции не установлены. @@ -214,6 +219,28 @@ on_...: ``` - **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. +### ``aux_ac.vlouver_set`` ### +Переводит жалюзи в указанное состояние. + +Состояние кодируется следующими целочисленными значениями: +- `0`: жалюзи находятся в состоянии `SWING` (качаются вверх-вниз); +- `1`: жалюзи остановлены в каком-то пользовательском положении; +- `2`: жалюзи установлены в верхнее положение; +- `3`: жалюзи установдены в положение на шаг выше среднего; +- `4`: жалюзи установены в среднее положение; +- `5`: жалюзи установдены в положение на шаг ниже среднего; +- `6`: жалюзи установлены в нижнее положение. + +```yaml +on_...: + then: + - aux_ac.vlouver_set: + id: aux_id + position: 3 # устанавливаем жалюзи в среднее положение +``` +- **aux_id** (**Обязательный**, строка): ID компонента `aux_ac`. +- **position** (**Обязательный**, целое число): состояние вертикальных жалюзи. + ### ``aux_ac.vlouver_stop`` ### Остановка вертикального движения жалюзи кондиционера. Если жалюзи качались в вертикальном направлении, то можно их остановить в нужном положении. diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index 3af1b52..2ffdac0 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -119,6 +119,22 @@ namespace aux_ac { AirCon *ac_; }; + template + class AirConVLouverSetAction : public Action + { + public: + AirConVLouverSetAction(AirCon *ac) : ac_(ac) {} + TEMPLATABLE_VALUE(uint8_t, value); + + void play(Ts... x) { + vlpos_ = this->value_.value(x...); + this->ac_->setVLouverFrontendSequence( (ac_vlouver_frontend)vlpos_); + } + + protected: + AirCon *ac_; + uint8_t vlpos_; + }; // **************************************** SEND TEST PACKET ACTION **************************************** diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 59d87b2..c338297 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -63,7 +63,7 @@ public: static const uint32_t AC_STATES_REQUEST_INTERVAL; }; -const std::string Constants::AC_FIRMWARE_VERSION = "0.2.5"; +const std::string Constants::AC_FIRMWARE_VERSION = "0.2.6"; const char *const Constants::TAG = "AirCon"; // custom fan modes @@ -440,6 +440,18 @@ enum ac_mildew : uint8_t { AC_MILDEW_OFF = 0x00, AC_MILDEW_ON = 0x08, AC_MILDEW_ // GK: define убрал, т.к. считаю, что сбрасывать счетчик не надо. // #define AC_MIN_COUNTER_MASK 0b00111111 +// положение вертикальных жалюзи для фронтенда +enum ac_vlouver_frontend : uint8_t { + AC_VLOUVER_FRONTEND_SWING = 0x00, + AC_VLOUVER_FRONTEND_STOP = 0x01, + AC_VLOUVER_FRONTEND_TOP = 0x02, + AC_VLOUVER_FRONTEND_MIDDLE_ABOVE = 0x03, + AC_VLOUVER_FRONTEND_MIDDLE = 0x04, + AC_VLOUVER_FRONTEND_MIDDLE_BELOW = 0x05, + AC_VLOUVER_FRONTEND_BOTTOM = 0x06, +}; + + /** команда для кондиционера * * ВАЖНО! В коде используется копирование команд простым присваиванием. @@ -1788,18 +1800,15 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { } // сенсоры, отображающие параметры сплита - esphome::sensor::Sensor *sensor_indoor_temperature_ = nullptr; - esphome::sensor::Sensor *sensor_outdoor_temperature_ = nullptr; - esphome::sensor::Sensor *sensor_inbound_temperature_ =nullptr; - esphome::sensor::Sensor *sensor_outbound_temperature_ =nullptr; - esphome::sensor::Sensor *sensor_compressor_temperature_ =nullptr; - // текущая мощность компрессора - esphome::sensor::Sensor *sensor_invertor_power_ = nullptr; - // бинарный сенсор, отображающий состояние дисплея - esphome::binary_sensor::BinarySensor *sensor_display_ = nullptr; - // бинарный сенсор состония разморозки - esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; - // текстовый сенсор, отображающий текущий режим работы сплита + esphome::sensor::Sensor *sensor_indoor_temperature_ = nullptr; + esphome::sensor::Sensor *sensor_outdoor_temperature_ = nullptr; + esphome::sensor::Sensor *sensor_inbound_temperature_ = nullptr; + esphome::sensor::Sensor *sensor_outbound_temperature_ = nullptr; + esphome::sensor::Sensor *sensor_compressor_temperature_ = nullptr; + esphome::sensor::Sensor *sensor_invertor_power_ = nullptr; + esphome::sensor::Sensor *sensor_vlouver_state_ = nullptr; + esphome::binary_sensor::BinarySensor *sensor_display_ = nullptr; + esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; esphome::text_sensor::TextSensor *sensor_preset_reporter_ = nullptr; @@ -1913,6 +1922,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { void set_inbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_inbound_temperature_ = temperature_sensor; } void set_outbound_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_outbound_temperature_ = temperature_sensor; } void set_compressor_temperature_sensor(sensor::Sensor *temperature_sensor) { sensor_compressor_temperature_ = temperature_sensor; } + void set_vlouver_state_sensor(sensor::Sensor *vlouver_state_sensor){ sensor_vlouver_state_ = vlouver_state_sensor; } void set_defrost_state(binary_sensor::BinarySensor *defrost_state_sensor) { sensor_defrost_ = defrost_state_sensor; } void set_display_sensor(binary_sensor::BinarySensor *display_sensor) { sensor_display_ = display_sensor; } void set_invertor_power_sensor(sensor::Sensor *invertor_power_sensor) { sensor_invertor_power_ = invertor_power_sensor; } @@ -2241,6 +2251,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { // флаг режима разморозки if (sensor_defrost_ != nullptr) sensor_defrost_->publish_state(_current_ac_state.defrost); + // положение вертикальных жалюзи + if (sensor_vlouver_state_ != nullptr) + sensor_vlouver_state_->publish_state( (float) this->getCurrentVlouverFrontendState() ); // сенсор состояния сплита if (sensor_preset_reporter_ != nullptr) { @@ -2407,7 +2420,21 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { ESP_LOGV(Constants::TAG, "%s Force Update: YES", " "); } } - + + if ((this->sensor_vlouver_state_) != nullptr) { + ESP_LOGCONFIG(Constants::TAG, "%s%s '%s'", " ", LOG_STR_LITERAL("Vertical louvers state"), (this->sensor_vlouver_state_)->get_name().c_str()); + ESP_LOGCONFIG(Constants::TAG, "%s Accuracy Decimals: %d", " ", (this->sensor_vlouver_state_)->get_accuracy_decimals()); + if (!(this->sensor_vlouver_state_)->get_icon().empty()) { + ESP_LOGCONFIG(Constants::TAG, "%s Icon: '%s'", " ", (this->sensor_vlouver_state_)->get_icon().c_str()); + } + if (!(this->sensor_vlouver_state_)->unique_id().empty()) { + ESP_LOGV(Constants::TAG, "%s Unique ID: '%s'", " ", (this->sensor_vlouver_state_)->unique_id().c_str()); + } + if ((this->sensor_vlouver_state_)->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()) { @@ -3059,7 +3086,72 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return true; } - // устанавливает жалюзи в нужное положение + // конвертирует состояние жалюзи из кодов сплита в коды для фронтенда + ac_vlouver_frontend AUXvlouverToVlouverFrontend(const ac_louver_V vLouver){ + switch (vLouver) { + case AC_LOUVERV_SWING_UPDOWN: + return AC_VLOUVER_FRONTEND_SWING; + + case AC_LOUVERV_OFF: + return AC_VLOUVER_FRONTEND_STOP; + + case AC_LOUVERV_SWING_TOP: + return AC_VLOUVER_FRONTEND_TOP; + + case AC_LOUVERV_SWING_MIDDLE_ABOVE: + return AC_VLOUVER_FRONTEND_MIDDLE_ABOVE; + + case AC_LOUVERV_SWING_MIDDLE: + return AC_VLOUVER_FRONTEND_MIDDLE; + + case AC_LOUVERV_SWING_MIDDLE_BELOW: + return AC_VLOUVER_FRONTEND_MIDDLE_BELOW; + + case AC_LOUVERV_SWING_BOTTOM: + return AC_VLOUVER_FRONTEND_BOTTOM; + + default: + _debugMsg(F("AUXvlouverToVlouverFrontend: unknown vertical louver state = %u"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__, _current_ac_state.louver.louver_v); + return AC_VLOUVER_FRONTEND_STOP; // + } + } + + // возвращает текущее положение шторок в кодах для фронтенда + ac_vlouver_frontend getCurrentVlouverFrontendState(){ + return AUXvlouverToVlouverFrontend(_current_ac_state.louver.louver_v); + } + + // конвертирует состояние жалюзи из кодов для фронтенда в коды сплита + ac_louver_V vlouverFrontendToAUXvlouver(const ac_vlouver_frontend vLouver){ + switch (vLouver) { + case AC_VLOUVER_FRONTEND_SWING: + return AC_LOUVERV_SWING_UPDOWN; + + case AC_VLOUVER_FRONTEND_STOP: + return AC_LOUVERV_OFF; + + case AC_VLOUVER_FRONTEND_TOP: + return AC_LOUVERV_SWING_TOP; + + case AC_VLOUVER_FRONTEND_MIDDLE_ABOVE: + return AC_LOUVERV_SWING_MIDDLE_ABOVE; + + case AC_VLOUVER_FRONTEND_MIDDLE: + return AC_LOUVERV_SWING_MIDDLE; + + case AC_VLOUVER_FRONTEND_MIDDLE_BELOW: + return AC_LOUVERV_SWING_MIDDLE_BELOW; + + case AC_VLOUVER_FRONTEND_BOTTOM: + return AC_LOUVERV_SWING_BOTTOM; + + default: + _debugMsg(F("vlouverFrontendToAUXvlouver: unknown vertical louver state = %u"), ESPHOME_LOG_LEVEL_DEBUG, __LINE__, _current_ac_state.louver.louver_v); + return AC_LOUVERV_OFF; // + } + } + + // устанавливает жалюзи в нужное положение по коду сплита bool setVLouverSequence(const ac_louver_V vLouver){ // нет смысла в последовательности, если нет коннекта с кондиционером if (!get_has_connection()) { @@ -3081,6 +3173,11 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return true; } + // устанавливает жалюзи в нужное положение по коду для фронтенда + bool setVLouverFrontendSequence(const ac_vlouver_frontend vLouver){ + return setVLouverSequence(vlouverFrontendToAUXvlouver(vLouver)); + } + // установка жалюзи в определенные положения bool setVLouverSwingSequence() { return setVLouverSequence(AC_LOUVERV_SWING_UPDOWN); } bool setVLouverStopSequence() { return setVLouverSequence(AC_LOUVERV_OFF); } diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 651c720..3bc9c90 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -22,6 +22,7 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_POWER_FACTOR, STATE_CLASS_MEASUREMENT, + CONF_POSITION, ) from esphome.components.climate import ( ClimateMode, @@ -53,14 +54,22 @@ CONF_DISPLAY_INVERTED = 'display_inverted' ICON_DISPLAY = "mdi:clock-digital" CONF_PRESET_REPORTER = "preset_reporter" ICON_PRESET_REPORTER = "mdi:format-list-group" +CONF_VLOUVER_STATE = "vlouver_state" +ICON_VLOUVER_STATE = "mdi:compare-vertical" aux_ac_ns = cg.esphome_ns.namespace("aux_ac") AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) Capabilities = aux_ac_ns.namespace("Constants") +# Display actions AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action) AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action) + +# test packet +AirConSendTestPacketAction = aux_ac_ns.class_("AirConSendTestPacketAction", automation.Action) + +# vertical louvers actions AirConVLouverSwingAction = aux_ac_ns.class_("AirConVLouverSwingAction", automation.Action) AirConVLouverStopAction = aux_ac_ns.class_("AirConVLouverStopAction", automation.Action) AirConVLouverTopAction = aux_ac_ns.class_("AirConVLouverTopAction", automation.Action) @@ -68,7 +77,8 @@ AirConVLouverMiddleAboveAction = aux_ac_ns.class_("AirConVLouverMiddleAboveActio AirConVLouverMiddleAction = aux_ac_ns.class_("AirConVLouverMiddleAction", automation.Action) AirConVLouverMiddleBelowAction = aux_ac_ns.class_("AirConVLouverMiddleBelowAction", automation.Action) AirConVLouverBottomAction = aux_ac_ns.class_("AirConVLouverBottomAction", automation.Action) -AirConSendTestPacketAction = aux_ac_ns.class_("AirConSendTestPacketAction", automation.Action) + +AirConVLouverSetAction = aux_ac_ns.class_("AirConVLouverSetAction", automation.Action) ALLOWED_CLIMATE_MODES = { "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, @@ -190,6 +200,14 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, } ), + cv.Optional(CONF_VLOUVER_STATE): sensor.sensor_schema( + icon=ICON_VLOUVER_STATE, + accuracy_decimals=0, + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, + } + ), cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema( icon=ICON_DISPLAY, ).extend( @@ -257,6 +275,11 @@ async def to_code(config): conf = config[CONF_COMPRESSOR_TEMPERATURE] sens = await sensor.new_sensor(conf) cg.add(var.set_compressor_temperature_sensor(sens)) + + if CONF_VLOUVER_STATE in config: + conf = config[CONF_VLOUVER_STATE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_vlouver_state_sensor(sens)) if CONF_DISPLAY_STATE in config: conf = config[CONF_DISPLAY_STATE] @@ -353,6 +376,21 @@ async def vlouver_bottom_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) return cg.new_Pvariable(action_id, template_arg, paren) +VLOUVER_SET_ACTION_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(AirCon), + cv.Required(CONF_POSITION): cv.templatable(cv.int_range(0, 6)), + } +) + +@automation.register_action("aux_ac.vlouver_set", AirConVLouverSetAction, VLOUVER_SET_ACTION_SCHEMA) +async def vlouver_set_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_POSITION], args, int) + cg.add(var.set_value(template_)) + return var + # ********************************************************************************************************* # ВАЖНО! Только для инженеров! @@ -367,11 +405,7 @@ SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id( } ) -@automation.register_action( - "aux_ac.send_packet", - AirConSendTestPacketAction, - SEND_TEST_PACKET_ACTION_SCHEMA -) +@automation.register_action( "aux_ac.send_packet", AirConSendTestPacketAction, SEND_TEST_PACKET_ACTION_SCHEMA ) async def send_packet_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/examples/advanced/ac_common.yaml b/examples/advanced/ac_common.yaml index a6e9f57..717a066 100644 --- a/examples/advanced/ac_common.yaml +++ b/examples/advanced/ac_common.yaml @@ -100,6 +100,10 @@ climate: name: ${upper_devicename} Preset Reporter id: ${devicename}_preset_reporter internal: false + vlouver_state: + name: ${upper_devicename} VLouvers State + id: ${devicename}_vlouver_state + internal: false visual: min_temperature: 16 max_temperature: 32 @@ -194,15 +198,18 @@ button: number: - platform: template - name: ${upper_devicename} Vertical Louver + name: ${upper_devicename} Vertical Louvers id: ${devicename}_vlouver icon: "mdi:circle-small" mode: "slider" min_value: 0 max_value: 6 step: 1 + update_interval: 2s + lambda: |- + return id(${devicename}_vlouver_state).state; set_action: then: - - lambda: !lambda |- - if (x == 6) x = 7; // 6 is incorrect louver position, 7 is stopped louver - id(aux_id).setVLouverSequence( static_cast(x) ); \ No newline at end of file + - aux_ac.vlouver_set: + id: aux_id + position: !lambda "return x;" \ No newline at end of file