#ifdef USE_ARDUINO #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "air_conditioner.h" #include "ac_adapter.h" #include #include namespace esphome { namespace midea { namespace ac { static void set_sensor(Sensor *sensor, float value) { if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value)) sensor->publish_state(value); } template void update_property(T &property, const T &value, bool &flag) { if (property != value) { property = value; flag = true; } } void AirConditioner::on_status_change() { bool need_publish = false; update_property(this->target_temperature, this->base_.getTargetTemp(), need_publish); update_property(this->current_temperature, this->base_.getIndoorTemp(), need_publish); auto mode = Converters::to_climate_mode(this->base_.getMode()); update_property(this->mode, mode, need_publish); auto swing_mode = Converters::to_climate_swing_mode(this->base_.getSwingMode()); update_property(this->swing_mode, swing_mode, need_publish); // Preset auto preset = this->base_.getPreset(); if (Converters::is_custom_midea_preset(preset)) { if (this->set_custom_preset_(Converters::to_custom_climate_preset(preset))) need_publish = true; } else if (this->set_preset_(Converters::to_climate_preset(preset))) { need_publish = true; } // Fan mode auto fan_mode = this->base_.getFanMode(); if (Converters::is_custom_midea_fan_mode(fan_mode)) { if (this->set_custom_fan_mode_(Converters::to_custom_climate_fan_mode(fan_mode))) need_publish = true; } else if (this->set_fan_mode_(Converters::to_climate_fan_mode(fan_mode))) { need_publish = true; } if (need_publish) this->publish_state(); set_sensor(this->outdoor_sensor_, this->base_.getOutdoorTemp()); set_sensor(this->power_sensor_, this->base_.getPowerUsage()); set_sensor(this->humidity_sensor_, this->base_.getIndoorHum()); } void AirConditioner::control(const ClimateCall &call) { dudanov::midea::ac::Control ctrl{}; if (call.get_target_temperature().has_value()) ctrl.targetTemp = call.get_target_temperature().value(); if (call.get_swing_mode().has_value()) ctrl.swingMode = Converters::to_midea_swing_mode(call.get_swing_mode().value()); if (call.get_mode().has_value()) ctrl.mode = Converters::to_midea_mode(call.get_mode().value()); if (call.get_preset().has_value()) { ctrl.preset = Converters::to_midea_preset(call.get_preset().value()); } else if (call.get_custom_preset().has_value()) { ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().value()); } if (call.get_fan_mode().has_value()) { ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value()); } else if (call.get_custom_fan_mode().has_value()) { ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().value()); } this->base_.control(ctrl); } ClimateTraits AirConditioner::traits() { auto traits = ClimateTraits(); traits.set_supports_current_temperature(true); traits.set_visual_min_temperature(17); traits.set_visual_max_temperature(30); traits.set_visual_temperature_step(0.5); traits.set_supported_modes(this->supported_modes_); traits.set_supported_swing_modes(this->supported_swing_modes_); traits.set_supported_presets(this->supported_presets_); traits.set_supported_custom_presets(this->supported_custom_presets_); traits.set_supported_custom_fan_modes(this->supported_custom_fan_modes_); /* + MINIMAL SET OF CAPABILITIES */ traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM); traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH); if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK) Converters::to_climate_traits(traits, this->base_.getCapabilities()); if (!traits.get_supported_modes().empty()) traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF); if (!traits.get_supported_swing_modes().empty()) traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF); if (!traits.get_supported_presets().empty()) traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); return traits; } void AirConditioner::dump_config() { ESP_LOGCONFIG(Constants::TAG, "MideaDongle:\n" " [x] Period: %dms\n" " [x] Response timeout: %dms\n" " [x] Request attempts: %d", this->base_.getPeriod(), this->base_.getTimeout(), this->base_.getNumAttempts()); #ifdef USE_REMOTE_TRANSMITTER ESP_LOGCONFIG(Constants::TAG, " [x] Using RemoteTransmitter"); #endif if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK) { this->base_.getCapabilities().dump(); } else if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_ERROR) { ESP_LOGW(Constants::TAG, "Failed to get 0xB5 capabilities report. Suggest to disable it in config and manually set your " "appliance options."); } this->dump_traits_(Constants::TAG); } /* ACTIONS */ void AirConditioner::do_follow_me(float temperature, bool use_fahrenheit, bool beeper) { #ifdef USE_REMOTE_TRANSMITTER // Check if temperature is finite (not NaN or infinite) if (!std::isfinite(temperature)) { ESP_LOGW(Constants::TAG, "Follow me action requires a finite temperature, got: %f", temperature); return; } // Round and convert temperature to long, then clamp and convert it to uint8_t uint8_t temp_uint8 = static_cast(esphome::clamp(std::lroundf(temperature), 0L, static_cast(UINT8_MAX))); char temp_symbol = use_fahrenheit ? 'F' : 'C'; ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %.5f °%c, rounded to: %u °%c", temperature, temp_symbol, temp_uint8, temp_symbol); // Create and transmit the data IrFollowMeData data(temp_uint8, use_fahrenheit, beeper); this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); #endif } void AirConditioner::do_swing_step() { #ifdef USE_REMOTE_TRANSMITTER IrSpecialData data(0x01); this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); #endif } void AirConditioner::do_display_toggle() { if (this->base_.getCapabilities().supportLightControl()) { this->base_.displayToggle(); } else { #ifdef USE_REMOTE_TRANSMITTER IrSpecialData data(0x08); this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); #endif } } } // namespace ac } // namespace midea } // namespace esphome #endif // USE_ARDUINO