[midea] Add display_control and send the display toggle over UART without a transmitter (#18671)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
Andres Signes Grimalt
2026-10-13 16:03:18 -10:00
committed by GitHub
co-authored by J. Nick Koston J. Nick Koston
parent 4b8af08155
commit f45e6f0bf4
6 changed files with 75 additions and 7 deletions
+27 -7
View File
@@ -127,6 +127,19 @@ ClimateTraits AirConditioner::traits() {
return traits;
}
#ifdef USE_REMOTE_TRANSMITTER
static const LogString *display_control_str(DisplayControl control) {
switch (control) {
case DisplayControl::DISPLAY_CONTROL_UART:
return LOG_STR("UART");
case DisplayControl::DISPLAY_CONTROL_IR:
return LOG_STR("IR");
default:
return LOG_STR("AUTO");
}
}
#endif
void AirConditioner::dump_config() {
ESP_LOGCONFIG(Constants::TAG,
"MideaDongle:\n"
@@ -135,7 +148,10 @@ void AirConditioner::dump_config() {
" [x] Request attempts: %d",
this->base_.getPeriod(), this->base_.getTimeout(), this->base_.getNumAttempts());
#ifdef USE_REMOTE_TRANSMITTER
ESP_LOGCONFIG(Constants::TAG, " [x] Using RemoteTransmitter");
ESP_LOGCONFIG(Constants::TAG,
" [x] Using RemoteTransmitter\n"
" [x] Display control: %s",
LOG_STR_ARG(display_control_str(this->display_control_)));
#endif
if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK) {
this->base_.getCapabilities().dump();
@@ -183,16 +199,20 @@ void AirConditioner::do_swing_step() {
}
void AirConditioner::do_display_toggle() {
if (this->base_.getCapabilities().supportLightControl()) {
this->base_.displayToggle();
} else {
#ifdef USE_REMOTE_TRANSMITTER
// AUTO uses IR unless the appliance reports light control over UART. Without a transmitter
// built in, every setting sends the UART request: Mundoclima, Rotenso and others accept it
// without advertising it in the 0xB5 capabilities report.
const bool use_ir = this->display_control_ == DisplayControl::DISPLAY_CONTROL_IR ||
(this->display_control_ == DisplayControl::DISPLAY_CONTROL_AUTO &&
!this->base_.getCapabilities().supportLightControl());
if (use_ir) {
IrSpecialData data(0x08);
this->transmitter_.transmit(data);
#else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
#endif
return;
}
#endif
this->base_.displayToggle();
}
} // namespace esphome::midea::ac
@@ -21,12 +21,20 @@ using climate::ClimateModeMask;
using climate::ClimateSwingModeMask;
using climate::ClimatePresetMask;
// How the display toggle reaches the appliance; the AUTO rules are in do_display_toggle()
enum class DisplayControl : uint8_t {
DISPLAY_CONTROL_UART, // always the UART request
DISPLAY_CONTROL_AUTO, // UART when the appliance reports it or no transmitter is built in, else IR
DISPLAY_CONTROL_IR, // always the IR remote, needs a transmitter
};
class AirConditioner final : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate {
public:
void dump_config() override;
void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; }
void set_humidity_setpoint_sensor(Sensor *sensor) { this->humidity_sensor_ = sensor; }
void set_power_sensor(Sensor *sensor) { this->power_sensor_ = sensor; }
void set_display_control(DisplayControl control) { this->display_control_ = control; }
void on_status_change() override;
/* ############### */
@@ -54,6 +62,7 @@ class AirConditioner final : public ApplianceBase<dudanov::midea::ac::AirConditi
ClimateSwingModeMask supported_swing_modes_{};
ClimatePresetMask supported_presets_{};
bool frost_protection_set_{false};
DisplayControl display_control_{DisplayControl::DISPLAY_CONTROL_AUTO};
Sensor *outdoor_sensor_{nullptr};
Sensor *humidity_sensor_{nullptr};
Sensor *power_sensor_{nullptr};
+24
View File
@@ -33,15 +33,18 @@ from esphome.const import (
UNIT_WATT,
)
from esphome.core import CORE
from esphome.types import ConfigType
CODEOWNERS = ["@dudanov"]
DEPENDENCIES = ["climate", "uart"]
AUTO_LOAD = ["sensor"]
CONF_POWER_USAGE = "power_usage"
CONF_HUMIDITY_SETPOINT = "humidity_setpoint"
CONF_DISPLAY_CONTROL = "display_control"
midea_ac_ns = cg.esphome_ns.namespace("midea").namespace("ac")
AirConditioner = midea_ac_ns.class_("AirConditioner", climate.Climate, cg.Component)
Capabilities = midea_ac_ns.namespace("Constants")
DisplayControl = midea_ac_ns.enum("DisplayControl", is_class=True)
def templatize(value):
@@ -82,11 +85,29 @@ CUSTOM_PRESETS = {
"FREEZE_PROTECTION": Capabilities.FREEZE_PROTECTION,
}
# How midea_ac.display_toggle reaches the appliance; see do_display_toggle() for the AUTO rules
DISPLAY_CONTROLS = {
"UART": DisplayControl.DISPLAY_CONTROL_UART,
"AUTO": DisplayControl.DISPLAY_CONTROL_AUTO,
"IR": DisplayControl.DISPLAY_CONTROL_IR,
}
validate_modes = cv.enum(ALLOWED_CLIMATE_MODES, upper=True)
validate_presets = cv.enum(ALLOWED_CLIMATE_PRESETS, upper=True)
validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True)
validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True)
validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True)
validate_display_control = cv.enum(DISPLAY_CONTROLS, upper=True)
def validate_display_control_with_transmitter(config: ConfigType) -> ConfigType:
"""`display_control: IR` needs a `remote_transmitter` to send through; UART and AUTO do not."""
if config.get(CONF_DISPLAY_CONTROL) == "IR" and CONF_TRANSMITTER_ID not in config:
raise cv.Invalid(
f"'{CONF_DISPLAY_CONTROL}: IR' requires '{CONF_TRANSMITTER_ID}' to be set"
)
return config
CONFIG_SCHEMA = cv.All(
climate.climate_schema(AirConditioner)
@@ -100,6 +121,7 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_BEEPER, default=False): cv.boolean,
cv.Optional(CONF_AUTOCONF, default=True): cv.boolean,
cv.Optional(CONF_DISPLAY_CONTROL, default="AUTO"): validate_display_control,
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes),
cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(
validate_swing_modes
@@ -134,6 +156,7 @@ CONFIG_SCHEMA = cv.All(
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
validate_display_control_with_transmitter,
cv.only_on(
[
PLATFORM_ESP32,
@@ -204,6 +227,7 @@ async def to_code(config):
cg.add(var.set_transmitter(transmitter_))
cg.add(var.set_beeper_feedback(config[CONF_BEEPER]))
cg.add(var.set_autoconf(config[CONF_AUTOCONF]))
cg.add(var.set_display_control(config[CONF_DISPLAY_CONTROL]))
if CONF_SUPPORTED_MODES in config:
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
if CONF_SUPPORTED_SWING_MODES in config:
@@ -0,0 +1,6 @@
climate:
- platform: midea
id: midea_uart_only
name: Midea Climate UART Only
autoconf: true
display_control: UART
+1
View File
@@ -33,6 +33,7 @@ climate:
timeout: 2s
beeper: false
autoconf: true
display_control: AUTO
visual:
min_temperature: 17 °C
max_temperature: 30 °C
@@ -0,0 +1,8 @@
packages:
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
midea: !include common-uart_only.yaml
# The MideaUART library includes esp_wifi.h on ESP-IDF
wifi:
ssid: MySSID
password: password1