Midea support v2 (#2188)

This commit is contained in:
Sergey V. DUDANOV
2021-09-09 01:10:02 +04:00
committed by GitHub
parent 2790d72bff
commit 4e120a291e
33 changed files with 1276 additions and 1226 deletions
+173
View File
@@ -0,0 +1,173 @@
#include "esphome/core/log.h"
#include "adapter.h"
namespace esphome {
namespace midea {
const char *const Constants::TAG = "midea";
const std::string Constants::FREEZE_PROTECTION = "freeze protection";
const std::string Constants::SILENT = "silent";
const std::string Constants::TURBO = "turbo";
ClimateMode Converters::to_climate_mode(MideaMode mode) {
switch (mode) {
case MideaMode::MODE_AUTO:
return ClimateMode::CLIMATE_MODE_HEAT_COOL;
case MideaMode::MODE_COOL:
return ClimateMode::CLIMATE_MODE_COOL;
case MideaMode::MODE_DRY:
return ClimateMode::CLIMATE_MODE_DRY;
case MideaMode::MODE_FAN_ONLY:
return ClimateMode::CLIMATE_MODE_FAN_ONLY;
case MideaMode::MODE_HEAT:
return ClimateMode::CLIMATE_MODE_HEAT;
default:
return ClimateMode::CLIMATE_MODE_OFF;
}
}
MideaMode Converters::to_midea_mode(ClimateMode mode) {
switch (mode) {
case ClimateMode::CLIMATE_MODE_HEAT_COOL:
return MideaMode::MODE_AUTO;
case ClimateMode::CLIMATE_MODE_COOL:
return MideaMode::MODE_COOL;
case ClimateMode::CLIMATE_MODE_DRY:
return MideaMode::MODE_DRY;
case ClimateMode::CLIMATE_MODE_FAN_ONLY:
return MideaMode::MODE_FAN_ONLY;
case ClimateMode::CLIMATE_MODE_HEAT:
return MideaMode::MODE_HEAT;
default:
return MideaMode::MODE_OFF;
}
}
ClimateSwingMode Converters::to_climate_swing_mode(MideaSwingMode mode) {
switch (mode) {
case MideaSwingMode::SWING_VERTICAL:
return ClimateSwingMode::CLIMATE_SWING_VERTICAL;
case MideaSwingMode::SWING_HORIZONTAL:
return ClimateSwingMode::CLIMATE_SWING_HORIZONTAL;
case MideaSwingMode::SWING_BOTH:
return ClimateSwingMode::CLIMATE_SWING_BOTH;
default:
return ClimateSwingMode::CLIMATE_SWING_OFF;
}
}
MideaSwingMode Converters::to_midea_swing_mode(ClimateSwingMode mode) {
switch (mode) {
case ClimateSwingMode::CLIMATE_SWING_VERTICAL:
return MideaSwingMode::SWING_VERTICAL;
case ClimateSwingMode::CLIMATE_SWING_HORIZONTAL:
return MideaSwingMode::SWING_HORIZONTAL;
case ClimateSwingMode::CLIMATE_SWING_BOTH:
return MideaSwingMode::SWING_BOTH;
default:
return MideaSwingMode::SWING_OFF;
}
}
MideaFanMode Converters::to_midea_fan_mode(ClimateFanMode mode) {
switch (mode) {
case ClimateFanMode::CLIMATE_FAN_LOW:
return MideaFanMode::FAN_LOW;
case ClimateFanMode::CLIMATE_FAN_MEDIUM:
return MideaFanMode::FAN_MEDIUM;
case ClimateFanMode::CLIMATE_FAN_HIGH:
return MideaFanMode::FAN_HIGH;
default:
return MideaFanMode::FAN_AUTO;
}
}
ClimateFanMode Converters::to_climate_fan_mode(MideaFanMode mode) {
switch (mode) {
case MideaFanMode::FAN_LOW:
return ClimateFanMode::CLIMATE_FAN_LOW;
case MideaFanMode::FAN_MEDIUM:
return ClimateFanMode::CLIMATE_FAN_MEDIUM;
case MideaFanMode::FAN_HIGH:
return ClimateFanMode::CLIMATE_FAN_HIGH;
default:
return ClimateFanMode::CLIMATE_FAN_AUTO;
}
}
bool Converters::is_custom_midea_fan_mode(MideaFanMode mode) {
switch (mode) {
case MideaFanMode::FAN_SILENT:
case MideaFanMode::FAN_TURBO:
return true;
default:
return false;
}
}
const std::string &Converters::to_custom_climate_fan_mode(MideaFanMode mode) {
switch (mode) {
case MideaFanMode::FAN_SILENT:
return Constants::SILENT;
default:
return Constants::TURBO;
}
}
MideaFanMode Converters::to_midea_fan_mode(const std::string &mode) {
if (mode == Constants::SILENT)
return MideaFanMode::FAN_SILENT;
return MideaFanMode::FAN_TURBO;
}
MideaPreset Converters::to_midea_preset(ClimatePreset preset) {
switch (preset) {
case ClimatePreset::CLIMATE_PRESET_SLEEP:
return MideaPreset::PRESET_SLEEP;
case ClimatePreset::CLIMATE_PRESET_ECO:
return MideaPreset::PRESET_ECO;
case ClimatePreset::CLIMATE_PRESET_BOOST:
return MideaPreset::PRESET_TURBO;
default:
return MideaPreset::PRESET_NONE;
}
}
ClimatePreset Converters::to_climate_preset(MideaPreset preset) {
switch (preset) {
case MideaPreset::PRESET_SLEEP:
return ClimatePreset::CLIMATE_PRESET_SLEEP;
case MideaPreset::PRESET_ECO:
return ClimatePreset::CLIMATE_PRESET_ECO;
case MideaPreset::PRESET_TURBO:
return ClimatePreset::CLIMATE_PRESET_BOOST;
default:
return ClimatePreset::CLIMATE_PRESET_NONE;
}
}
bool Converters::is_custom_midea_preset(MideaPreset preset) { return preset == MideaPreset::PRESET_FREEZE_PROTECTION; }
const std::string &Converters::to_custom_climate_preset(MideaPreset preset) { return Constants::FREEZE_PROTECTION; }
MideaPreset Converters::to_midea_preset(const std::string &preset) { return MideaPreset::PRESET_FREEZE_PROTECTION; }
void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities) {
if (capabilities.supportAutoMode())
traits.add_supported_mode(ClimateMode::CLIMATE_MODE_HEAT_COOL);
if (capabilities.supportCoolMode())
traits.add_supported_mode(ClimateMode::CLIMATE_MODE_COOL);
if (capabilities.supportHeatMode())
traits.add_supported_mode(ClimateMode::CLIMATE_MODE_HEAT);
if (capabilities.supportDryMode())
traits.add_supported_mode(ClimateMode::CLIMATE_MODE_DRY);
if (capabilities.supportTurboPreset())
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_BOOST);
if (capabilities.supportEcoPreset())
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_ECO);
if (capabilities.supportFrostProtectionPreset())
traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION);
}
} // namespace midea
} // namespace esphome
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <Appliance/AirConditioner/AirConditioner.h>
#include "esphome/components/climate/climate_traits.h"
#include "appliance_base.h"
namespace esphome {
namespace midea {
using MideaMode = dudanov::midea::ac::Mode;
using MideaSwingMode = dudanov::midea::ac::SwingMode;
using MideaFanMode = dudanov::midea::ac::FanMode;
using MideaPreset = dudanov::midea::ac::Preset;
class Constants {
public:
static const char *const TAG;
static const std::string FREEZE_PROTECTION;
static const std::string SILENT;
static const std::string TURBO;
};
class Converters {
public:
static MideaMode to_midea_mode(ClimateMode mode);
static ClimateMode to_climate_mode(MideaMode mode);
static MideaSwingMode to_midea_swing_mode(ClimateSwingMode mode);
static ClimateSwingMode to_climate_swing_mode(MideaSwingMode mode);
static MideaPreset to_midea_preset(ClimatePreset preset);
static MideaPreset to_midea_preset(const std::string &preset);
static bool is_custom_midea_preset(MideaPreset preset);
static ClimatePreset to_climate_preset(MideaPreset preset);
static const std::string &to_custom_climate_preset(MideaPreset preset);
static MideaFanMode to_midea_fan_mode(ClimateFanMode fan_mode);
static MideaFanMode to_midea_fan_mode(const std::string &fan_mode);
static bool is_custom_midea_fan_mode(MideaFanMode fan_mode);
static ClimateFanMode to_climate_fan_mode(MideaFanMode fan_mode);
static const std::string &to_custom_climate_fan_mode(MideaFanMode fan_mode);
static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities);
};
} // namespace midea
} // namespace esphome
@@ -0,0 +1,152 @@
#include "esphome/core/log.h"
#include "air_conditioner.h"
#include "adapter.h"
#ifdef USE_REMOTE_TRANSMITTER
#include "midea_ir.h"
#endif
namespace esphome {
namespace midea {
static void set_sensor(Sensor *sensor, float value) {
if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value))
sensor->publish_state(value);
}
template<typename T> 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_mode(ClimateMode::CLIMATE_MODE_OFF);
traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY);
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);
traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF);
traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL);
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE);
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP);
if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK)
Converters::to_climate_traits(traits, this->base_.getCapabilities());
return traits;
}
void AirConditioner::dump_config() {
ESP_LOGCONFIG(Constants::TAG, "MideaDongle:");
ESP_LOGCONFIG(Constants::TAG, " [x] Period: %dms", this->base_.getPeriod());
ESP_LOGCONFIG(Constants::TAG, " [x] Response timeout: %dms", this->base_.getTimeout());
ESP_LOGCONFIG(Constants::TAG, " [x] Request attempts: %d", 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 beeper) {
#ifdef USE_REMOTE_TRANSMITTER
IrFollowMeData data(static_cast<uint8_t>(lroundf(temperature)), beeper);
this->transmit_ir(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->transmit_ir(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->transmit_ir(data);
#else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
#endif
}
}
} // namespace midea
} // namespace esphome
@@ -0,0 +1,41 @@
#pragma once
#include <Appliance/AirConditioner/AirConditioner.h>
#include "appliance_base.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace midea {
using sensor::Sensor;
using climate::ClimateCall;
class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner> {
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 on_status_change() override;
/* ############### */
/* ### ACTIONS ### */
/* ############### */
void do_follow_me(float temperature, bool beeper = false);
void do_display_toggle();
void do_swing_step();
void do_beeper_on() { this->set_beeper_feedback(true); }
void do_beeper_off() { this->set_beeper_feedback(false); }
void do_power_on() { this->base_.setPowerState(true); }
void do_power_off() { this->base_.setPowerState(false); }
protected:
void control(const ClimateCall &call) override;
ClimateTraits traits() override;
Sensor *outdoor_sensor_{nullptr};
Sensor *humidity_sensor_{nullptr};
Sensor *power_sensor_{nullptr};
};
} // namespace midea
} // namespace esphome
+76
View File
@@ -0,0 +1,76 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/climate/climate.h"
#ifdef USE_REMOTE_TRANSMITTER
#include "esphome/components/remote_base/midea_protocol.h"
#include "esphome/components/remote_transmitter/remote_transmitter.h"
#endif
#include <Appliance/ApplianceBase.h>
#include <Helpers/Logger.h>
namespace esphome {
namespace midea {
using climate::ClimatePreset;
using climate::ClimateTraits;
using climate::ClimateMode;
using climate::ClimateSwingMode;
using climate::ClimateFanMode;
template<typename T> class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate {
static_assert(std::is_base_of<dudanov::midea::ApplianceBase, T>::value,
"T must derive from dudanov::midea::ApplianceBase class");
public:
ApplianceBase() {
this->base_.setStream(this);
this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this));
dudanov::midea::ApplianceBase::setLogger([](int level, const char *tag, int line, String format, va_list args) {
esp_log_vprintf_(level, tag, line, format.c_str(), args);
});
}
bool can_proceed() override {
return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
}
float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
void setup() override { this->base_.setup(); }
void loop() override { this->base_.loop(); }
void set_period(uint32_t ms) { this->base_.setPeriod(ms); }
void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); }
void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); }
void set_beeper_feedback(bool state) { this->base_.setBeeper(state); }
void set_autoconf(bool value) { this->base_.setAutoconf(value); }
void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); }
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { this->supported_swing_modes_ = std::move(modes); }
void set_supported_presets(std::set<ClimatePreset> presets) { this->supported_presets_ = std::move(presets); }
void set_custom_presets(std::set<std::string> presets) { this->supported_custom_presets_ = std::move(presets); }
void set_custom_fan_modes(std::set<std::string> modes) { this->supported_custom_fan_modes_ = std::move(modes); }
virtual void on_status_change() = 0;
#ifdef USE_REMOTE_TRANSMITTER
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
this->transmitter_ = transmitter;
}
void transmit_ir(remote_base::MideaData &data) {
data.finalize();
auto transmit = this->transmitter_->transmit();
remote_base::MideaProtocol().encode(transmit.get_data(), data);
transmit.perform();
}
#endif
protected:
T base_;
std::set<ClimateMode> supported_modes_{};
std::set<ClimateSwingMode> supported_swing_modes_{};
std::set<ClimatePreset> supported_presets_{};
std::set<std::string> supported_custom_presets_{};
std::set<std::string> supported_custom_fan_modes_{};
#ifdef USE_REMOTE_TRANSMITTER
remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr};
#endif
};
} // namespace midea
} // namespace esphome
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include "esphome/core/automation.h"
#include "air_conditioner.h"
namespace esphome {
namespace midea {
template<typename... Ts> class MideaActionBase : public Action<Ts...> {
public:
void set_parent(AirConditioner *parent) { this->parent_ = parent; }
protected:
AirConditioner *parent_;
};
template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> {
TEMPLATABLE_VALUE(float, temperature)
TEMPLATABLE_VALUE(bool, beeper)
void play(Ts... x) override {
this->parent_->do_follow_me(this->temperature_.value(x...), this->beeper_.value(x...));
}
};
template<typename... Ts> class SwingStepAction : public MideaActionBase<Ts...> {
public:
void play(Ts... x) override { this->parent_->do_swing_step(); }
};
template<typename... Ts> class DisplayToggleAction : public MideaActionBase<Ts...> {
public:
void play(Ts... x) override { this->parent_->do_display_toggle(); }
};
template<typename... Ts> class BeeperOnAction : public MideaActionBase<Ts...> {
public:
void play(Ts... x) override { this->parent_->do_beeper_on(); }
};
template<typename... Ts> class BeeperOffAction : public MideaActionBase<Ts...> {
public:
void play(Ts... x) override { this->parent_->do_beeper_off(); }
};
template<typename... Ts> class PowerOnAction : public MideaActionBase<Ts...> {
public:
void play(Ts... x) override { this->parent_->do_power_on(); }
};
template<typename... Ts> class PowerOffAction : public MideaActionBase<Ts...> {
public:
void play(Ts... x) override { this->parent_->do_power_off(); }
};
} // namespace midea
} // namespace esphome
+284
View File
@@ -0,0 +1,284 @@
from esphome.core import coroutine
from esphome import automation
from esphome.components import climate, sensor, uart, remote_transmitter
from esphome.components.remote_base import CONF_TRANSMITTER_ID
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import (
CONF_AUTOCONF,
CONF_BEEPER,
CONF_CUSTOM_FAN_MODES,
CONF_CUSTOM_PRESETS,
CONF_ID,
CONF_NUM_ATTEMPTS,
CONF_PERIOD,
CONF_SUPPORTED_MODES,
CONF_SUPPORTED_PRESETS,
CONF_SUPPORTED_SWING_MODES,
CONF_TIMEOUT,
CONF_TEMPERATURE,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
ICON_POWER,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
UNIT_WATT,
)
from esphome.components.climate import (
ClimateMode,
ClimatePreset,
ClimateSwingMode,
)
CODEOWNERS = ["@dudanov"]
DEPENDENCIES = ["climate", "uart", "wifi"]
AUTO_LOAD = ["sensor"]
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
CONF_POWER_USAGE = "power_usage"
CONF_HUMIDITY_SETPOINT = "humidity_setpoint"
midea_ns = cg.esphome_ns.namespace("midea")
AirConditioner = midea_ns.class_("AirConditioner", climate.Climate, cg.Component)
Capabilities = midea_ns.namespace("Constants")
def templatize(value):
if isinstance(value, cv.Schema):
value = value.schema
ret = {}
for key, val in value.items():
ret[key] = cv.templatable(val)
return cv.Schema(ret)
def register_action(name, type_, schema):
validator = templatize(schema).extend(MIDEA_ACTION_BASE_SCHEMA)
registerer = automation.register_action(f"midea_ac.{name}", type_, validator)
def decorator(func):
async def new_func(config, action_id, template_arg, args):
ac_ = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg)
cg.add(var.set_parent(ac_))
await coroutine(func)(var, config, args)
return var
return registerer(new_func)
return decorator
ALLOWED_CLIMATE_MODES = {
"HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
"COOL": ClimateMode.CLIMATE_MODE_COOL,
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
"DRY": ClimateMode.CLIMATE_MODE_DRY,
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
}
ALLOWED_CLIMATE_PRESETS = {
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
}
ALLOWED_CLIMATE_SWING_MODES = {
"BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH,
"VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,
"HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
}
CUSTOM_FAN_MODES = {
"SILENT": Capabilities.SILENT,
"TURBO": Capabilities.TURBO,
}
CUSTOM_PRESETS = {
"FREEZE_PROTECTION": Capabilities.FREEZE_PROTECTION,
}
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)
CONFIG_SCHEMA = cv.All(
climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AirConditioner),
cv.Optional(CONF_PERIOD, default="1s"): cv.time_period,
cv.Optional(CONF_TIMEOUT, default="2s"): cv.time_period,
cv.Optional(CONF_NUM_ATTEMPTS, default=3): cv.int_range(min=1, max=5),
cv.Optional(CONF_TRANSMITTER_ID): cv.use_id(
remote_transmitter.RemoteTransmitterComponent
),
cv.Optional(CONF_BEEPER, default=False): cv.boolean,
cv.Optional(CONF_AUTOCONF, default=True): cv.boolean,
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes),
cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(
validate_swing_modes
),
cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(validate_presets),
cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(validate_custom_presets),
cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list(
validate_custom_fan_modes
),
cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_USAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
icon=ICON_POWER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY_SETPOINT): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
# Actions
FollowMeAction = midea_ns.class_("FollowMeAction", automation.Action)
DisplayToggleAction = midea_ns.class_("DisplayToggleAction", automation.Action)
SwingStepAction = midea_ns.class_("SwingStepAction", automation.Action)
BeeperOnAction = midea_ns.class_("BeeperOnAction", automation.Action)
BeeperOffAction = midea_ns.class_("BeeperOffAction", automation.Action)
PowerOnAction = midea_ns.class_("PowerOnAction", automation.Action)
PowerOffAction = midea_ns.class_("PowerOffAction", automation.Action)
MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.use_id(AirConditioner),
}
)
# FollowMe action
MIDEA_FOLLOW_ME_MIN = 0
MIDEA_FOLLOW_ME_MAX = 37
MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
{
cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean),
}
)
@register_action("follow_me", FollowMeAction, MIDEA_FOLLOW_ME_SCHEMA)
async def follow_me_to_code(var, config, args):
template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_)
cg.add(var.set_beeper(template_))
template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_)
cg.add(var.set_temperature(template_))
# Toggle Display action
@register_action(
"display_toggle",
DisplayToggleAction,
cv.Schema({}),
)
async def display_toggle_to_code(var, config, args):
pass
# Swing Step action
@register_action(
"swing_step",
SwingStepAction,
cv.Schema({}),
)
async def swing_step_to_code(var, config, args):
pass
# Beeper On action
@register_action(
"beeper_on",
BeeperOnAction,
cv.Schema({}),
)
async def beeper_on_to_code(var, config, args):
pass
# Beeper Off action
@register_action(
"beeper_off",
BeeperOffAction,
cv.Schema({}),
)
async def beeper_off_to_code(var, config, args):
pass
# Power On action
@register_action(
"power_on",
PowerOnAction,
cv.Schema({}),
)
async def power_on_to_code(var, config, args):
pass
# Power Off action
@register_action(
"power_off",
PowerOffAction,
cv.Schema({}),
)
async def power_off_to_code(var, config, args):
pass
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
await climate.register_climate(var, config)
cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds))
cg.add(var.set_response_timeout(config[CONF_TIMEOUT].total_milliseconds))
cg.add(var.set_request_attempts(config[CONF_NUM_ATTEMPTS]))
if CONF_TRANSMITTER_ID in config:
cg.add_define("USE_REMOTE_TRANSMITTER")
transmitter_ = await cg.get_variable(config[CONF_TRANSMITTER_ID])
cg.add(var.set_transmitter(transmitter_))
cg.add(var.set_beeper_feedback(config[CONF_BEEPER]))
cg.add(var.set_autoconf(config[CONF_AUTOCONF]))
if CONF_SUPPORTED_MODES in config:
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
if CONF_SUPPORTED_SWING_MODES in config:
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
if CONF_SUPPORTED_PRESETS in config:
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
if CONF_CUSTOM_PRESETS in config:
cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS]))
if CONF_CUSTOM_FAN_MODES in config:
cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES]))
if CONF_OUTDOOR_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE])
cg.add(var.set_outdoor_temperature_sensor(sens))
if CONF_POWER_USAGE in config:
sens = await sensor.new_sensor(config[CONF_POWER_USAGE])
cg.add(var.set_power_sensor(sens))
if CONF_HUMIDITY_SETPOINT in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT])
cg.add(var.set_humidity_setpoint_sensor(sens))
cg.add_library("dudanov/MideaUART", "1.1.5")
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#ifdef USE_REMOTE_TRANSMITTER
#include "esphome/components/remote_base/midea_protocol.h"
namespace esphome {
namespace midea {
using IrData = remote_base::MideaData;
class IrFollowMeData : public IrData {
public:
// Default constructor (temp: 30C, beeper: off)
IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {}
// Copy from Base
IrFollowMeData(const IrData &data) : IrData(data) {}
// Direct from temperature and beeper values
IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() {
this->set_temp(temp);
this->set_beeper(beeper);
}
/* TEMPERATURE */
uint8_t temp() const { return this->data_[4] - 1; }
void set_temp(uint8_t val) { this->data_[4] = std::min(MAX_TEMP, val) + 1; }
/* BEEPER */
bool beeper() const { return this->data_[3] & 128; }
void set_beeper(bool val) { this->set_value_(3, 1, 7, val); }
protected:
static const uint8_t MAX_TEMP = 37;
};
class IrSpecialData : public IrData {
public:
IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {}
};
} // namespace midea
} // namespace esphome
#endif