diff --git a/README-EN.md b/README-EN.md index 9a639eb..768f88c 100644 --- a/README-EN.md +++ b/README-EN.md @@ -145,6 +145,10 @@ climate: name: AC Preset Reporter id: ac_preset_reporter internal: false + actual_fan_speed: + name: AC Actual Fan Speed + id: ac_actual_fan_speed + internal: false vlouver_state: name: AC Vertical Louvers State id: ac_vlouver_state @@ -230,9 +234,25 @@ climate: - **inverter_power_limit_value** (*Optional*): Configuration of the power limit value sensor. All settings are the same as for the **indoor_temperature** (see description above). It reports the current value of the power limitation function for the inverter HVAC. This sensor represents the value only after the HVAC confirms the power limitation. The value is always in the range from 30% to 100%. This is the hardware limitation. -- **preset_reporter** (*Optional*): Parameters of text sensor with current preset. 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 reporting their active presets (from **supported_presets** and **custom_presets** lists) to MQTT. This behavior has been noticed at least in version 1.20.0. In case you are using MQTT and want to receive information about active preset, you should declare this sensor in your yaml. +- **actual_fan_speed** (*Optional*): Configuration of the actual fan speed sensor. This sensor reports the real fan speed as a percentage (0-100%). All settings are the same as for the **indoor_temperature** (see description above), plus the following optional percentage overrides: + - **off_percent** (*Optional*, integer 0-100): Custom percentage for OFF speed. Default: 0% + - **mute_percent** (*Optional*, integer 0-100): Custom percentage for MUTE speed. + - **low_percent** (*Optional*, integer 0-100): Custom percentage for LOW speed. + - **mid_percent** (*Optional*, integer 0-100): Custom percentage for MID speed. + - **high_percent** (*Optional*, integer 0-100): Custom percentage for HIGH speed. + - **turbo_percent** (*Optional*, integer 0-100): Custom percentage for TURBO speed. + + When percentage options are not specified, defaults are calculated based on which modes are enabled in **custom_fan_modes**: + - Neither MUTE nor TURBO: OFF=0%, LOW=33%, MID=67%, HIGH=100% + - MUTE only: OFF=0%, MUTE=25%, LOW=50%, MID=75%, HIGH=100% + - TURBO only: OFF=0%, LOW=25%, MID=50%, HIGH=75%, TURBO=100% + - Both MUTE and TURBO: OFF=0%, MUTE=20%, LOW=40%, MID=60%, HIGH=80%, TURBO=100% + + This value reflects the actual fan speed reported by the AC unit, which may differ from the requested fan mode. + - **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``. diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 1d7539a..d901b1c 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -2245,7 +2245,16 @@ namespace esphome esphome::binary_sensor::BinarySensor *sensor_display_ = nullptr; esphome::binary_sensor::BinarySensor *sensor_defrost_ = nullptr; esphome::text_sensor::TextSensor *sensor_preset_reporter_ = nullptr; + esphome::sensor::Sensor *sensor_actual_fan_speed_ = nullptr; esphome::sensor::Sensor *sensor_inverter_power_limit_value_ = nullptr; + + // Custom fan speed percentages (-1 means use auto-calculated default) + int8_t fan_speed_off_percent_ = -1; + int8_t fan_speed_mute_percent_ = -1; + int8_t fan_speed_low_percent_ = -1; + int8_t fan_speed_mid_percent_ = -1; + int8_t fan_speed_high_percent_ = -1; + int8_t fan_speed_turbo_percent_ = -1; esphome::binary_sensor::BinarySensor *sensor_inverter_power_limit_state_ = nullptr; // загружает на выполнение последовательность команд на включение/выключение табло с температурой @@ -2394,7 +2403,16 @@ namespace esphome void set_display_sensor(binary_sensor::BinarySensor *display_sensor) { sensor_display_ = display_sensor; } void set_inverter_power_sensor(sensor::Sensor *inverter_power_sensor) { sensor_inverter_power_ = inverter_power_sensor; } void set_preset_reporter_sensor(text_sensor::TextSensor *preset_reporter_sensor) { sensor_preset_reporter_ = preset_reporter_sensor; } + void set_actual_fan_speed_sensor(sensor::Sensor *actual_fan_speed_sensor) { sensor_actual_fan_speed_ = actual_fan_speed_sensor; } void set_inverter_power_limit_value_sensor(sensor::Sensor *inverter_power_limit_value_sensor) { sensor_inverter_power_limit_value_ = inverter_power_limit_value_sensor; } + + // Fan speed percentage setters + void set_fan_speed_off_percent(int8_t percent) { fan_speed_off_percent_ = percent; } + void set_fan_speed_mute_percent(int8_t percent) { fan_speed_mute_percent_ = percent; } + void set_fan_speed_low_percent(int8_t percent) { fan_speed_low_percent_ = percent; } + void set_fan_speed_mid_percent(int8_t percent) { fan_speed_mid_percent_ = percent; } + void set_fan_speed_high_percent(int8_t percent) { fan_speed_high_percent_ = percent; } + void set_fan_speed_turbo_percent(int8_t percent) { fan_speed_turbo_percent_ = percent; } void set_inverter_power_limit_state_sensor(binary_sensor::BinarySensor *inverter_power_limit_state_sensor) { sensor_inverter_power_limit_state_ = inverter_power_limit_state_sensor; } bool get_hw_initialized() { return _hw_initialized; }; @@ -2416,6 +2434,90 @@ namespace esphome return (v == AC_LOUVERV_SWING_UPDOWN); } + // Convert real fan speed enum to percentage (0-100%) + // Uses custom percentages if configured, otherwise calculates defaults based on + // which custom_fan_modes are enabled (MUTE and/or TURBO): + // - Neither: OFF=0%, LOW=33%, MID=67%, HIGH=100% + // - MUTE only: OFF=0%, MUTE=25%, LOW=50%, MID=75%, HIGH=100% + // - TURBO only: OFF=0%, LOW=25%, MID=50%, HIGH=75%, TURBO=100% + // - Both: OFF=0%, MUTE=20%, LOW=40%, MID=60%, HIGH=80%, TURBO=100% + inline float actual_fan_speed_to_percent(ac_realFan speed) { + // Check if custom percentage is configured for this speed level + switch (speed) { + case AC_REAL_FAN_OFF: + if (fan_speed_off_percent_ >= 0) return static_cast(fan_speed_off_percent_); + return 0.0f; // OFF is always 0% + case AC_REAL_FAN_MUTE: + if (fan_speed_mute_percent_ >= 0) return static_cast(fan_speed_mute_percent_); + break; + case AC_REAL_FAN_LOW: + if (fan_speed_low_percent_ >= 0) return static_cast(fan_speed_low_percent_); + break; + case AC_REAL_FAN_MID: + if (fan_speed_mid_percent_ >= 0) return static_cast(fan_speed_mid_percent_); + break; + case AC_REAL_FAN_HIGH: + if (fan_speed_high_percent_ >= 0) return static_cast(fan_speed_high_percent_); + break; + case AC_REAL_FAN_TURBO: + if (fan_speed_turbo_percent_ >= 0) return static_cast(fan_speed_turbo_percent_); + break; + default: + return 0.0f; + } + + // No custom percentage set, calculate default based on enabled modes + bool mute_enabled = false; + bool turbo_enabled = false; + for (const char* mode : _supported_custom_fan_modes) { + if (strcmp(mode, Constants::MUTE) == 0) mute_enabled = true; + if (strcmp(mode, Constants::TURBO) == 0) turbo_enabled = true; + } + + // Return default percentage based on enabled modes + if (mute_enabled && turbo_enabled) { + // Both enabled: MUTE=20%, LOW=40%, MID=60%, HIGH=80%, TURBO=100% + switch (speed) { + case AC_REAL_FAN_MUTE: return 20.0f; + case AC_REAL_FAN_LOW: return 40.0f; + case AC_REAL_FAN_MID: return 60.0f; + case AC_REAL_FAN_HIGH: return 80.0f; + case AC_REAL_FAN_TURBO: return 100.0f; + default: return 0.0f; + } + } else if (mute_enabled) { + // MUTE only: MUTE=25%, LOW=50%, MID=75%, HIGH=100% + switch (speed) { + case AC_REAL_FAN_MUTE: return 25.0f; + case AC_REAL_FAN_LOW: return 50.0f; + case AC_REAL_FAN_MID: return 75.0f; + case AC_REAL_FAN_HIGH: return 100.0f; + case AC_REAL_FAN_TURBO: return 100.0f; // Treat as max + default: return 0.0f; + } + } else if (turbo_enabled) { + // TURBO only: LOW=25%, MID=50%, HIGH=75%, TURBO=100% + switch (speed) { + case AC_REAL_FAN_MUTE: return 25.0f; // Treat as LOW + case AC_REAL_FAN_LOW: return 25.0f; + case AC_REAL_FAN_MID: return 50.0f; + case AC_REAL_FAN_HIGH: return 75.0f; + case AC_REAL_FAN_TURBO: return 100.0f; + default: return 0.0f; + } + } else { + // Neither: LOW=33%, MID=67%, HIGH=100% + switch (speed) { + case AC_REAL_FAN_MUTE: return 33.0f; // Treat as LOW + case AC_REAL_FAN_LOW: return 33.0f; + case AC_REAL_FAN_MID: return 67.0f; + case AC_REAL_FAN_HIGH: return 100.0f; + case AC_REAL_FAN_TURBO: return 100.0f; // Treat as max + default: return 0.0f; + } + } + } + // возвращает, есть ли елементы в последовательности команд bool hasSequence() { @@ -2792,6 +2894,9 @@ namespace esphome // мощность инвертора if (sensor_inverter_power_ != nullptr) sensor_inverter_power_->publish_state(_current_ac_state.inverter_power); + // actual fan speed as percentage + if (sensor_actual_fan_speed_ != nullptr) + sensor_actual_fan_speed_->publish_state(actual_fan_speed_to_percent(_current_ac_state.realFanSpeed)); // флаг режима разморозки if (sensor_defrost_ != nullptr) sensor_defrost_->publish_state(_current_ac_state.defrost); diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index 58c4617..9b6a007 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -74,6 +74,17 @@ CONF_PRESET_REPORTER = "preset_reporter" ICON_PRESET_REPORTER = "mdi:format-list-group" +CONF_ACTUAL_FAN_SPEED = "actual_fan_speed" +ICON_ACTUAL_FAN_SPEED = "mdi:fan" + +# Fan speed percentage configuration options +CONF_OFF_PERCENT = "off_percent" +CONF_MUTE_PERCENT = "mute_percent" +CONF_LOW_PERCENT = "low_percent" +CONF_MID_PERCENT = "mid_percent" +CONF_HIGH_PERCENT = "high_percent" +CONF_TURBO_PERCENT = "turbo_percent" + CONF_VLOUVER_STATE = "vlouver_state" ICON_VLOUVER_STATE = "mdi:compare-vertical" @@ -298,6 +309,23 @@ def output_info(config): cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, } ), + cv.Optional(CONF_ACTUAL_FAN_SPEED): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_ACTUAL_FAN_SPEED, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, + cv.Optional(CONF_OFF_PERCENT): cv.int_range(min=0, max=100), + cv.Optional(CONF_MUTE_PERCENT): cv.int_range(min=0, max=100), + cv.Optional(CONF_LOW_PERCENT): cv.int_range(min=0, max=100), + cv.Optional(CONF_MID_PERCENT): cv.int_range(min=0, max=100), + cv.Optional(CONF_HIGH_PERCENT): cv.int_range(min=0, max=100), + cv.Optional(CONF_TURBO_PERCENT): cv.int_range(min=0, max=100), + } + ), cv.Optional(CONF_INVERTER_POWER_LIMIT_VALUE): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, icon=ICON_INVERTER_POWER_LIMIT_VALUE, @@ -406,6 +434,24 @@ async def to_code(config): sens = await text_sensor.new_text_sensor(conf) cg.add(var.set_preset_reporter_sensor(sens)) + if CONF_ACTUAL_FAN_SPEED in config: + conf = config[CONF_ACTUAL_FAN_SPEED] + sens = await sensor.new_sensor(conf) + cg.add(var.set_actual_fan_speed_sensor(sens)) + # Set custom percentages if provided + if CONF_OFF_PERCENT in conf: + cg.add(var.set_fan_speed_off_percent(conf[CONF_OFF_PERCENT])) + if CONF_MUTE_PERCENT in conf: + cg.add(var.set_fan_speed_mute_percent(conf[CONF_MUTE_PERCENT])) + if CONF_LOW_PERCENT in conf: + cg.add(var.set_fan_speed_low_percent(conf[CONF_LOW_PERCENT])) + if CONF_MID_PERCENT in conf: + cg.add(var.set_fan_speed_mid_percent(conf[CONF_MID_PERCENT])) + if CONF_HIGH_PERCENT in conf: + cg.add(var.set_fan_speed_high_percent(conf[CONF_HIGH_PERCENT])) + if CONF_TURBO_PERCENT in conf: + cg.add(var.set_fan_speed_turbo_percent(conf[CONF_TURBO_PERCENT])) + if CONF_INVERTER_POWER_LIMIT_VALUE in config: conf = config[CONF_INVERTER_POWER_LIMIT_VALUE] sens = await sensor.new_sensor(conf) diff --git a/examples/advanced/ac_common.yaml b/examples/advanced/ac_common.yaml index 060be4e..32efca6 100644 --- a/examples/advanced/ac_common.yaml +++ b/examples/advanced/ac_common.yaml @@ -104,6 +104,18 @@ climate: name: ${upper_devicename} Preset Reporter id: ${devicename}_preset_reporter internal: false + actual_fan_speed: + name: ${upper_devicename} Actual Fan Speed + id: ${devicename}_actual_fan_speed + internal: false + # Optional: customize the percentage for each fan speed level + # These are the defaults when both MUTE and TURBO are enabled: + off_percent: 0 + mute_percent: 20 + low_percent: 40 + mid_percent: 60 + high_percent: 80 + turbo_percent: 100 vlouver_state: name: ${upper_devicename} VLouvers State id: ${devicename}_vlouver_state