mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 15:41:52 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a91e6a6bdf | ||
|
|
8600620305 | ||
|
|
96721f305f | ||
|
|
2bf70d7d00 | ||
|
|
1d8c170f48 | ||
|
|
6009c7edb4 | ||
|
|
e3f36c033e | ||
|
|
d4eb0f1655 | ||
|
|
e20ec00071 | ||
|
|
150114d774 | ||
|
|
89dfa5ea82 | ||
|
|
97aa930ad2 | ||
|
|
2a5def10e7 | ||
|
|
969834e037 | ||
|
|
d73a44c504 | ||
|
|
8aec092ab6 | ||
|
|
4fa959ba45 | ||
|
|
b43712d78d | ||
|
|
01904a0f10 | ||
|
|
dd875e7529 | ||
|
|
f1dcf0f0b8 | ||
|
|
a045d001bf | ||
|
|
066c1022d0 | ||
|
|
59c192becc | ||
|
|
a800816750 | ||
|
|
99d9ab4e40 | ||
|
|
f310ca1b74 | ||
|
|
f763daa577 | ||
|
|
970563e07b | ||
|
|
e006045f59 | ||
|
|
fff3645901 | ||
|
|
a5383fd208 |
@@ -394,7 +394,7 @@ def command_update_all(args):
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration)
|
||||
files = list_yaml_files(args.configuration[0])
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
@@ -408,7 +408,7 @@ def command_update_all(args):
|
||||
print("-" * twidth)
|
||||
print()
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
||||
"esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f
|
||||
)
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
|
||||
@@ -514,14 +514,26 @@ def parse_args(argv):
|
||||
|
||||
compat_parser.error = _raise
|
||||
|
||||
try:
|
||||
result, unparsed = compat_parser.parse_known_args(argv[1:])
|
||||
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
|
||||
argv = argv[0:last_option] + [result.command] + result.configuration + unparsed
|
||||
deprecated_argv_suggestion = argv
|
||||
except argparse.ArgumentError:
|
||||
# This is not an old-style command line, so we don't have to do anything.
|
||||
deprecated_argv_suggestion = None
|
||||
deprecated_argv_suggestion = None
|
||||
|
||||
if ["dashboard", "config"] == argv[1:3]:
|
||||
# this is most likely meant in new-style arg format. do not try compat parsing
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
result, unparsed = compat_parser.parse_known_args(argv[1:])
|
||||
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
|
||||
unparsed = [
|
||||
"--device" if arg in ("--upload-port", "--serial-port") else arg
|
||||
for arg in unparsed
|
||||
]
|
||||
argv = (
|
||||
argv[0:last_option] + [result.command] + result.configuration + unparsed
|
||||
)
|
||||
deprecated_argv_suggestion = argv
|
||||
except argparse.ArgumentError:
|
||||
# This is not an old-style command line, so we don't have to do anything.
|
||||
pass
|
||||
|
||||
# And continue on with regular parsing
|
||||
parser = argparse.ArgumentParser(
|
||||
|
||||
@@ -710,13 +710,14 @@ enum ClimateAction {
|
||||
CLIMATE_ACTION_FAN = 6;
|
||||
}
|
||||
enum ClimatePreset {
|
||||
CLIMATE_PRESET_ECO = 0;
|
||||
CLIMATE_PRESET_AWAY = 1;
|
||||
CLIMATE_PRESET_BOOST = 2;
|
||||
CLIMATE_PRESET_COMFORT = 3;
|
||||
CLIMATE_PRESET_HOME = 4;
|
||||
CLIMATE_PRESET_SLEEP = 5;
|
||||
CLIMATE_PRESET_ACTIVITY = 6;
|
||||
CLIMATE_PRESET_NONE = 0;
|
||||
CLIMATE_PRESET_HOME = 1;
|
||||
CLIMATE_PRESET_AWAY = 2;
|
||||
CLIMATE_PRESET_BOOST = 3;
|
||||
CLIMATE_PRESET_COMFORT = 4;
|
||||
CLIMATE_PRESET_ECO = 5;
|
||||
CLIMATE_PRESET_SLEEP = 6;
|
||||
CLIMATE_PRESET_ACTIVITY = 7;
|
||||
}
|
||||
message ListEntitiesClimateResponse {
|
||||
option (id) = 46;
|
||||
@@ -734,7 +735,9 @@ message ListEntitiesClimateResponse {
|
||||
float visual_min_temperature = 8;
|
||||
float visual_max_temperature = 9;
|
||||
float visual_temperature_step = 10;
|
||||
bool supports_away = 11;
|
||||
// for older peer versions - in new system this
|
||||
// is if CLIMATE_PRESET_AWAY exists is supported_presets
|
||||
bool legacy_supports_away = 11;
|
||||
bool supports_action = 12;
|
||||
repeated ClimateFanMode supported_fan_modes = 13;
|
||||
repeated ClimateSwingMode supported_swing_modes = 14;
|
||||
@@ -754,7 +757,8 @@ message ClimateStateResponse {
|
||||
float target_temperature = 4;
|
||||
float target_temperature_low = 5;
|
||||
float target_temperature_high = 6;
|
||||
bool away = 7;
|
||||
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
|
||||
bool legacy_away = 7;
|
||||
ClimateAction action = 8;
|
||||
ClimateFanMode fan_mode = 9;
|
||||
ClimateSwingMode swing_mode = 10;
|
||||
@@ -777,8 +781,9 @@ message ClimateCommandRequest {
|
||||
float target_temperature_low = 7;
|
||||
bool has_target_temperature_high = 8;
|
||||
float target_temperature_high = 9;
|
||||
bool has_away = 10;
|
||||
bool away = 11;
|
||||
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
|
||||
bool has_legacy_away = 10;
|
||||
bool legacy_away = 11;
|
||||
bool has_fan_mode = 12;
|
||||
ClimateFanMode fan_mode = 13;
|
||||
bool has_swing_mode = 14;
|
||||
|
||||
@@ -475,14 +475,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
} else {
|
||||
resp.target_temperature = climate->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_away())
|
||||
resp.away = climate->away;
|
||||
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
|
||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value())
|
||||
resp.custom_fan_mode = climate->custom_fan_mode.value();
|
||||
if (traits.get_supports_presets() && climate->preset.has_value())
|
||||
if (traits.get_supports_presets() && climate->preset.has_value()) {
|
||||
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
|
||||
resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY;
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
|
||||
resp.custom_preset = climate->custom_preset.value();
|
||||
if (traits.get_supports_swing_modes())
|
||||
@@ -498,40 +498,26 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
msg.unique_id = get_default_unique_id("climate", climate);
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
for (auto mode :
|
||||
{climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT,
|
||||
climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_HEAT_COOL}) {
|
||||
if (traits.supports_mode(mode))
|
||||
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
||||
}
|
||||
|
||||
for (auto mode : traits.get_supported_modes())
|
||||
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
||||
|
||||
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
||||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
||||
msg.supports_away = traits.get_supports_away();
|
||||
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
|
||||
msg.supports_action = traits.get_supports_action();
|
||||
for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO,
|
||||
climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
|
||||
climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) {
|
||||
if (traits.supports_fan_mode(fan_mode))
|
||||
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
|
||||
}
|
||||
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) {
|
||||
|
||||
for (auto fan_mode : traits.get_supported_fan_modes())
|
||||
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
|
||||
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
|
||||
msg.supported_custom_fan_modes.push_back(custom_fan_mode);
|
||||
}
|
||||
for (auto preset : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST,
|
||||
climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP,
|
||||
climate::CLIMATE_PRESET_ACTIVITY}) {
|
||||
if (traits.supports_preset(preset))
|
||||
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
|
||||
}
|
||||
for (auto const &custom_preset : traits.get_supported_custom_presets()) {
|
||||
for (auto preset : traits.get_supported_presets())
|
||||
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
|
||||
for (auto const &custom_preset : traits.get_supported_custom_presets())
|
||||
msg.supported_custom_presets.push_back(custom_preset);
|
||||
}
|
||||
for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL}) {
|
||||
if (traits.supports_swing_mode(swing_mode))
|
||||
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
|
||||
}
|
||||
for (auto swing_mode : traits.get_supported_swing_modes())
|
||||
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
|
||||
return this->send_list_entities_climate_response(msg);
|
||||
}
|
||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
@@ -548,8 +534,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
call.set_target_temperature_low(msg.target_temperature_low);
|
||||
if (msg.has_target_temperature_high)
|
||||
call.set_target_temperature_high(msg.target_temperature_high);
|
||||
if (msg.has_away)
|
||||
call.set_away(msg.away);
|
||||
if (msg.has_legacy_away)
|
||||
call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME);
|
||||
if (msg.has_fan_mode)
|
||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||
if (msg.has_custom_fan_mode)
|
||||
@@ -629,7 +615,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 4;
|
||||
resp.api_version_minor = 5;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
return resp;
|
||||
|
||||
@@ -192,16 +192,18 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::ClimatePreset value) {
|
||||
switch (value) {
|
||||
case enums::CLIMATE_PRESET_ECO:
|
||||
return "CLIMATE_PRESET_ECO";
|
||||
case enums::CLIMATE_PRESET_NONE:
|
||||
return "CLIMATE_PRESET_NONE";
|
||||
case enums::CLIMATE_PRESET_HOME:
|
||||
return "CLIMATE_PRESET_HOME";
|
||||
case enums::CLIMATE_PRESET_AWAY:
|
||||
return "CLIMATE_PRESET_AWAY";
|
||||
case enums::CLIMATE_PRESET_BOOST:
|
||||
return "CLIMATE_PRESET_BOOST";
|
||||
case enums::CLIMATE_PRESET_COMFORT:
|
||||
return "CLIMATE_PRESET_COMFORT";
|
||||
case enums::CLIMATE_PRESET_HOME:
|
||||
return "CLIMATE_PRESET_HOME";
|
||||
case enums::CLIMATE_PRESET_ECO:
|
||||
return "CLIMATE_PRESET_ECO";
|
||||
case enums::CLIMATE_PRESET_SLEEP:
|
||||
return "CLIMATE_PRESET_SLEEP";
|
||||
case enums::CLIMATE_PRESET_ACTIVITY:
|
||||
@@ -2672,7 +2674,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->supports_away = value.as_bool();
|
||||
this->legacy_supports_away = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
@@ -2756,7 +2758,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_float(8, this->visual_min_temperature);
|
||||
buffer.encode_float(9, this->visual_max_temperature);
|
||||
buffer.encode_float(10, this->visual_temperature_step);
|
||||
buffer.encode_bool(11, this->supports_away);
|
||||
buffer.encode_bool(11, this->legacy_supports_away);
|
||||
buffer.encode_bool(12, this->supports_action);
|
||||
for (auto &it : this->supported_fan_modes) {
|
||||
buffer.encode_enum<enums::ClimateFanMode>(13, it, true);
|
||||
@@ -2823,8 +2825,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_away: ");
|
||||
out.append(YESNO(this->supports_away));
|
||||
out.append(" legacy_supports_away: ");
|
||||
out.append(YESNO(this->legacy_supports_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_action: ");
|
||||
@@ -2869,7 +2871,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->away = value.as_bool();
|
||||
this->legacy_away = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
@@ -2939,7 +2941,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_float(4, this->target_temperature);
|
||||
buffer.encode_float(5, this->target_temperature_low);
|
||||
buffer.encode_float(6, this->target_temperature_high);
|
||||
buffer.encode_bool(7, this->away);
|
||||
buffer.encode_bool(7, this->legacy_away);
|
||||
buffer.encode_enum<enums::ClimateAction>(8, this->action);
|
||||
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
|
||||
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
|
||||
@@ -2979,8 +2981,8 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" away: ");
|
||||
out.append(YESNO(this->away));
|
||||
out.append(" legacy_away: ");
|
||||
out.append(YESNO(this->legacy_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" action: ");
|
||||
@@ -3031,11 +3033,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->has_away = value.as_bool();
|
||||
this->has_legacy_away = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->away = value.as_bool();
|
||||
this->legacy_away = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
@@ -3120,8 +3122,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_float(7, this->target_temperature_low);
|
||||
buffer.encode_bool(8, this->has_target_temperature_high);
|
||||
buffer.encode_float(9, this->target_temperature_high);
|
||||
buffer.encode_bool(10, this->has_away);
|
||||
buffer.encode_bool(11, this->away);
|
||||
buffer.encode_bool(10, this->has_legacy_away);
|
||||
buffer.encode_bool(11, this->legacy_away);
|
||||
buffer.encode_bool(12, this->has_fan_mode);
|
||||
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
|
||||
buffer.encode_bool(14, this->has_swing_mode);
|
||||
@@ -3176,12 +3178,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_away: ");
|
||||
out.append(YESNO(this->has_away));
|
||||
out.append(" has_legacy_away: ");
|
||||
out.append(YESNO(this->has_legacy_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" away: ");
|
||||
out.append(YESNO(this->away));
|
||||
out.append(" legacy_away: ");
|
||||
out.append(YESNO(this->legacy_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_fan_mode: ");
|
||||
|
||||
@@ -90,13 +90,14 @@ enum ClimateAction : uint32_t {
|
||||
CLIMATE_ACTION_FAN = 6,
|
||||
};
|
||||
enum ClimatePreset : uint32_t {
|
||||
CLIMATE_PRESET_ECO = 0,
|
||||
CLIMATE_PRESET_AWAY = 1,
|
||||
CLIMATE_PRESET_BOOST = 2,
|
||||
CLIMATE_PRESET_COMFORT = 3,
|
||||
CLIMATE_PRESET_HOME = 4,
|
||||
CLIMATE_PRESET_SLEEP = 5,
|
||||
CLIMATE_PRESET_ACTIVITY = 6,
|
||||
CLIMATE_PRESET_NONE = 0,
|
||||
CLIMATE_PRESET_HOME = 1,
|
||||
CLIMATE_PRESET_AWAY = 2,
|
||||
CLIMATE_PRESET_BOOST = 3,
|
||||
CLIMATE_PRESET_COMFORT = 4,
|
||||
CLIMATE_PRESET_ECO = 5,
|
||||
CLIMATE_PRESET_SLEEP = 6,
|
||||
CLIMATE_PRESET_ACTIVITY = 7,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
@@ -709,7 +710,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
float visual_min_temperature{0.0f};
|
||||
float visual_max_temperature{0.0f};
|
||||
float visual_temperature_step{0.0f};
|
||||
bool supports_away{false};
|
||||
bool legacy_supports_away{false};
|
||||
bool supports_action{false};
|
||||
std::vector<enums::ClimateFanMode> supported_fan_modes{};
|
||||
std::vector<enums::ClimateSwingMode> supported_swing_modes{};
|
||||
@@ -732,7 +733,7 @@ class ClimateStateResponse : public ProtoMessage {
|
||||
float target_temperature{0.0f};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
bool away{false};
|
||||
bool legacy_away{false};
|
||||
enums::ClimateAction action{};
|
||||
enums::ClimateFanMode fan_mode{};
|
||||
enums::ClimateSwingMode swing_mode{};
|
||||
@@ -758,8 +759,8 @@ class ClimateCommandRequest : public ProtoMessage {
|
||||
float target_temperature_low{0.0f};
|
||||
bool has_target_temperature_high{false};
|
||||
float target_temperature_high{0.0f};
|
||||
bool has_away{false};
|
||||
bool away{false};
|
||||
bool has_legacy_away{false};
|
||||
bool legacy_away{false};
|
||||
bool has_fan_mode{false};
|
||||
enums::ClimateFanMode fan_mode{};
|
||||
bool has_swing_mode{false};
|
||||
|
||||
@@ -21,7 +21,7 @@ void BangBangClimate::setup() {
|
||||
restore->to_call(this).perform();
|
||||
} else {
|
||||
// restore from defaults, change_away handles those for us
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
this->change_away_(false);
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,8 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
|
||||
this->target_temperature_low = *call.get_target_temperature_low();
|
||||
if (call.get_target_temperature_high().has_value())
|
||||
this->target_temperature_high = *call.get_target_temperature_high();
|
||||
if (call.get_away().has_value())
|
||||
this->change_away_(*call.get_away());
|
||||
if (call.get_preset().has_value())
|
||||
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
|
||||
|
||||
this->compute_state_();
|
||||
this->publish_state();
|
||||
@@ -41,16 +41,25 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
|
||||
climate::ClimateTraits BangBangClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(true);
|
||||
traits.set_supports_auto_mode(true);
|
||||
traits.set_supports_cool_mode(this->supports_cool_);
|
||||
traits.set_supports_heat_mode(this->supports_heat_);
|
||||
traits.set_supported_modes({
|
||||
climate::CLIMATE_MODE_OFF,
|
||||
climate::CLIMATE_MODE_HEAT_COOL,
|
||||
});
|
||||
if (supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
traits.set_supports_two_point_target_temperature(true);
|
||||
traits.set_supports_away(this->supports_away_);
|
||||
if (supports_away_)
|
||||
traits.set_supported_presets({
|
||||
climate::CLIMATE_PRESET_HOME,
|
||||
climate::CLIMATE_PRESET_AWAY,
|
||||
});
|
||||
traits.set_supports_action(true);
|
||||
return traits;
|
||||
}
|
||||
void BangBangClimate::compute_state_() {
|
||||
if (this->mode != climate::CLIMATE_MODE_AUTO) {
|
||||
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
// in non-auto mode, switch directly to appropriate action
|
||||
// - HEAT mode -> HEATING action
|
||||
// - COOL mode -> COOLING action
|
||||
@@ -140,7 +149,7 @@ void BangBangClimate::change_away_(bool away) {
|
||||
this->target_temperature_low = this->away_config_.default_temperature_low;
|
||||
this->target_temperature_high = this->away_config_.default_temperature_high;
|
||||
}
|
||||
this->away = away;
|
||||
this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME;
|
||||
}
|
||||
void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) {
|
||||
this->normal_config_ = normal_config;
|
||||
|
||||
@@ -36,7 +36,7 @@ ClimateTraits = climate_ns.class_("ClimateTraits")
|
||||
ClimateMode = climate_ns.enum("ClimateMode")
|
||||
CLIMATE_MODES = {
|
||||
"OFF": ClimateMode.CLIMATE_MODE_OFF,
|
||||
"HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL,
|
||||
"HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
|
||||
"COOL": ClimateMode.CLIMATE_MODE_COOL,
|
||||
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
|
||||
"DRY": ClimateMode.CLIMATE_MODE_DRY,
|
||||
|
||||
@@ -27,7 +27,9 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
call.set_target_temperature(this->target_temperature_.optional_value(x...));
|
||||
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
|
||||
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
|
||||
call.set_away(this->away_.optional_value(x...));
|
||||
if (away_.has_value()) {
|
||||
call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME);
|
||||
}
|
||||
call.set_fan_mode(this->fan_mode_.optional_value(x...));
|
||||
call.set_fan_mode(this->custom_fan_mode_.optional_value(x...));
|
||||
call.set_preset(this->preset_.optional_value(x...));
|
||||
|
||||
@@ -43,9 +43,6 @@ void ClimateCall::perform() {
|
||||
if (this->target_temperature_high_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
|
||||
}
|
||||
if (this->away_.has_value()) {
|
||||
ESP_LOGD(TAG, " Away Mode: %s", ONOFF(*this->away_));
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
void ClimateCall::validate_() {
|
||||
@@ -125,12 +122,6 @@ void ClimateCall::validate_() {
|
||||
this->target_temperature_high_.reset();
|
||||
}
|
||||
}
|
||||
if (this->away_.has_value()) {
|
||||
if (!traits.get_supports_away()) {
|
||||
ESP_LOGW(TAG, " Cannot set away mode for this device!");
|
||||
this->away_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
ClimateCall &ClimateCall::set_mode(ClimateMode mode) {
|
||||
this->mode_ = mode;
|
||||
@@ -181,8 +172,7 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
|
||||
} else {
|
||||
auto custom_fan_modes = this->parent_->get_traits().get_supported_custom_fan_modes();
|
||||
if (std::find(custom_fan_modes.begin(), custom_fan_modes.end(), fan_mode) != custom_fan_modes.end()) {
|
||||
if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
|
||||
this->custom_fan_mode_ = fan_mode;
|
||||
this->fan_mode_.reset();
|
||||
} else {
|
||||
@@ -218,8 +208,7 @@ ClimateCall &ClimateCall::set_preset(const std::string &preset) {
|
||||
} else if (str_equals_case_insensitive(preset, "ACTIVITY")) {
|
||||
this->set_preset(CLIMATE_PRESET_ACTIVITY);
|
||||
} else {
|
||||
auto custom_presets = this->parent_->get_traits().get_supported_custom_presets();
|
||||
if (std::find(custom_presets.begin(), custom_presets.end(), preset) != custom_presets.end()) {
|
||||
if (this->parent_->get_traits().supports_custom_preset(preset)) {
|
||||
this->custom_preset_ = preset;
|
||||
this->preset_.reset();
|
||||
} else {
|
||||
@@ -269,18 +258,23 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_;
|
||||
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
|
||||
const optional<bool> &ClimateCall::get_away() const { return this->away_; }
|
||||
optional<bool> ClimateCall::get_away() const {
|
||||
if (!this->preset_.has_value())
|
||||
return {};
|
||||
return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY;
|
||||
}
|
||||
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
|
||||
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
||||
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
|
||||
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
|
||||
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
|
||||
ClimateCall &ClimateCall::set_away(bool away) {
|
||||
this->away_ = away;
|
||||
this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_away(optional<bool> away) {
|
||||
this->away_ = away;
|
||||
if (away.has_value())
|
||||
this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
|
||||
@@ -338,20 +332,17 @@ void Climate::save_state_() {
|
||||
} else {
|
||||
state.target_temperature = this->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_away()) {
|
||||
state.away = this->away;
|
||||
}
|
||||
if (traits.get_supports_fan_modes() && fan_mode.has_value()) {
|
||||
state.uses_custom_fan_mode = false;
|
||||
state.fan_mode = this->fan_mode.value();
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
|
||||
state.uses_custom_fan_mode = true;
|
||||
auto &custom_fan_modes = traits.get_supported_custom_fan_modes();
|
||||
auto it = std::find(custom_fan_modes.begin(), custom_fan_modes.end(), this->custom_fan_mode.value());
|
||||
// only set custom fan mode if value exists, otherwise leave it as is
|
||||
if (it != custom_fan_modes.cend()) {
|
||||
state.custom_fan_mode = std::distance(custom_fan_modes.begin(), it);
|
||||
const auto &supported = traits.get_supported_custom_fan_modes();
|
||||
std::vector<std::string> vec{supported.begin(), supported.end()};
|
||||
auto it = std::find(vec.begin(), vec.end(), custom_fan_mode);
|
||||
if (it != vec.end()) {
|
||||
state.custom_fan_mode = std::distance(vec.begin(), it);
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_presets() && preset.has_value()) {
|
||||
@@ -360,11 +351,12 @@ void Climate::save_state_() {
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) {
|
||||
state.uses_custom_preset = true;
|
||||
auto custom_presets = traits.get_supported_custom_presets();
|
||||
auto it = std::find(custom_presets.begin(), custom_presets.end(), this->custom_preset.value());
|
||||
const auto &supported = traits.get_supported_custom_presets();
|
||||
std::vector<std::string> vec{supported.begin(), supported.end()};
|
||||
auto it = std::find(vec.begin(), vec.end(), custom_preset);
|
||||
// only set custom preset if value exists, otherwise leave it as is
|
||||
if (it != custom_presets.cend()) {
|
||||
state.custom_preset = std::distance(custom_presets.begin(), it);
|
||||
if (it != vec.cend()) {
|
||||
state.custom_preset = std::distance(vec.begin(), it);
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
@@ -405,9 +397,6 @@ void Climate::publish_state() {
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature);
|
||||
}
|
||||
if (traits.get_supports_away()) {
|
||||
ESP_LOGD(TAG, " Away: %s", ONOFF(this->away));
|
||||
}
|
||||
|
||||
// Send state to frontend
|
||||
this->state_callback_.call();
|
||||
@@ -453,9 +442,6 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
||||
} else {
|
||||
call.set_target_temperature(this->target_temperature);
|
||||
}
|
||||
if (traits.get_supports_away()) {
|
||||
call.set_away(this->away);
|
||||
}
|
||||
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||
call.set_fan_mode(this->fan_mode);
|
||||
}
|
||||
@@ -476,23 +462,27 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
|
||||
} else {
|
||||
climate->target_temperature = this->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_away()) {
|
||||
climate->away = this->away;
|
||||
}
|
||||
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
|
||||
climate->fan_mode = this->fan_mode;
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) {
|
||||
climate->custom_fan_mode = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
|
||||
// std::set has consistent order (lexicographic for strings), so this is ok
|
||||
const auto &modes = traits.get_supported_custom_fan_modes();
|
||||
std::vector<std::string> modes_vec{modes.begin(), modes.end()};
|
||||
if (custom_fan_mode < modes_vec.size()) {
|
||||
climate->custom_fan_mode = modes_vec[this->custom_fan_mode];
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_presets() && !this->uses_custom_preset) {
|
||||
climate->preset = this->preset;
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && this->uses_custom_preset) {
|
||||
climate->custom_preset = traits.get_supported_custom_presets()[this->custom_preset];
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) {
|
||||
climate->custom_preset = traits.get_supported_custom_presets()[this->preset];
|
||||
// std::set has consistent order (lexicographic for strings), so this is ok
|
||||
const auto &presets = traits.get_supported_custom_presets();
|
||||
std::vector<std::string> presets_vec{presets.begin(), presets.end()};
|
||||
if (custom_preset < presets_vec.size()) {
|
||||
climate->custom_preset = presets_vec[this->custom_preset];
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
climate->swing_mode = this->swing_mode;
|
||||
|
||||
@@ -63,7 +63,9 @@ class ClimateCall {
|
||||
* For climate devices with two point target temperature control
|
||||
*/
|
||||
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
|
||||
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead")
|
||||
ClimateCall &set_away(bool away);
|
||||
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead")
|
||||
ClimateCall &set_away(optional<bool> away);
|
||||
/// Set the fan mode of the climate device.
|
||||
ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
|
||||
@@ -94,7 +96,8 @@ class ClimateCall {
|
||||
const optional<float> &get_target_temperature() const;
|
||||
const optional<float> &get_target_temperature_low() const;
|
||||
const optional<float> &get_target_temperature_high() const;
|
||||
const optional<bool> &get_away() const;
|
||||
ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead")
|
||||
optional<bool> get_away() const;
|
||||
const optional<ClimateFanMode> &get_fan_mode() const;
|
||||
const optional<ClimateSwingMode> &get_swing_mode() const;
|
||||
const optional<std::string> &get_custom_fan_mode() const;
|
||||
@@ -109,7 +112,6 @@ class ClimateCall {
|
||||
optional<float> target_temperature_;
|
||||
optional<float> target_temperature_low_;
|
||||
optional<float> target_temperature_high_;
|
||||
optional<bool> away_;
|
||||
optional<ClimateFanMode> fan_mode_;
|
||||
optional<ClimateSwingMode> swing_mode_;
|
||||
optional<std::string> custom_fan_mode_;
|
||||
@@ -120,7 +122,6 @@ class ClimateCall {
|
||||
/// Struct used to save the state of the climate device in restore memory.
|
||||
struct ClimateDeviceRestoreState {
|
||||
ClimateMode mode;
|
||||
bool away;
|
||||
bool uses_custom_fan_mode{false};
|
||||
union {
|
||||
ClimateFanMode fan_mode;
|
||||
@@ -159,7 +160,7 @@ struct ClimateDeviceRestoreState {
|
||||
*
|
||||
* The entire state of the climate device is encoded in public properties of the base class (current_temperature,
|
||||
* mode etc). These are read-only for the user and rw for integrations. The reason these are public
|
||||
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...`
|
||||
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...`
|
||||
*/
|
||||
class Climate : public Nameable {
|
||||
public:
|
||||
@@ -191,6 +192,7 @@ class Climate : public Nameable {
|
||||
* Away allows climate devices to have two different target temperature configs:
|
||||
* one for normal mode and one for away mode.
|
||||
*/
|
||||
ESPDEPRECATED("away is deprecated, use preset instead")
|
||||
bool away{false};
|
||||
|
||||
/// The active fan mode of the climate device.
|
||||
|
||||
@@ -84,6 +84,10 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
|
||||
|
||||
const char *climate_preset_to_string(ClimatePreset preset) {
|
||||
switch (preset) {
|
||||
case climate::CLIMATE_PRESET_NONE:
|
||||
return "NONE";
|
||||
case climate::CLIMATE_PRESET_HOME:
|
||||
return "HOME";
|
||||
case climate::CLIMATE_PRESET_ECO:
|
||||
return "ECO";
|
||||
case climate::CLIMATE_PRESET_AWAY:
|
||||
@@ -92,8 +96,6 @@ const char *climate_preset_to_string(ClimatePreset preset) {
|
||||
return "BOOST";
|
||||
case climate::CLIMATE_PRESET_COMFORT:
|
||||
return "COMFORT";
|
||||
case climate::CLIMATE_PRESET_HOME:
|
||||
return "HOME";
|
||||
case climate::CLIMATE_PRESET_SLEEP:
|
||||
return "SLEEP";
|
||||
case climate::CLIMATE_PRESET_ACTIVITY:
|
||||
|
||||
@@ -39,7 +39,6 @@ enum ClimateAction : uint8_t {
|
||||
CLIMATE_ACTION_FAN = 6,
|
||||
};
|
||||
|
||||
/// Enum for all modes a climate fan can be in
|
||||
enum ClimateFanMode : uint8_t {
|
||||
/// The fan mode is set to On
|
||||
CLIMATE_FAN_ON = 0,
|
||||
@@ -75,20 +74,22 @@ enum ClimateSwingMode : uint8_t {
|
||||
|
||||
/// Enum for all modes a climate swing can be in
|
||||
enum ClimatePreset : uint8_t {
|
||||
/// Preset is set to ECO
|
||||
CLIMATE_PRESET_ECO = 0,
|
||||
/// Preset is set to AWAY
|
||||
CLIMATE_PRESET_AWAY = 1,
|
||||
/// Preset is set to BOOST
|
||||
CLIMATE_PRESET_BOOST = 2,
|
||||
/// Preset is set to COMFORT
|
||||
CLIMATE_PRESET_COMFORT = 3,
|
||||
/// Preset is set to HOME
|
||||
CLIMATE_PRESET_HOME = 4,
|
||||
/// Preset is set to SLEEP
|
||||
CLIMATE_PRESET_SLEEP = 5,
|
||||
/// Preset is set to ACTIVITY
|
||||
CLIMATE_PRESET_ACTIVITY = 6,
|
||||
/// No preset is active
|
||||
CLIMATE_PRESET_NONE = 0,
|
||||
/// Device is in home preset
|
||||
CLIMATE_PRESET_HOME = 1,
|
||||
/// Device is in away preset
|
||||
CLIMATE_PRESET_AWAY = 2,
|
||||
/// Device is in boost preset
|
||||
CLIMATE_PRESET_BOOST = 3,
|
||||
/// Device is in comfort preset
|
||||
CLIMATE_PRESET_COMFORT = 4,
|
||||
/// Device is running an energy-saving preset
|
||||
CLIMATE_PRESET_ECO = 5,
|
||||
/// Device is prepared for sleep
|
||||
CLIMATE_PRESET_SLEEP = 6,
|
||||
/// Device is reacting to activity (e.g., movement sensors)
|
||||
CLIMATE_PRESET_ACTIVITY = 7,
|
||||
};
|
||||
|
||||
/// Convert the given ClimateMode to a human-readable string.
|
||||
|
||||
@@ -4,50 +4,6 @@
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
bool ClimateTraits::supports_mode(ClimateMode mode) const {
|
||||
switch (mode) {
|
||||
case CLIMATE_MODE_OFF:
|
||||
return true;
|
||||
case CLIMATE_MODE_AUTO:
|
||||
return this->supports_auto_mode_;
|
||||
case CLIMATE_MODE_COOL:
|
||||
return this->supports_cool_mode_;
|
||||
case CLIMATE_MODE_HEAT:
|
||||
return this->supports_heat_mode_;
|
||||
case CLIMATE_MODE_FAN_ONLY:
|
||||
return this->supports_fan_only_mode_;
|
||||
case CLIMATE_MODE_DRY:
|
||||
return this->supports_dry_mode_;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ClimateTraits::get_supports_current_temperature() const { return supports_current_temperature_; }
|
||||
void ClimateTraits::set_supports_current_temperature(bool supports_current_temperature) {
|
||||
supports_current_temperature_ = supports_current_temperature;
|
||||
}
|
||||
bool ClimateTraits::get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
|
||||
void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
|
||||
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
|
||||
}
|
||||
void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; }
|
||||
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; }
|
||||
void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
|
||||
void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) {
|
||||
supports_fan_only_mode_ = supports_fan_only_mode;
|
||||
}
|
||||
void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; }
|
||||
void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; }
|
||||
void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; }
|
||||
float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; }
|
||||
void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) {
|
||||
visual_min_temperature_ = visual_min_temperature;
|
||||
}
|
||||
float ClimateTraits::get_visual_max_temperature() const { return visual_max_temperature_; }
|
||||
void ClimateTraits::set_visual_max_temperature(float visual_max_temperature) {
|
||||
visual_max_temperature_ = visual_max_temperature;
|
||||
}
|
||||
float ClimateTraits::get_visual_temperature_step() const { return visual_temperature_step_; }
|
||||
int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
|
||||
// use printf %g to find number of digits based on temperature step
|
||||
char buf[32];
|
||||
@@ -59,160 +15,6 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
|
||||
|
||||
return str.length() - dot_pos - 1;
|
||||
}
|
||||
void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
|
||||
bool ClimateTraits::get_supports_away() const { return supports_away_; }
|
||||
bool ClimateTraits::get_supports_action() const { return supports_action_; }
|
||||
|
||||
void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) {
|
||||
this->supports_fan_mode_on_ = supports_fan_mode_on;
|
||||
}
|
||||
void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) {
|
||||
this->supports_fan_mode_off_ = supports_fan_mode_off;
|
||||
}
|
||||
void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) {
|
||||
this->supports_fan_mode_auto_ = supports_fan_mode_auto;
|
||||
}
|
||||
void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) {
|
||||
this->supports_fan_mode_low_ = supports_fan_mode_low;
|
||||
}
|
||||
void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) {
|
||||
this->supports_fan_mode_medium_ = supports_fan_mode_medium;
|
||||
}
|
||||
void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) {
|
||||
this->supports_fan_mode_high_ = supports_fan_mode_high;
|
||||
}
|
||||
void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) {
|
||||
this->supports_fan_mode_middle_ = supports_fan_mode_middle;
|
||||
}
|
||||
void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) {
|
||||
this->supports_fan_mode_focus_ = supports_fan_mode_focus;
|
||||
}
|
||||
void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) {
|
||||
this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse;
|
||||
}
|
||||
bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const {
|
||||
switch (fan_mode) {
|
||||
case climate::CLIMATE_FAN_ON:
|
||||
return this->supports_fan_mode_on_;
|
||||
case climate::CLIMATE_FAN_OFF:
|
||||
return this->supports_fan_mode_off_;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
return this->supports_fan_mode_auto_;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
return this->supports_fan_mode_low_;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
return this->supports_fan_mode_medium_;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
return this->supports_fan_mode_high_;
|
||||
case climate::CLIMATE_FAN_MIDDLE:
|
||||
return this->supports_fan_mode_middle_;
|
||||
case climate::CLIMATE_FAN_FOCUS:
|
||||
return this->supports_fan_mode_focus_;
|
||||
case climate::CLIMATE_FAN_DIFFUSE:
|
||||
return this->supports_fan_mode_diffuse_;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ClimateTraits::get_supports_fan_modes() const {
|
||||
return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ ||
|
||||
this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ ||
|
||||
this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_;
|
||||
}
|
||||
void ClimateTraits::set_supported_custom_fan_modes(std::vector<std::string> &supported_custom_fan_modes) {
|
||||
this->supported_custom_fan_modes_ = supported_custom_fan_modes;
|
||||
}
|
||||
const std::vector<std::string> ClimateTraits::get_supported_custom_fan_modes() const {
|
||||
return this->supported_custom_fan_modes_;
|
||||
}
|
||||
bool ClimateTraits::supports_custom_fan_mode(std::string &custom_fan_mode) const {
|
||||
return std::count(this->supported_custom_fan_modes_.begin(), this->supported_custom_fan_modes_.end(),
|
||||
custom_fan_mode);
|
||||
}
|
||||
bool ClimateTraits::supports_preset(ClimatePreset preset) const {
|
||||
switch (preset) {
|
||||
case climate::CLIMATE_PRESET_ECO:
|
||||
return this->supports_preset_eco_;
|
||||
case climate::CLIMATE_PRESET_AWAY:
|
||||
return this->supports_preset_away_;
|
||||
case climate::CLIMATE_PRESET_BOOST:
|
||||
return this->supports_preset_boost_;
|
||||
case climate::CLIMATE_PRESET_COMFORT:
|
||||
return this->supports_preset_comfort_;
|
||||
case climate::CLIMATE_PRESET_HOME:
|
||||
return this->supports_preset_home_;
|
||||
case climate::CLIMATE_PRESET_SLEEP:
|
||||
return this->supports_preset_sleep_;
|
||||
case climate::CLIMATE_PRESET_ACTIVITY:
|
||||
return this->supports_preset_activity_;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void ClimateTraits::set_supports_preset_eco(bool supports_preset_eco) {
|
||||
this->supports_preset_eco_ = supports_preset_eco;
|
||||
}
|
||||
void ClimateTraits::set_supports_preset_away(bool supports_preset_away) {
|
||||
this->supports_preset_away_ = supports_preset_away;
|
||||
}
|
||||
void ClimateTraits::set_supports_preset_boost(bool supports_preset_boost) {
|
||||
this->supports_preset_boost_ = supports_preset_boost;
|
||||
}
|
||||
void ClimateTraits::set_supports_preset_comfort(bool supports_preset_comfort) {
|
||||
this->supports_preset_comfort_ = supports_preset_comfort;
|
||||
}
|
||||
void ClimateTraits::set_supports_preset_home(bool supports_preset_home) {
|
||||
this->supports_preset_home_ = supports_preset_home;
|
||||
}
|
||||
void ClimateTraits::set_supports_preset_sleep(bool supports_preset_sleep) {
|
||||
this->supports_preset_sleep_ = supports_preset_sleep;
|
||||
}
|
||||
void ClimateTraits::set_supports_preset_activity(bool supports_preset_activity) {
|
||||
this->supports_preset_activity_ = supports_preset_activity;
|
||||
}
|
||||
bool ClimateTraits::get_supports_presets() const {
|
||||
return this->supports_preset_eco_ || this->supports_preset_away_ || this->supports_preset_boost_ ||
|
||||
this->supports_preset_comfort_ || this->supports_preset_home_ || this->supports_preset_sleep_ ||
|
||||
this->supports_preset_activity_;
|
||||
}
|
||||
void ClimateTraits::set_supported_custom_presets(std::vector<std::string> &supported_custom_presets) {
|
||||
this->supported_custom_presets_ = supported_custom_presets;
|
||||
}
|
||||
const std::vector<std::string> ClimateTraits::get_supported_custom_presets() const {
|
||||
return this->supported_custom_presets_;
|
||||
}
|
||||
bool ClimateTraits::supports_custom_preset(std::string &custom_preset) const {
|
||||
return std::count(this->supported_custom_presets_.begin(), this->supported_custom_presets_.end(), custom_preset);
|
||||
}
|
||||
void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) {
|
||||
this->supports_swing_mode_off_ = supports_swing_mode_off;
|
||||
}
|
||||
void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) {
|
||||
this->supports_swing_mode_both_ = supports_swing_mode_both;
|
||||
}
|
||||
void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) {
|
||||
this->supports_swing_mode_vertical_ = supports_swing_mode_vertical;
|
||||
}
|
||||
void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) {
|
||||
this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal;
|
||||
}
|
||||
bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const {
|
||||
switch (swing_mode) {
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
return this->supports_swing_mode_off_;
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
return this->supports_swing_mode_both_;
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
return this->supports_swing_mode_vertical_;
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
return this->supports_swing_mode_horizontal_;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ClimateTraits::get_supports_swing_modes() const {
|
||||
return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ ||
|
||||
supports_swing_mode_horizontal_;
|
||||
}
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "climate_mode.h"
|
||||
#include <set>
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
@@ -24,8 +25,6 @@ namespace climate {
|
||||
* - heat mode (increases current temperature)
|
||||
* - dry mode (removes humidity from air)
|
||||
* - fan mode (only turns on fan)
|
||||
* - supports away - away mode means that the climate device supports two different
|
||||
* target temperature settings: one target temp setting for "away" mode and one for non-away mode.
|
||||
* - supports action - if the climate device supports reporting the active
|
||||
* current action of the device with the action property.
|
||||
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
|
||||
@@ -41,93 +40,147 @@ namespace climate {
|
||||
*/
|
||||
class ClimateTraits {
|
||||
public:
|
||||
bool get_supports_current_temperature() const;
|
||||
void set_supports_current_temperature(bool supports_current_temperature);
|
||||
bool get_supports_two_point_target_temperature() const;
|
||||
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature);
|
||||
void set_supports_auto_mode(bool supports_auto_mode);
|
||||
void set_supports_cool_mode(bool supports_cool_mode);
|
||||
void set_supports_heat_mode(bool supports_heat_mode);
|
||||
void set_supports_fan_only_mode(bool supports_fan_only_mode);
|
||||
void set_supports_dry_mode(bool supports_dry_mode);
|
||||
void set_supports_away(bool supports_away);
|
||||
bool get_supports_away() const;
|
||||
void set_supports_action(bool supports_action);
|
||||
bool get_supports_action() const;
|
||||
bool supports_mode(ClimateMode mode) const;
|
||||
void set_supports_fan_mode_on(bool supports_fan_mode_on);
|
||||
void set_supports_fan_mode_off(bool supports_fan_mode_off);
|
||||
void set_supports_fan_mode_auto(bool supports_fan_mode_auto);
|
||||
void set_supports_fan_mode_low(bool supports_fan_mode_low);
|
||||
void set_supports_fan_mode_medium(bool supports_fan_mode_medium);
|
||||
void set_supports_fan_mode_high(bool supports_fan_mode_high);
|
||||
void set_supports_fan_mode_middle(bool supports_fan_mode_middle);
|
||||
void set_supports_fan_mode_focus(bool supports_fan_mode_focus);
|
||||
void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse);
|
||||
bool supports_fan_mode(ClimateFanMode fan_mode) const;
|
||||
bool get_supports_fan_modes() const;
|
||||
void set_supported_custom_fan_modes(std::vector<std::string> &supported_custom_fan_modes);
|
||||
const std::vector<std::string> get_supported_custom_fan_modes() const;
|
||||
bool supports_custom_fan_mode(std::string &custom_fan_mode) const;
|
||||
bool supports_preset(ClimatePreset preset) const;
|
||||
void set_supports_preset_eco(bool supports_preset_eco);
|
||||
void set_supports_preset_away(bool supports_preset_away);
|
||||
void set_supports_preset_boost(bool supports_preset_boost);
|
||||
void set_supports_preset_comfort(bool supports_preset_comfort);
|
||||
void set_supports_preset_home(bool supports_preset_home);
|
||||
void set_supports_preset_sleep(bool supports_preset_sleep);
|
||||
void set_supports_preset_activity(bool supports_preset_activity);
|
||||
bool get_supports_presets() const;
|
||||
void set_supported_custom_presets(std::vector<std::string> &supported_custom_presets);
|
||||
const std::vector<std::string> get_supported_custom_presets() const;
|
||||
bool supports_custom_preset(std::string &custom_preset) const;
|
||||
void set_supports_swing_mode_off(bool supports_swing_mode_off);
|
||||
void set_supports_swing_mode_both(bool supports_swing_mode_both);
|
||||
void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical);
|
||||
void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal);
|
||||
bool supports_swing_mode(ClimateSwingMode swing_mode) const;
|
||||
bool get_supports_swing_modes() const;
|
||||
bool get_supports_current_temperature() const { return supports_current_temperature_; }
|
||||
void set_supports_current_temperature(bool supports_current_temperature) {
|
||||
supports_current_temperature_ = supports_current_temperature;
|
||||
}
|
||||
bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
|
||||
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
|
||||
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
|
||||
}
|
||||
void set_supported_modes(std::set<ClimateMode> modes) { supported_modes_ = std::move(modes); }
|
||||
void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
void set_supports_fan_only_mode(bool supports_fan_only_mode) {
|
||||
set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode);
|
||||
}
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
|
||||
bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
|
||||
const std::set<ClimateMode> get_supported_modes() const { return supported_modes_; }
|
||||
|
||||
float get_visual_min_temperature() const;
|
||||
void set_visual_min_temperature(float visual_min_temperature);
|
||||
float get_visual_max_temperature() const;
|
||||
void set_visual_max_temperature(float visual_max_temperature);
|
||||
float get_visual_temperature_step() const;
|
||||
void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
|
||||
bool get_supports_action() const { return supports_action_; }
|
||||
|
||||
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { supported_fan_modes_ = std::move(modes); }
|
||||
void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
|
||||
bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
|
||||
bool get_supports_fan_modes() const { return !supported_fan_modes_.empty(); }
|
||||
const std::set<ClimateFanMode> get_supported_fan_modes() const { return supported_fan_modes_; }
|
||||
|
||||
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
|
||||
supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
|
||||
}
|
||||
const std::set<std::string> &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; }
|
||||
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
|
||||
return supported_custom_fan_modes_.count(custom_fan_mode);
|
||||
}
|
||||
|
||||
void set_supported_presets(std::set<ClimatePreset> presets) { supported_presets_ = std::move(presets); }
|
||||
void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); }
|
||||
bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); }
|
||||
bool get_supports_presets() const { return !supported_presets_.empty(); }
|
||||
const std::set<climate::ClimatePreset> &get_supported_presets() const { return supported_presets_; }
|
||||
|
||||
void set_supported_custom_presets(std::set<std::string> supported_custom_presets) {
|
||||
supported_custom_presets_ = std::move(supported_custom_presets);
|
||||
}
|
||||
const std::set<std::string> &get_supported_custom_presets() const { return supported_custom_presets_; }
|
||||
bool supports_custom_preset(const std::string &custom_preset) const {
|
||||
return supported_custom_presets_.count(custom_preset);
|
||||
}
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead")
|
||||
void set_supports_away(bool supports) {
|
||||
if (supports) {
|
||||
supported_presets_.insert(CLIMATE_PRESET_AWAY);
|
||||
supported_presets_.insert(CLIMATE_PRESET_HOME);
|
||||
}
|
||||
}
|
||||
ESPDEPRECATED("This method is deprecated, use supports_preset() instead")
|
||||
bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); }
|
||||
|
||||
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
|
||||
void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void set_supports_swing_mode_horizontal(bool supported) {
|
||||
set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported);
|
||||
}
|
||||
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
|
||||
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
|
||||
const std::set<ClimateSwingMode> get_supported_swing_modes() { return supported_swing_modes_; }
|
||||
|
||||
float get_visual_min_temperature() const { return visual_min_temperature_; }
|
||||
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
|
||||
float get_visual_max_temperature() const { return visual_max_temperature_; }
|
||||
void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
|
||||
float get_visual_temperature_step() const { return visual_temperature_step_; }
|
||||
int8_t get_temperature_accuracy_decimals() const;
|
||||
void set_visual_temperature_step(float temperature_step);
|
||||
void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
|
||||
|
||||
protected:
|
||||
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
||||
if (supported) {
|
||||
supported_modes_.insert(mode);
|
||||
} else {
|
||||
supported_modes_.erase(mode);
|
||||
}
|
||||
}
|
||||
void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) {
|
||||
if (supported) {
|
||||
supported_fan_modes_.insert(mode);
|
||||
} else {
|
||||
supported_fan_modes_.erase(mode);
|
||||
}
|
||||
}
|
||||
void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) {
|
||||
if (supported) {
|
||||
supported_swing_modes_.insert(mode);
|
||||
} else {
|
||||
supported_swing_modes_.erase(mode);
|
||||
}
|
||||
}
|
||||
|
||||
bool supports_current_temperature_{false};
|
||||
bool supports_two_point_target_temperature_{false};
|
||||
bool supports_auto_mode_{false};
|
||||
bool supports_cool_mode_{false};
|
||||
bool supports_heat_mode_{false};
|
||||
bool supports_fan_only_mode_{false};
|
||||
bool supports_dry_mode_{false};
|
||||
bool supports_away_{false};
|
||||
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF};
|
||||
bool supports_action_{false};
|
||||
bool supports_fan_mode_on_{false};
|
||||
bool supports_fan_mode_off_{false};
|
||||
bool supports_fan_mode_auto_{false};
|
||||
bool supports_fan_mode_low_{false};
|
||||
bool supports_fan_mode_medium_{false};
|
||||
bool supports_fan_mode_high_{false};
|
||||
bool supports_fan_mode_middle_{false};
|
||||
bool supports_fan_mode_focus_{false};
|
||||
bool supports_fan_mode_diffuse_{false};
|
||||
bool supports_swing_mode_off_{false};
|
||||
bool supports_swing_mode_both_{false};
|
||||
bool supports_swing_mode_vertical_{false};
|
||||
bool supports_swing_mode_horizontal_{false};
|
||||
bool supports_preset_eco_{false};
|
||||
bool supports_preset_away_{false};
|
||||
bool supports_preset_boost_{false};
|
||||
bool supports_preset_comfort_{false};
|
||||
bool supports_preset_home_{false};
|
||||
bool supports_preset_sleep_{false};
|
||||
bool supports_preset_activity_{false};
|
||||
std::vector<std::string> supported_custom_fan_modes_;
|
||||
std::vector<std::string> supported_custom_presets_;
|
||||
std::set<climate::ClimateFanMode> supported_fan_modes_;
|
||||
std::set<climate::ClimateSwingMode> supported_swing_modes_;
|
||||
std::set<climate::ClimatePreset> supported_presets_;
|
||||
std::set<std::string> supported_custom_fan_modes_;
|
||||
std::set<std::string> supported_custom_presets_;
|
||||
|
||||
float visual_min_temperature_{10};
|
||||
float visual_max_temperature_{30};
|
||||
|
||||
@@ -9,63 +9,22 @@ static const char *TAG = "climate_ir";
|
||||
climate::ClimateTraits ClimateIR::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
||||
traits.set_supports_auto_mode(true);
|
||||
traits.set_supports_cool_mode(this->supports_cool_);
|
||||
traits.set_supports_heat_mode(this->supports_heat_);
|
||||
traits.set_supports_dry_mode(this->supports_dry_);
|
||||
traits.set_supports_fan_only_mode(this->supports_fan_only_);
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
||||
if (supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
if (supports_dry_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (supports_fan_only_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
|
||||
traits.set_supports_two_point_target_temperature(false);
|
||||
traits.set_supports_away(false);
|
||||
traits.set_visual_min_temperature(this->minimum_temperature_);
|
||||
traits.set_visual_max_temperature(this->maximum_temperature_);
|
||||
traits.set_visual_temperature_step(this->temperature_step_);
|
||||
for (auto fan_mode : this->fan_modes_) {
|
||||
switch (fan_mode) {
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
traits.set_supports_fan_mode_auto(true);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_DIFFUSE:
|
||||
traits.set_supports_fan_mode_diffuse(true);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_FOCUS:
|
||||
traits.set_supports_fan_mode_focus(true);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
traits.set_supports_fan_mode_high(true);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
traits.set_supports_fan_mode_low(true);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
traits.set_supports_fan_mode_medium(true);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MIDDLE:
|
||||
traits.set_supports_fan_mode_middle(true);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_OFF:
|
||||
traits.set_supports_fan_mode_off(true);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_ON:
|
||||
traits.set_supports_fan_mode_on(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto swing_mode : this->swing_modes_) {
|
||||
switch (swing_mode) {
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
traits.set_supports_swing_mode_off(true);
|
||||
break;
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
traits.set_supports_swing_mode_both(true);
|
||||
break;
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
traits.set_supports_swing_mode_vertical(true);
|
||||
break;
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
traits.set_supports_swing_mode_horizontal(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
traits.set_supported_fan_modes(fan_modes_);
|
||||
traits.set_supported_swing_modes(swing_modes_);
|
||||
return traits;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,8 @@ namespace climate_ir {
|
||||
class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener {
|
||||
public:
|
||||
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
||||
bool supports_dry = false, bool supports_fan_only = false,
|
||||
std::vector<climate::ClimateFanMode> fan_modes = {},
|
||||
std::vector<climate::ClimateSwingMode> swing_modes = {}) {
|
||||
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
|
||||
std::set<climate::ClimateSwingMode> swing_modes = {}) {
|
||||
this->minimum_temperature_ = minimum_temperature;
|
||||
this->maximum_temperature_ = maximum_temperature;
|
||||
this->temperature_step_ = temperature_step;
|
||||
@@ -58,8 +57,8 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||
bool supports_heat_{true};
|
||||
bool supports_dry_{false};
|
||||
bool supports_fan_only_{false};
|
||||
std::vector<climate::ClimateFanMode> fan_modes_ = {};
|
||||
std::vector<climate::ClimateSwingMode> swing_modes_ = {};
|
||||
std::set<climate::ClimateFanMode> fan_modes_ = {};
|
||||
std::set<climate::ClimateSwingMode> swing_modes_ = {};
|
||||
|
||||
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
|
||||
@@ -39,7 +39,7 @@ void LgIrClimate::transmit_state() {
|
||||
send_swing_cmd_ = false;
|
||||
remote_state |= COMMAND_SWING;
|
||||
} else {
|
||||
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) {
|
||||
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
remote_state |= COMMAND_ON_AI;
|
||||
} else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
|
||||
remote_state |= COMMAND_ON;
|
||||
@@ -52,7 +52,7 @@ void LgIrClimate::transmit_state() {
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state |= COMMAND_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state |= COMMAND_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
@@ -89,7 +89,7 @@ void LgIrClimate::transmit_state() {
|
||||
}
|
||||
}
|
||||
|
||||
if (this->mode == climate::CLIMATE_MODE_AUTO) {
|
||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
// remote_state |= FAN_MODE_AUTO_DRY;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
if ((remote_state & COMMAND_MASK) == COMMAND_ON) {
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
}
|
||||
|
||||
if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
|
||||
@@ -138,7 +138,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
||||
} else {
|
||||
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO)
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN)
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
|
||||
@@ -152,7 +152,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
|
||||
|
||||
// Fan Speed
|
||||
if (this->mode == climate::CLIMATE_MODE_AUTO) {
|
||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
|
||||
this->mode == climate::CLIMATE_MODE_DRY) {
|
||||
|
||||
@@ -70,7 +70,7 @@ void CoolixClimate::transmit_state() {
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state |= COOLIX_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state |= COOLIX_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
@@ -89,7 +89,7 @@ void CoolixClimate::transmit_state() {
|
||||
} else {
|
||||
remote_state |= COOLIX_FAN_TEMP_CODE;
|
||||
}
|
||||
if (this->mode == climate::CLIMATE_MODE_AUTO || this->mode == climate::CLIMATE_MODE_DRY) {
|
||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
remote_state |= COOLIX_FAN_MODE_AUTO_DRY;
|
||||
} else {
|
||||
@@ -197,7 +197,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
|
||||
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
@@ -207,7 +207,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
|
||||
// Fan Speed
|
||||
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_AUTO ||
|
||||
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
|
||||
this->mode == climate::CLIMATE_MODE_DRY)
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
|
||||
|
||||
@@ -45,6 +45,9 @@ CONFIG_SCHEMA = (
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"cse7766", baud_rate=4800, require_rx=True
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
@@ -64,9 +67,3 @@ async def to_code(config):
|
||||
conf = config[CONF_POWER]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
uart.validate_device(
|
||||
"cse7766", config, item_config, baud_rate=4800, require_tx=False
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ uint8_t DaikinClimate::operation_mode_() {
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode |= DAIKIN_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
operating_mode |= DAIKIN_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
@@ -131,7 +131,7 @@ uint8_t DaikinClimate::temperature_() {
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
return 0x32;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
return 0xc0;
|
||||
default:
|
||||
@@ -160,7 +160,7 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case DAIKIN_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case DAIKIN_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
|
||||
@@ -43,12 +43,11 @@ const uint8_t DAIKIN_STATE_FRAME_SIZE = 19;
|
||||
class DaikinClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
DaikinClimate()
|
||||
: climate_ir::ClimateIR(
|
||||
DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
|
||||
std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
|
||||
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
|
||||
std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
|
||||
: climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
||||
climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
|
||||
|
||||
protected:
|
||||
// Transmit via IR the state of this climate controller.
|
||||
|
||||
@@ -68,6 +68,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
).extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"dfplayer", baud_rate=9600, require_tx=True
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
@@ -80,12 +83,6 @@ async def to_code(config):
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
uart.validate_device(
|
||||
"dfplayer", config, item_config, baud_rate=9600, require_rx=False
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play_next",
|
||||
NextAction,
|
||||
|
||||
@@ -91,7 +91,16 @@ bool ESP32BLE::ble_setup_() {
|
||||
}
|
||||
}
|
||||
|
||||
err = esp_ble_gap_set_device_name(App.get_name().c_str());
|
||||
std::string name = App.get_name();
|
||||
if (name.length() > 20) {
|
||||
if (App.is_name_add_mac_suffix_enabled()) {
|
||||
name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address
|
||||
} else {
|
||||
name = name.substr(0, 20);
|
||||
}
|
||||
}
|
||||
|
||||
err = esp_ble_gap_set_device_name(name.c_str());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err);
|
||||
return false;
|
||||
|
||||
@@ -132,7 +132,7 @@ void FujitsuGeneralClimate::transmit_state() {
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
default:
|
||||
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO);
|
||||
break;
|
||||
@@ -343,7 +343,7 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
case FUJITSU_GENERAL_MODE_AUTO:
|
||||
default:
|
||||
// TODO: CLIMATE_MODE_10C is missing from esphome
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ CONFIG_SCHEMA = (
|
||||
.extend(cv.polling_component_schema("20s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
@@ -95,7 +96,3 @@ async def to_code(config):
|
||||
|
||||
# https://platformio.org/lib/show/1655/TinyGPSPlus
|
||||
cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
uart.validate_device("gps", config, item_config, require_tx=False)
|
||||
|
||||
@@ -155,7 +155,7 @@ void HitachiClimate::transmit_state() {
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
set_mode_(HITACHI_AC344_MODE_HEAT);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
set_mode_(HITACHI_AC344_MODE_AUTO);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
@@ -251,7 +251,7 @@ bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
|
||||
@@ -79,11 +79,10 @@ const uint16_t HITACHI_AC344_BITS = HITACHI_AC344_STATE_LENGTH * 8;
|
||||
class HitachiClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
HitachiClimate()
|
||||
: climate_ir::ClimateIR(
|
||||
HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true,
|
||||
std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
|
||||
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
|
||||
std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {}
|
||||
: climate_ir::ClimateIR(HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
||||
climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {}
|
||||
|
||||
protected:
|
||||
uint8_t remote_state_[HITACHI_AC344_STATE_LENGTH]{0x01, 0x10, 0x00, 0x40, 0x00, 0xFF, 0x00, 0xCC, 0x00, 0x00, 0x00,
|
||||
|
||||
@@ -2,19 +2,16 @@ import urllib.parse as urlparse
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TIMEOUT,
|
||||
CONF_ESPHOME,
|
||||
CONF_METHOD,
|
||||
CONF_ARDUINO_VERSION,
|
||||
ARDUINO_VERSION_ESP8266,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_URL,
|
||||
)
|
||||
from esphome.core import CORE, Lambda
|
||||
from esphome.core.config import PLATFORMIO_ESP8266_LUT
|
||||
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["json"]
|
||||
@@ -36,29 +33,6 @@ CONF_VERIFY_SSL = "verify_ssl"
|
||||
CONF_ON_RESPONSE = "on_response"
|
||||
|
||||
|
||||
def validate_framework(config):
|
||||
if CORE.is_esp32:
|
||||
return config
|
||||
|
||||
version = "RECOMMENDED"
|
||||
if CONF_ARDUINO_VERSION in CORE.raw_config[CONF_ESPHOME]:
|
||||
version = CORE.raw_config[CONF_ESPHOME][CONF_ARDUINO_VERSION]
|
||||
|
||||
if version in ["LATEST", "DEV"]:
|
||||
return config
|
||||
|
||||
framework = (
|
||||
PLATFORMIO_ESP8266_LUT[version]
|
||||
if version in PLATFORMIO_ESP8266_LUT
|
||||
else version
|
||||
)
|
||||
if framework < ARDUINO_VERSION_ESP8266["2.5.1"]:
|
||||
raise cv.Invalid(
|
||||
"This component is not supported on arduino framework version below 2.5.1"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def validate_url(value):
|
||||
value = cv.string(value)
|
||||
try:
|
||||
@@ -92,19 +66,32 @@ def validate_secure_url(config):
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HttpRequestComponent),
|
||||
cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
|
||||
cv.Optional(
|
||||
CONF_TIMEOUT, default="5s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
.add_extra(validate_framework)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HttpRequestComponent),
|
||||
cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
|
||||
cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def validate_framework(value):
|
||||
if not CORE.is_esp8266:
|
||||
# only for ESP8266
|
||||
return
|
||||
|
||||
framework_version = fv.get_arduino_framework_version()
|
||||
if framework_version is None or framework_version == "dev":
|
||||
return
|
||||
|
||||
if framework_version < "2.5.1":
|
||||
raise cv.Invalid(
|
||||
"This component is not supported on arduino framework version below 2.5.1, ",
|
||||
"please check esphome->arduino_version",
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -167,24 +167,38 @@ climate::ClimateTraits MideaAC::traits() {
|
||||
traits.set_visual_min_temperature(17);
|
||||
traits.set_visual_max_temperature(30);
|
||||
traits.set_visual_temperature_step(0.5);
|
||||
traits.set_supports_auto_mode(true);
|
||||
traits.set_supports_cool_mode(true);
|
||||
traits.set_supports_dry_mode(true);
|
||||
traits.set_supports_heat_mode(true);
|
||||
traits.set_supports_fan_only_mode(true);
|
||||
traits.set_supports_fan_mode_auto(true);
|
||||
traits.set_supports_fan_mode_low(true);
|
||||
traits.set_supports_fan_mode_medium(true);
|
||||
traits.set_supports_fan_mode_high(true);
|
||||
traits.set_supported_modes({
|
||||
climate::CLIMATE_MODE_OFF,
|
||||
climate::CLIMATE_MODE_HEAT_COOL,
|
||||
climate::CLIMATE_MODE_COOL,
|
||||
climate::CLIMATE_MODE_DRY,
|
||||
climate::CLIMATE_MODE_HEAT,
|
||||
climate::CLIMATE_MODE_FAN_ONLY,
|
||||
});
|
||||
traits.set_supported_fan_modes({
|
||||
climate::CLIMATE_FAN_AUTO,
|
||||
climate::CLIMATE_FAN_LOW,
|
||||
climate::CLIMATE_FAN_MEDIUM,
|
||||
climate::CLIMATE_FAN_HIGH,
|
||||
});
|
||||
traits.set_supported_custom_fan_modes(this->traits_custom_fan_modes_);
|
||||
traits.set_supports_swing_mode_off(true);
|
||||
traits.set_supports_swing_mode_vertical(true);
|
||||
traits.set_supports_swing_mode_horizontal(this->traits_swing_horizontal_);
|
||||
traits.set_supports_swing_mode_both(this->traits_swing_both_);
|
||||
traits.set_supports_preset_home(true);
|
||||
traits.set_supports_preset_eco(this->traits_preset_eco_);
|
||||
traits.set_supports_preset_sleep(this->traits_preset_sleep_);
|
||||
traits.set_supports_preset_boost(this->traits_preset_boost_);
|
||||
traits.set_supported_swing_modes({
|
||||
climate::CLIMATE_SWING_OFF,
|
||||
climate::CLIMATE_SWING_VERTICAL,
|
||||
});
|
||||
if (traits_swing_horizontal_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL);
|
||||
if (traits_swing_both_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
|
||||
traits.set_supported_presets({
|
||||
climate::CLIMATE_PRESET_HOME,
|
||||
});
|
||||
if (traits_preset_eco_)
|
||||
traits.add_supported_preset(climate::CLIMATE_PRESET_ECO);
|
||||
if (traits_preset_sleep_)
|
||||
traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP);
|
||||
if (traits_preset_boost_)
|
||||
traits.add_supported_preset(climate::CLIMATE_PRESET_BOOST);
|
||||
traits.set_supported_custom_presets(this->traits_custom_presets_);
|
||||
traits.set_supports_current_temperature(true);
|
||||
return traits;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/midea_dongle/midea_dongle.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/midea_dongle/midea_dongle.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "midea_frame.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -26,10 +26,12 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu
|
||||
void set_preset_sleep(bool state) { this->traits_preset_sleep_ = state; }
|
||||
void set_preset_boost(bool state) { this->traits_preset_boost_ = state; }
|
||||
bool allow_preset(climate::ClimatePreset preset) const;
|
||||
void set_custom_fan_modes(std::vector<std::string> custom_fan_modes) {
|
||||
this->traits_custom_fan_modes_ = custom_fan_modes;
|
||||
void set_custom_fan_modes(std::set<std::string> custom_fan_modes) {
|
||||
this->traits_custom_fan_modes_ = std::move(custom_fan_modes);
|
||||
}
|
||||
void set_custom_presets(std::set<std::string> custom_presets) {
|
||||
this->traits_custom_presets_ = std::move(custom_presets);
|
||||
}
|
||||
void set_custom_presets(std::vector<std::string> custom_presets) { this->traits_custom_presets_ = custom_presets; }
|
||||
bool allow_custom_preset(const std::string &custom_preset) const;
|
||||
|
||||
protected:
|
||||
@@ -53,8 +55,8 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu
|
||||
bool traits_preset_eco_{false};
|
||||
bool traits_preset_sleep_{false};
|
||||
bool traits_preset_boost_{false};
|
||||
std::vector<std::string> traits_custom_fan_modes_{{}};
|
||||
std::vector<std::string> traits_custom_presets_{{}};
|
||||
std::set<std::string> traits_custom_fan_modes_{{}};
|
||||
std::set<std::string> traits_custom_presets_{{}};
|
||||
};
|
||||
|
||||
} // namespace midea_ac
|
||||
|
||||
@@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent";
|
||||
const std::string MIDEA_TURBO_FAN_MODE = "turbo";
|
||||
const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection";
|
||||
|
||||
const uint8_t QueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x00,
|
||||
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68};
|
||||
const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x81,
|
||||
0x00, 0xFF, 0x03, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0x31};
|
||||
|
||||
const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21,
|
||||
0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
@@ -44,7 +44,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const {
|
||||
return climate::CLIMATE_MODE_OFF;
|
||||
switch (this->pbuf_[12] >> 5) {
|
||||
case MIDEA_MODE_AUTO:
|
||||
return climate::CLIMATE_MODE_AUTO;
|
||||
return climate::CLIMATE_MODE_HEAT_COOL;
|
||||
case MIDEA_MODE_COOL:
|
||||
return climate::CLIMATE_MODE_COOL;
|
||||
case MIDEA_MODE_DRY:
|
||||
@@ -61,7 +61,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const {
|
||||
void PropertiesFrame::set_mode(climate::ClimateMode mode) {
|
||||
uint8_t m;
|
||||
switch (mode) {
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
m = MIDEA_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
|
||||
@@ -33,7 +33,7 @@ void MitsubishiClimate::transmit_state() {
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state[6] = MITSUBISHI_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state[6] = MITSUBISHI_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
|
||||
@@ -61,7 +61,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
|
||||
// temp_step
|
||||
root["temp_step"] = traits.get_visual_temperature_step();
|
||||
|
||||
if (traits.get_supports_away()) {
|
||||
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
|
||||
// away_mode_command_topic
|
||||
root["away_mode_cmd_t"] = this->get_away_command_topic();
|
||||
// away_mode_state_topic
|
||||
@@ -164,19 +164,19 @@ void MQTTClimateComponent::setup() {
|
||||
});
|
||||
}
|
||||
|
||||
if (traits.get_supports_away()) {
|
||||
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
|
||||
this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) {
|
||||
auto onoff = parse_on_off(payload.c_str());
|
||||
auto call = this->device_->make_call();
|
||||
switch (onoff) {
|
||||
case PARSE_ON:
|
||||
call.set_away(true);
|
||||
call.set_preset(CLIMATE_PRESET_AWAY);
|
||||
break;
|
||||
case PARSE_OFF:
|
||||
call.set_away(false);
|
||||
call.set_preset(CLIMATE_PRESET_HOME);
|
||||
break;
|
||||
case PARSE_TOGGLE:
|
||||
call.set_away(!this->device_->away);
|
||||
call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY);
|
||||
break;
|
||||
case PARSE_NONE:
|
||||
default:
|
||||
@@ -259,8 +259,8 @@ bool MQTTClimateComponent::publish_state_() {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (traits.get_supports_away()) {
|
||||
std::string payload = ONOFF(this->device_->away);
|
||||
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
|
||||
std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY);
|
||||
if (!this->publish(this->get_away_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ void PIDClimate::setup() {
|
||||
restore->to_call(this).perform();
|
||||
} else {
|
||||
// restore from defaults, change_away handles those for us
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
this->target_temperature = this->default_target_temperature_;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ void PIDClimate::control(const climate::ClimateCall &call) {
|
||||
this->target_temperature = *call.get_target_temperature();
|
||||
|
||||
// If switching to non-auto mode, set output immediately
|
||||
if (this->mode != climate::CLIMATE_MODE_AUTO)
|
||||
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL)
|
||||
this->handle_non_auto_mode_();
|
||||
|
||||
this->publish_state();
|
||||
@@ -39,10 +39,14 @@ void PIDClimate::control(const climate::ClimateCall &call) {
|
||||
climate::ClimateTraits PIDClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(true);
|
||||
traits.set_supports_auto_mode(true);
|
||||
traits.set_supports_two_point_target_temperature(false);
|
||||
traits.set_supports_cool_mode(this->supports_cool_());
|
||||
traits.set_supports_heat_mode(this->supports_heat_());
|
||||
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
||||
if (supports_cool_())
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (supports_heat_())
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
|
||||
traits.set_supports_action(true);
|
||||
return traits;
|
||||
}
|
||||
@@ -121,14 +125,14 @@ void PIDClimate::update_pid_() {
|
||||
// keep autotuner instance so that subsequent dump_configs will print the long result message.
|
||||
} else {
|
||||
value = res.output;
|
||||
if (mode != climate::CLIMATE_MODE_AUTO) {
|
||||
if (mode != climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
ESP_LOGW(TAG, "For PID autotuner you need to set AUTO (also called heat/cool) mode!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->mode != climate::CLIMATE_MODE_AUTO) {
|
||||
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
this->handle_non_auto_mode_();
|
||||
} else {
|
||||
this->write_output_(value);
|
||||
|
||||
@@ -19,13 +19,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
).extend(spi.spi_device_schema(cs_pin_required=True))
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
|
||||
"rc522_spi", require_miso=True, require_mosi=True
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await rc522.setup_rc522(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
# validate given SPI hub is suitable for rc522_spi, it needs both miso and mosi
|
||||
spi.validate_device("rc522_spi", config, item_config, True, True)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome import automation
|
||||
from esphome.components.output import FloatOutput
|
||||
from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID
|
||||
@@ -36,12 +37,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
# Not adding this to FloatOutput as this is the only component which needs `update_frequency`
|
||||
|
||||
parent_config = config.get_config_by_id(item_config[CONF_OUTPUT])
|
||||
platform = parent_config[CONF_PLATFORM]
|
||||
|
||||
def validate_parent_output_config(value):
|
||||
platform = value.get(CONF_PLATFORM)
|
||||
PWM_GOOD = ["esp8266_pwm", "ledc"]
|
||||
PWM_BAD = [
|
||||
"ac_dimmer ",
|
||||
@@ -55,14 +52,25 @@ def validate(config, item_config):
|
||||
]
|
||||
|
||||
if platform in PWM_BAD:
|
||||
raise ValueError(f"Component rtttl cannot use {platform} as output component")
|
||||
raise cv.Invalid(f"Component rtttl cannot use {platform} as output component")
|
||||
|
||||
if platform not in PWM_GOOD:
|
||||
_LOGGER.warning(
|
||||
"Component rtttl is not known to work with the selected output type. Make sure this output supports custom frequency output method."
|
||||
"Component rtttl is not known to work with the selected output type. "
|
||||
"Make sure this output supports custom frequency output method."
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_OUTPUT): fv.id_declaration_match_schema(
|
||||
validate_parent_output_config
|
||||
)
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -40,6 +40,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
.extend(cv.polling_component_schema("5s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"sim800l", baud_rate=9600, require_tx=True, require_rx=True
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
@@ -54,10 +57,6 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
uart.validate_device("sim800l", config, item_config, baud_rate=9600)
|
||||
|
||||
|
||||
SIM800L_SEND_SMS_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sim800LComponent),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_CLK_PIN,
|
||||
@@ -69,9 +70,24 @@ async def register_spi_device(var, config):
|
||||
cg.add(var.set_cs_pin(pin))
|
||||
|
||||
|
||||
def validate_device(name, config, item_config, require_mosi, require_miso):
|
||||
spi_config = config.get_config_by_id(item_config[CONF_SPI_ID])
|
||||
if require_mosi and CONF_MISO_PIN not in spi_config:
|
||||
raise ValueError(f"Component {name} requires parent spi to declare miso_pin")
|
||||
if require_miso and CONF_MOSI_PIN not in spi_config:
|
||||
raise ValueError(f"Component {name} requires parent spi to declare mosi_pin")
|
||||
def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool):
|
||||
hub_schema = {}
|
||||
if require_miso:
|
||||
hub_schema[
|
||||
cv.Required(
|
||||
CONF_MISO_PIN,
|
||||
msg=f"Component {name} requires this spi bus to declare a miso_pin",
|
||||
)
|
||||
] = cv.valid
|
||||
if require_mosi:
|
||||
hub_schema[
|
||||
cv.Required(
|
||||
CONF_MOSI_PIN,
|
||||
msg=f"Component {name} requires this spi bus to declare a mosi_pin",
|
||||
)
|
||||
] = cv.valid
|
||||
|
||||
return cv.Schema(
|
||||
{cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ void Tcl112Climate::transmit_state() {
|
||||
|
||||
// Set mode
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state[6] &= 0xF0;
|
||||
remote_state[6] |= TCL112_AUTO;
|
||||
break;
|
||||
@@ -204,7 +204,7 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case TCL112_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
await climate.register_climate(var, config)
|
||||
|
||||
auto_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config
|
||||
heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config
|
||||
two_points_available = CONF_HEAT_ACTION in config and (
|
||||
CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config
|
||||
)
|
||||
@@ -258,10 +258,10 @@ async def to_code(config):
|
||||
var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION]
|
||||
)
|
||||
|
||||
if auto_mode_available is True:
|
||||
cg.add(var.set_supports_auto(True))
|
||||
if heat_cool_mode_available is True:
|
||||
cg.add(var.set_supports_heat_cool(True))
|
||||
else:
|
||||
cg.add(var.set_supports_auto(False))
|
||||
cg.add(var.set_supports_heat_cool(False))
|
||||
|
||||
if CONF_COOL_ACTION in config:
|
||||
await automation.build_automation(
|
||||
|
||||
@@ -21,7 +21,7 @@ void ThermostatClimate::setup() {
|
||||
restore->to_call(this).perform();
|
||||
} else {
|
||||
// restore from defaults, change_away handles temps for us
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
this->change_away_(false);
|
||||
}
|
||||
// refresh the climate action based on the restored settings
|
||||
@@ -50,12 +50,13 @@ void ThermostatClimate::control(const climate::ClimateCall &call) {
|
||||
this->target_temperature_low = *call.get_target_temperature_low();
|
||||
if (call.get_target_temperature_high().has_value())
|
||||
this->target_temperature_high = *call.get_target_temperature_high();
|
||||
if (call.get_away().has_value()) {
|
||||
if (call.get_preset().has_value()) {
|
||||
// setup_complete_ blocks modifying/resetting the temps immediately after boot
|
||||
if (this->setup_complete_) {
|
||||
this->change_away_(*call.get_away());
|
||||
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
|
||||
} else {
|
||||
this->away = *call.get_away();
|
||||
this->preset = *call.get_preset();
|
||||
;
|
||||
}
|
||||
}
|
||||
// set point validation
|
||||
@@ -78,26 +79,51 @@ void ThermostatClimate::control(const climate::ClimateCall &call) {
|
||||
climate::ClimateTraits ThermostatClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(true);
|
||||
traits.set_supports_auto_mode(this->supports_auto_);
|
||||
traits.set_supports_cool_mode(this->supports_cool_);
|
||||
traits.set_supports_dry_mode(this->supports_dry_);
|
||||
traits.set_supports_fan_only_mode(this->supports_fan_only_);
|
||||
traits.set_supports_heat_mode(this->supports_heat_);
|
||||
traits.set_supports_fan_mode_on(this->supports_fan_mode_on_);
|
||||
traits.set_supports_fan_mode_off(this->supports_fan_mode_off_);
|
||||
traits.set_supports_fan_mode_auto(this->supports_fan_mode_auto_);
|
||||
traits.set_supports_fan_mode_low(this->supports_fan_mode_low_);
|
||||
traits.set_supports_fan_mode_medium(this->supports_fan_mode_medium_);
|
||||
traits.set_supports_fan_mode_high(this->supports_fan_mode_high_);
|
||||
traits.set_supports_fan_mode_middle(this->supports_fan_mode_middle_);
|
||||
traits.set_supports_fan_mode_focus(this->supports_fan_mode_focus_);
|
||||
traits.set_supports_fan_mode_diffuse(this->supports_fan_mode_diffuse_);
|
||||
traits.set_supports_swing_mode_both(this->supports_swing_mode_both_);
|
||||
traits.set_supports_swing_mode_horizontal(this->supports_swing_mode_horizontal_);
|
||||
traits.set_supports_swing_mode_off(this->supports_swing_mode_off_);
|
||||
traits.set_supports_swing_mode_vertical(this->supports_swing_mode_vertical_);
|
||||
if (supports_auto_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_AUTO);
|
||||
if (supports_heat_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
||||
if (supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (supports_dry_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (supports_fan_only_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
if (supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
|
||||
if (supports_fan_mode_on_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_ON);
|
||||
if (supports_fan_mode_off_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_OFF);
|
||||
if (supports_fan_mode_auto_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO);
|
||||
if (supports_fan_mode_low_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW);
|
||||
if (supports_fan_mode_medium_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM);
|
||||
if (supports_fan_mode_high_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH);
|
||||
if (supports_fan_mode_middle_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE);
|
||||
if (supports_fan_mode_focus_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS);
|
||||
if (supports_fan_mode_diffuse_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE);
|
||||
|
||||
if (supports_swing_mode_both_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
|
||||
if (supports_swing_mode_horizontal_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL);
|
||||
if (supports_swing_mode_off_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF);
|
||||
if (supports_swing_mode_vertical_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL);
|
||||
|
||||
if (supports_away_)
|
||||
traits.set_supported_presets({climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_AWAY});
|
||||
|
||||
traits.set_supports_two_point_target_temperature(this->supports_two_points_);
|
||||
traits.set_supports_away(this->supports_away_);
|
||||
traits.set_supports_action(true);
|
||||
return traits;
|
||||
}
|
||||
@@ -130,7 +156,7 @@ climate::ClimateAction ThermostatClimate::compute_action_() {
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
target_action = climate::CLIMATE_ACTION_OFF;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
if (this->supports_cool_) {
|
||||
@@ -321,7 +347,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) {
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
trig = this->off_mode_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
// trig = this->auto_mode_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
@@ -339,7 +365,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) {
|
||||
default:
|
||||
// we cannot report an invalid mode back to HA (even if it asked for one)
|
||||
// and must assume some valid value
|
||||
mode = climate::CLIMATE_MODE_AUTO;
|
||||
mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
// trig = this->auto_mode_trigger_;
|
||||
}
|
||||
assert(trig != nullptr);
|
||||
@@ -398,7 +424,7 @@ void ThermostatClimate::change_away_(bool away) {
|
||||
} else
|
||||
this->target_temperature = this->away_config_.default_temperature;
|
||||
}
|
||||
this->away = away;
|
||||
this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME;
|
||||
}
|
||||
void ThermostatClimate::set_normal_config(const ThermostatClimateTargetTempConfig &normal_config) {
|
||||
this->normal_config_ = normal_config;
|
||||
@@ -434,6 +460,9 @@ ThermostatClimate::ThermostatClimate()
|
||||
swing_mode_vertical_trigger_(new Trigger<>()) {}
|
||||
void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; }
|
||||
void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) {
|
||||
this->supports_heat_cool_ = supports_heat_cool;
|
||||
}
|
||||
void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; }
|
||||
void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
|
||||
@@ -521,6 +550,7 @@ void ThermostatClimate::dump_config() {
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Hysteresis: %.1f°C", this->hysteresis_);
|
||||
ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_));
|
||||
ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_));
|
||||
ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
|
||||
ESP_LOGCONFIG(TAG, " Supports DRY: %s", YESNO(this->supports_dry_));
|
||||
ESP_LOGCONFIG(TAG, " Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_));
|
||||
|
||||
@@ -29,6 +29,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
void set_hysteresis(float hysteresis);
|
||||
void set_sensor(sensor::Sensor *sensor);
|
||||
void set_supports_auto(bool supports_auto);
|
||||
void set_supports_heat_cool(bool supports_heat_cool);
|
||||
void set_supports_cool(bool supports_cool);
|
||||
void set_supports_dry(bool supports_dry);
|
||||
void set_supports_fan_only(bool supports_fan_only);
|
||||
@@ -113,6 +114,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
/// A false value for any given attribute means that the controller has no such action
|
||||
/// (for example a thermostat, where only heating and not-heating is possible).
|
||||
bool supports_auto_{false};
|
||||
bool supports_heat_cool_{false};
|
||||
bool supports_cool_{false};
|
||||
bool supports_dry_{false};
|
||||
bool supports_fan_only_{false};
|
||||
|
||||
@@ -80,7 +80,7 @@ void ToshibaClimate::transmit_state() {
|
||||
mode = TOSHIBA_MODE_COOL;
|
||||
break;
|
||||
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
default:
|
||||
mode = TOSHIBA_MODE_AUTO;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
case TOSHIBA_MODE_AUTO:
|
||||
default:
|
||||
/* Note: Dry and Fan-only modes are reported as Auto, as they are not supported yet */
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
}
|
||||
|
||||
/* Get the target temperature */
|
||||
|
||||
@@ -13,7 +13,7 @@ void TuyaClimate::setup() {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
if (datapoint.value_bool) {
|
||||
if (this->supports_heat_ && this->supports_cool_) {
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
} else if (this->supports_heat_) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
} else if (this->supports_cool_) {
|
||||
@@ -68,8 +68,10 @@ void TuyaClimate::control(const climate::ClimateCall &call) {
|
||||
climate::ClimateTraits TuyaClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(this->current_temperature_id_.has_value());
|
||||
traits.set_supports_heat_mode(this->supports_heat_);
|
||||
traits.set_supports_cool_mode(this->supports_cool_);
|
||||
if (supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
if (supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
traits.set_supports_action(true);
|
||||
return traits;
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ void Tuya::send_raw_command_(TuyaCommand command) {
|
||||
void Tuya::process_command_queue_() {
|
||||
uint32_t delay = millis() - this->last_command_timestamp_;
|
||||
// Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly
|
||||
if (delay > COMMAND_DELAY && !command_queue_.empty()) {
|
||||
if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) {
|
||||
this->send_raw_command_(command_queue_.front());
|
||||
this->command_queue_.erase(command_queue_.begin());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from typing import Optional
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome import pins, automation
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
@@ -92,42 +95,6 @@ async def to_code(config):
|
||||
cg.add(var.set_parity(config[CONF_PARITY]))
|
||||
|
||||
|
||||
def validate_device(
|
||||
name, config, item_config, baud_rate=None, require_tx=True, require_rx=True
|
||||
):
|
||||
if not hasattr(config, "uart_devices"):
|
||||
config.uart_devices = {}
|
||||
devices = config.uart_devices
|
||||
|
||||
uart_config = config.get_config_by_id(item_config[CONF_UART_ID])
|
||||
|
||||
uart_id = uart_config[CONF_ID]
|
||||
device = devices.setdefault(uart_id, {})
|
||||
|
||||
if require_tx:
|
||||
if CONF_TX_PIN not in uart_config:
|
||||
raise ValueError(f"Component {name} requires parent uart to declare tx_pin")
|
||||
if CONF_TX_PIN in device:
|
||||
raise ValueError(
|
||||
f"Component {name} cannot use the same uart.{CONF_TX_PIN} as component {device[CONF_TX_PIN]} is already using it"
|
||||
)
|
||||
device[CONF_TX_PIN] = name
|
||||
|
||||
if require_rx:
|
||||
if CONF_RX_PIN not in uart_config:
|
||||
raise ValueError(f"Component {name} requires parent uart to declare rx_pin")
|
||||
if CONF_RX_PIN in device:
|
||||
raise ValueError(
|
||||
f"Component {name} cannot use the same uart.{CONF_RX_PIN} as component {device[CONF_RX_PIN]} is already using it"
|
||||
)
|
||||
device[CONF_RX_PIN] = name
|
||||
|
||||
if baud_rate and uart_config[CONF_BAUD_RATE] != baud_rate:
|
||||
raise ValueError(
|
||||
f"Component {name} requires parent uart baud rate be {baud_rate}"
|
||||
)
|
||||
|
||||
|
||||
# A schema to use for all UART devices, all UART integrations must extend this!
|
||||
UART_DEVICE_SCHEMA = cv.Schema(
|
||||
{
|
||||
@@ -135,6 +102,64 @@ UART_DEVICE_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
KEY_UART_DEVICES = "uart_devices"
|
||||
|
||||
|
||||
def final_validate_device_schema(
|
||||
name: str,
|
||||
*,
|
||||
baud_rate: Optional[int] = None,
|
||||
require_tx: bool = False,
|
||||
require_rx: bool = False,
|
||||
):
|
||||
def validate_baud_rate(value):
|
||||
if value != baud_rate:
|
||||
raise cv.Invalid(
|
||||
f"Component {name} required baud rate {baud_rate} for the uart bus"
|
||||
)
|
||||
return value
|
||||
|
||||
def validate_pin(opt, device):
|
||||
def validator(value):
|
||||
if opt in device:
|
||||
raise cv.Invalid(
|
||||
f"The uart {opt} is used both by {name} and {device[opt]}, "
|
||||
f"but can only be used by one. Please create a new uart bus for {name}."
|
||||
)
|
||||
device[opt] = name
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
def validate_hub(hub_config):
|
||||
hub_schema = {}
|
||||
uart_id = hub_config[CONF_ID]
|
||||
devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {})
|
||||
device = devices.setdefault(uart_id, {})
|
||||
|
||||
if require_tx:
|
||||
hub_schema[
|
||||
cv.Required(
|
||||
CONF_TX_PIN,
|
||||
msg=f"Component {name} requires this uart bus to declare a tx_pin",
|
||||
)
|
||||
] = validate_pin(CONF_TX_PIN, device)
|
||||
if require_rx:
|
||||
hub_schema[
|
||||
cv.Required(
|
||||
CONF_RX_PIN,
|
||||
msg=f"Component {name} requires this uart bus to declare a rx_pin",
|
||||
)
|
||||
] = validate_pin(CONF_RX_PIN, device)
|
||||
if baud_rate is not None:
|
||||
hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate
|
||||
return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config)
|
||||
|
||||
return cv.Schema(
|
||||
{cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(validate_hub)},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
async def register_uart_device(var, config):
|
||||
"""Register a UART device, setting up all the internal values.
|
||||
|
||||
@@ -48,7 +48,7 @@ void WhirlpoolClimate::transmit_state() {
|
||||
this->powered_on_assumed = powered_on;
|
||||
}
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
// set fan auto
|
||||
// set temp auto temp
|
||||
// set sleep false
|
||||
@@ -239,7 +239,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case WHIRLPOOL_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
from esphome.components.network import add_mdns_library
|
||||
@@ -137,18 +138,53 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend(
|
||||
)
|
||||
|
||||
|
||||
def validate(config, item_config):
|
||||
if (
|
||||
(CONF_NETWORKS in item_config)
|
||||
and (item_config[CONF_NETWORKS] == [])
|
||||
and (CONF_AP not in item_config)
|
||||
):
|
||||
if "esp32_improv" not in config:
|
||||
raise ValueError(
|
||||
"Please specify at least an SSID or an Access Point to create."
|
||||
def final_validate(config):
|
||||
has_sta = bool(config.get(CONF_NETWORKS, True))
|
||||
has_ap = CONF_AP in config
|
||||
has_improv = "esp32_improv" in fv.full_config.get()
|
||||
if (not has_sta) and (not has_ap) and (not has_improv):
|
||||
raise cv.Invalid(
|
||||
"Please specify at least an SSID or an Access Point to create."
|
||||
)
|
||||
|
||||
|
||||
def final_validate_power_esp32_ble(value):
|
||||
if not CORE.is_esp32:
|
||||
return
|
||||
if value != "NONE":
|
||||
# WiFi should be in modem sleep (!=NONE) with BLE coexistence
|
||||
# https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-guides/wifi.html#station-sleep
|
||||
return
|
||||
framework_version = fv.get_arduino_framework_version()
|
||||
if framework_version not in (None, "dev") and framework_version < "1.0.5":
|
||||
# Only frameworks 1.0.5+ impacted
|
||||
return
|
||||
full = fv.full_config.get()
|
||||
for conflicting in [
|
||||
"esp32_ble",
|
||||
"esp32_ble_beacon",
|
||||
"esp32_ble_server",
|
||||
"esp32_ble_tracker",
|
||||
]:
|
||||
if conflicting in full:
|
||||
raise cv.Invalid(
|
||||
f"power_save_mode NONE is incompatible with {conflicting}. "
|
||||
f"Please remove the power save mode. See also "
|
||||
f"https://github.com/esphome/issues/issues/2141#issuecomment-865688582"
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_POWER_SAVE_MODE): final_validate_power_esp32_ble,
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
),
|
||||
final_validate,
|
||||
)
|
||||
|
||||
|
||||
def _validate(config):
|
||||
if CONF_PASSWORD in config and CONF_SSID not in config:
|
||||
raise cv.Invalid("Cannot have WiFi password without SSID!")
|
||||
|
||||
@@ -587,7 +587,7 @@ void WiFiComponent::retry_connect() {
|
||||
}
|
||||
|
||||
bool WiFiComponent::can_proceed() {
|
||||
if (this->has_ap() && !this->has_sta()) {
|
||||
if (!this->has_sta()) {
|
||||
return true;
|
||||
}
|
||||
return this->is_connected();
|
||||
|
||||
@@ -82,11 +82,14 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000;
|
||||
climate::ClimateTraits YashimaClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
||||
traits.set_supports_auto_mode(true);
|
||||
traits.set_supports_cool_mode(this->supports_cool_);
|
||||
traits.set_supports_heat_mode(this->supports_heat_);
|
||||
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
||||
if (supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
|
||||
traits.set_supports_two_point_target_temperature(false);
|
||||
traits.set_supports_away(false);
|
||||
traits.set_visual_min_temperature(YASHIMA_TEMP_MIN);
|
||||
traits.set_visual_max_temperature(YASHIMA_TEMP_MAX);
|
||||
traits.set_visual_temperature_step(1);
|
||||
@@ -139,7 +142,7 @@ void YashimaClimate::transmit_state_() {
|
||||
|
||||
// Set mode
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state[0] |= YASHIMA_MODE_AUTO_BYTE0;
|
||||
remote_state[5] |= YASHIMA_MODE_AUTO_BYTE5;
|
||||
break;
|
||||
|
||||
@@ -21,11 +21,13 @@ from esphome.helpers import indent
|
||||
from esphome.util import safe_print, OrderedDict
|
||||
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from esphome.core import ConfigType
|
||||
from esphome.loader import get_component, get_platform, ComponentManifest
|
||||
from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue
|
||||
from esphome.voluptuous_schema import ExtraKeysInvalid
|
||||
from esphome.log import color, Fore
|
||||
import esphome.final_validate as fv
|
||||
import esphome.config_validation as cv
|
||||
from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,7 +56,7 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool
|
||||
return path[: len(other)] == other
|
||||
|
||||
|
||||
class Config(OrderedDict):
|
||||
class Config(OrderedDict, fv.FinalValidateConfig):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# A list of voluptuous errors
|
||||
@@ -65,6 +67,7 @@ class Config(OrderedDict):
|
||||
self.output_paths = [] # type: List[Tuple[ConfigPath, str]]
|
||||
# A list of components ids with the config path
|
||||
self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
|
||||
self._data = {}
|
||||
|
||||
def add_error(self, error):
|
||||
# type: (vol.Invalid) -> None
|
||||
@@ -72,6 +75,12 @@ class Config(OrderedDict):
|
||||
for err in error.errors:
|
||||
self.add_error(err)
|
||||
return
|
||||
if cv.ROOT_CONFIG_PATH in error.path:
|
||||
# Root value means that the path before the root should be ignored
|
||||
last_root = max(
|
||||
i for i, v in enumerate(error.path) if v is cv.ROOT_CONFIG_PATH
|
||||
)
|
||||
error.path = error.path[last_root + 1 :]
|
||||
self.errors.append(error)
|
||||
|
||||
@contextmanager
|
||||
@@ -140,13 +149,16 @@ class Config(OrderedDict):
|
||||
|
||||
return doc_range
|
||||
|
||||
def get_nested_item(self, path):
|
||||
# type: (ConfigPath) -> ConfigType
|
||||
def get_nested_item(
|
||||
self, path: ConfigPathType, raise_error: bool = False
|
||||
) -> ConfigFragmentType:
|
||||
data = self
|
||||
for item_index in path:
|
||||
try:
|
||||
data = data[item_index]
|
||||
except (KeyError, IndexError, TypeError):
|
||||
if raise_error:
|
||||
raise
|
||||
return {}
|
||||
return data
|
||||
|
||||
@@ -163,11 +175,20 @@ class Config(OrderedDict):
|
||||
part.append(item_index)
|
||||
return part
|
||||
|
||||
def get_config_by_id(self, id):
|
||||
def get_path_for_id(self, id: core.ID):
|
||||
"""Return the config fragment where the given ID is declared."""
|
||||
for declared_id, path in self.declare_ids:
|
||||
if declared_id.id == str(id):
|
||||
return self.get_nested_item(path[:-1])
|
||||
return None
|
||||
return path
|
||||
raise KeyError(f"ID {id} not found in configuration")
|
||||
|
||||
def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType:
|
||||
return self.get_nested_item(path, raise_error=True)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""Return temporary data used by final validation functions."""
|
||||
return self._data
|
||||
|
||||
|
||||
def iter_ids(config, path=None):
|
||||
@@ -189,23 +210,22 @@ def do_id_pass(result): # type: (Config) -> None
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_types import Component
|
||||
|
||||
declare_ids = result.declare_ids # type: List[Tuple[core.ID, ConfigPath]]
|
||||
searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
|
||||
for id, path in iter_ids(result):
|
||||
if id.is_declaration:
|
||||
if id.id is not None:
|
||||
# Look for duplicate definitions
|
||||
match = next((v for v in declare_ids if v[0].id == id.id), None)
|
||||
match = next((v for v in result.declare_ids if v[0].id == id.id), None)
|
||||
if match is not None:
|
||||
opath = "->".join(str(v) for v in match[1])
|
||||
result.add_str_error(f"ID {id.id} redefined! Check {opath}", path)
|
||||
continue
|
||||
declare_ids.append((id, path))
|
||||
result.declare_ids.append((id, path))
|
||||
else:
|
||||
searching_ids.append((id, path))
|
||||
# Resolve default ids after manual IDs
|
||||
for id, _ in declare_ids:
|
||||
id.resolve([v[0].id for v in declare_ids])
|
||||
for id, _ in result.declare_ids:
|
||||
id.resolve([v[0].id for v in result.declare_ids])
|
||||
if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component):
|
||||
CORE.component_ids.add(id.id)
|
||||
|
||||
@@ -213,7 +233,7 @@ def do_id_pass(result): # type: (Config) -> None
|
||||
for id, path in searching_ids:
|
||||
if id.id is not None:
|
||||
# manually declared
|
||||
match = next((v[0] for v in declare_ids if v[0].id == id.id), None)
|
||||
match = next((v[0] for v in result.declare_ids if v[0].id == id.id), None)
|
||||
if match is None or not match.is_manual:
|
||||
# No declared ID with this name
|
||||
import difflib
|
||||
@@ -224,7 +244,7 @@ def do_id_pass(result): # type: (Config) -> None
|
||||
)
|
||||
# Find candidates
|
||||
matches = difflib.get_close_matches(
|
||||
id.id, [v[0].id for v in declare_ids if v[0].is_manual]
|
||||
id.id, [v[0].id for v in result.declare_ids if v[0].is_manual]
|
||||
)
|
||||
if matches:
|
||||
matches_s = ", ".join(f'"{x}"' for x in matches)
|
||||
@@ -245,7 +265,7 @@ def do_id_pass(result): # type: (Config) -> None
|
||||
|
||||
if id.id is None and id.type is not None:
|
||||
matches = []
|
||||
for v in declare_ids:
|
||||
for v in result.declare_ids:
|
||||
if v[0] is None or not isinstance(v[0].type, MockObjClass):
|
||||
continue
|
||||
inherits = v[0].type.inherits_from(id.type)
|
||||
@@ -278,8 +298,6 @@ def do_id_pass(result): # type: (Config) -> None
|
||||
|
||||
|
||||
def recursive_check_replaceme(value):
|
||||
import esphome.config_validation as cv
|
||||
|
||||
if isinstance(value, list):
|
||||
return cv.Schema([recursive_check_replaceme])(value)
|
||||
if isinstance(value, dict):
|
||||
@@ -558,14 +576,16 @@ def validate_config(config, command_line_substitutions):
|
||||
# 7. Final validation
|
||||
if not result.errors:
|
||||
# Inter - components validation
|
||||
for path, conf, comp in validate_queue:
|
||||
if comp.config_schema is None:
|
||||
token = fv.full_config.set(result)
|
||||
|
||||
for path, _, comp in validate_queue:
|
||||
if comp.final_validate_schema is None:
|
||||
continue
|
||||
if callable(comp.validate):
|
||||
try:
|
||||
comp.validate(result, result.get_nested_item(path))
|
||||
except ValueError as err:
|
||||
result.add_str_error(err, path)
|
||||
conf = result.get_nested_item(path)
|
||||
with result.catch_error(path):
|
||||
comp.final_validate_schema(conf)
|
||||
|
||||
fv.full_config.reset(token)
|
||||
|
||||
return result
|
||||
|
||||
@@ -621,8 +641,12 @@ def _format_vol_invalid(ex, config):
|
||||
)
|
||||
elif "extra keys not allowed" in str(ex):
|
||||
message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren)
|
||||
elif "required key not provided" in str(ex):
|
||||
message += "'{}' is a required option for [{}].".format(ex.path[-1], paren)
|
||||
elif isinstance(ex, vol.RequiredFieldInvalid):
|
||||
if ex.msg == "required key not provided":
|
||||
message += "'{}' is a required option for [{}].".format(ex.path[-1], paren)
|
||||
else:
|
||||
# Required has set a custom error message
|
||||
message += ex.msg
|
||||
else:
|
||||
message += humanize_error(config, ex)
|
||||
|
||||
|
||||
@@ -75,6 +75,9 @@ Inclusive = vol.Inclusive
|
||||
ALLOW_EXTRA = vol.ALLOW_EXTRA
|
||||
UNDEFINED = vol.UNDEFINED
|
||||
RequiredFieldInvalid = vol.RequiredFieldInvalid
|
||||
# this sentinel object can be placed in an 'Invalid' path to say
|
||||
# the rest of the error path is relative to the root config path
|
||||
ROOT_CONFIG_PATH = object()
|
||||
|
||||
RESERVED_IDS = [
|
||||
# C++ keywords http://en.cppreference.com/w/cpp/keyword
|
||||
@@ -218,8 +221,8 @@ class Required(vol.Required):
|
||||
- *not* the `config.get(CONF_<KEY>)` syntax.
|
||||
"""
|
||||
|
||||
def __init__(self, key):
|
||||
super().__init__(key)
|
||||
def __init__(self, key, msg=None):
|
||||
super().__init__(key, msg=msg)
|
||||
|
||||
|
||||
def check_not_templatable(value):
|
||||
@@ -1073,6 +1076,7 @@ def invalid(message):
|
||||
|
||||
|
||||
def valid(value):
|
||||
"""A validator that is always valid and returns the value as-is."""
|
||||
return value
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
MAJOR_VERSION = 1
|
||||
MINOR_VERSION = 19
|
||||
PATCH_VERSION = "0b5"
|
||||
PATCH_VERSION = "4"
|
||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ARDUINO_VERSION,
|
||||
@@ -23,6 +23,7 @@ from esphome.util import OrderedDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..cpp_generator import MockObj, MockObjClass, Statement
|
||||
from ..types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -462,9 +463,9 @@ class EsphomeCore:
|
||||
# The board that's used (for example nodemcuv2)
|
||||
self.board: Optional[str] = None
|
||||
# The full raw configuration
|
||||
self.raw_config: Optional[ConfigType] = None
|
||||
self.raw_config: Optional["ConfigType"] = None
|
||||
# The validated configuration, this is None until the config has been validated
|
||||
self.config: Optional[ConfigType] = None
|
||||
self.config: Optional["ConfigType"] = None
|
||||
# The pending tasks in the task queue (mostly for C++ generation)
|
||||
# This is a priority queue (with heapq)
|
||||
# Each item is a tuple of form: (-priority, unique number, task)
|
||||
@@ -752,6 +753,3 @@ class EnumValue:
|
||||
|
||||
|
||||
CORE = EsphomeCore()
|
||||
|
||||
ConfigType = Dict[str, Any]
|
||||
CoreType = EsphomeCore
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace esphome {
|
||||
class Application {
|
||||
public:
|
||||
void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) {
|
||||
this->name_add_mac_suffix_ = name_add_mac_suffix;
|
||||
if (name_add_mac_suffix) {
|
||||
this->name_ = name + "-" + get_mac_address().substr(6);
|
||||
} else {
|
||||
@@ -97,6 +98,8 @@ class Application {
|
||||
/// Get the name of this Application set by set_name().
|
||||
const std::string &get_name() const { return this->name_; }
|
||||
|
||||
bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; }
|
||||
|
||||
const std::string &get_compilation_time() const { return this->compilation_time_; }
|
||||
|
||||
/** Set the target interval with which to run the loop() calls.
|
||||
@@ -245,6 +248,7 @@ class Application {
|
||||
|
||||
std::string name_;
|
||||
std::string compilation_time_;
|
||||
bool name_add_mac_suffix_;
|
||||
uint32_t last_loop_{0};
|
||||
uint32_t loop_interval_{16};
|
||||
int dump_config_at_{-1};
|
||||
|
||||
@@ -8,7 +8,8 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from esphome.core import coroutine, ID, CORE, ConfigType
|
||||
from esphome.core import coroutine, ID, CORE
|
||||
from esphome.types import ConfigType
|
||||
from esphome.cpp_generator import RawExpression, add, get_variable
|
||||
from esphome.cpp_types import App, GPIOPin
|
||||
from esphome.util import Registry, RegistryEntry
|
||||
|
||||
@@ -716,9 +716,6 @@ class LogoutHandler(BaseHandler):
|
||||
self.redirect("./login")
|
||||
|
||||
|
||||
_STATIC_FILE_HASHES = {}
|
||||
|
||||
|
||||
def get_base_frontend_path():
|
||||
if ENV_DEV not in os.environ:
|
||||
import esphome_dashboard
|
||||
@@ -741,19 +738,23 @@ def get_static_path(*args):
|
||||
return os.path.join(get_base_frontend_path(), "static", *args)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def get_static_file_url(name):
|
||||
base = f"./static/{name}"
|
||||
|
||||
if ENV_DEV in os.environ:
|
||||
return base
|
||||
|
||||
# Module imports can't deduplicate if stuff added to url
|
||||
if name == "js/esphome/index.js":
|
||||
return f"./static/{name}"
|
||||
import esphome_dashboard
|
||||
|
||||
if name in _STATIC_FILE_HASHES:
|
||||
hash_ = _STATIC_FILE_HASHES[name]
|
||||
else:
|
||||
path = get_static_path(name)
|
||||
with open(path, "rb") as f_handle:
|
||||
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
|
||||
_STATIC_FILE_HASHES[name] = hash_
|
||||
return f"./static/{name}?hash={hash_}"
|
||||
return base.replace("index.js", esphome_dashboard.entrypoint())
|
||||
|
||||
path = get_static_path(name)
|
||||
with open(path, "rb") as f_handle:
|
||||
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
|
||||
return f"{base}?hash={hash_}"
|
||||
|
||||
|
||||
def make_app(debug=get_bool_env(ENV_DEV)):
|
||||
@@ -820,9 +821,6 @@ def make_app(debug=get_bool_env(ENV_DEV)):
|
||||
**app_settings,
|
||||
)
|
||||
|
||||
if debug:
|
||||
_STATIC_FILE_HASHES.clear()
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
||||
81
esphome/final_validate.py
Normal file
81
esphome/final_validate.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from abc import ABC, abstractmethod, abstractproperty
|
||||
from typing import Dict, Any
|
||||
import contextvars
|
||||
|
||||
from esphome.types import ConfigFragmentType, ID, ConfigPathType
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
ARDUINO_VERSION_ESP32,
|
||||
ARDUINO_VERSION_ESP8266,
|
||||
CONF_ESPHOME,
|
||||
CONF_ARDUINO_VERSION,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
class FinalValidateConfig(ABC):
|
||||
@abstractproperty
|
||||
def data(self) -> Dict[str, Any]:
|
||||
"""A dictionary that can be used by post validation functions to store
|
||||
global data during the validation phase. Each component should store its
|
||||
data under a unique key
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_path_for_id(self, id: ID) -> ConfigPathType:
|
||||
"""Get the config path a given ID has been declared in.
|
||||
|
||||
This is the location under the _validated_ config (for example, with cv.ensure_list applied)
|
||||
Raises KeyError if the id was not declared in the configuration.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType:
|
||||
"""Get the config fragment for the given global path.
|
||||
|
||||
Raises KeyError if a key in the path does not exist.
|
||||
"""
|
||||
|
||||
|
||||
FinalValidateConfig.register(dict)
|
||||
|
||||
# Context variable tracking the full config for some final validation functions.
|
||||
full_config: contextvars.ContextVar[FinalValidateConfig] = contextvars.ContextVar(
|
||||
"full_config"
|
||||
)
|
||||
|
||||
|
||||
def id_declaration_match_schema(schema):
|
||||
"""A final-validation schema function that applies a schema to the outer config fragment of an
|
||||
ID declaration.
|
||||
|
||||
This validator must be applied to ID values.
|
||||
"""
|
||||
if not isinstance(schema, cv.Schema):
|
||||
schema = cv.Schema(schema, extra=cv.ALLOW_EXTRA)
|
||||
|
||||
def validator(value):
|
||||
fconf = full_config.get()
|
||||
path = fconf.get_path_for_id(value)[:-1]
|
||||
declaration_config = fconf.get_config_for_path(path)
|
||||
with cv.prepend_path([cv.ROOT_CONFIG_PATH] + path):
|
||||
return schema(declaration_config)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def get_arduino_framework_version():
|
||||
path = [CONF_ESPHOME, CONF_ARDUINO_VERSION]
|
||||
# This is run after core validation, so the property is set even if user didn't
|
||||
version: str = full_config.get().get_config_for_path(path)
|
||||
|
||||
if CORE.is_esp32:
|
||||
version_map = ARDUINO_VERSION_ESP32
|
||||
elif CORE.is_esp8266:
|
||||
version_map = ARDUINO_VERSION_ESP8266
|
||||
else:
|
||||
raise ValueError("Platform not supported yet for this validator")
|
||||
|
||||
reverse_map = {v: k for k, v in version_map.items()}
|
||||
framework_version = reverse_map.get(version)
|
||||
return framework_version
|
||||
@@ -12,6 +12,7 @@ from pathlib import Path
|
||||
from esphome.const import ESP_PLATFORMS, SOURCE_FILE_EXTENSIONS
|
||||
import esphome.core.config
|
||||
from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -81,8 +82,13 @@ class ComponentManifest:
|
||||
return getattr(self.module, "CODEOWNERS", [])
|
||||
|
||||
@property
|
||||
def validate(self):
|
||||
return getattr(self.module, "validate", None)
|
||||
def final_validate_schema(self) -> Optional[Callable[[ConfigType], None]]:
|
||||
"""Components can declare a `FINAL_VALIDATE_SCHEMA` cv.Schema that gets called
|
||||
after the main validation. In that function checks across components can be made.
|
||||
|
||||
Note that the function can't mutate the configuration - no changes are saved
|
||||
"""
|
||||
return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None)
|
||||
|
||||
@property
|
||||
def source_files(self) -> Dict[Path, SourceFile]:
|
||||
|
||||
@@ -4,14 +4,13 @@ from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Optional, List
|
||||
|
||||
from esphome import const
|
||||
from esphome.core import CORE
|
||||
from esphome.helpers import write_file_if_changed
|
||||
|
||||
# pylint: disable=unused-import, wrong-import-order
|
||||
from esphome.core import CoreType
|
||||
from typing import Any, Optional, List
|
||||
from esphome.types import CoreType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
18
esphome/types.py
Normal file
18
esphome/types.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""This helper module tracks commonly used types in the esphome python codebase."""
|
||||
from typing import Dict, Union, List
|
||||
|
||||
from esphome.core import ID, Lambda, EsphomeCore
|
||||
|
||||
ConfigFragmentType = Union[
|
||||
str,
|
||||
int,
|
||||
float,
|
||||
None,
|
||||
Dict[Union[str, int], "ConfigFragmentType"],
|
||||
List["ConfigFragmentType"],
|
||||
ID,
|
||||
Lambda,
|
||||
]
|
||||
ConfigType = Dict[str, ConfigFragmentType]
|
||||
CoreType = EsphomeCore
|
||||
ConfigPathType = Union[str, int]
|
||||
@@ -11,4 +11,4 @@ ifaddr==0.1.7
|
||||
platformio==5.1.1
|
||||
esptool==2.8
|
||||
click==7.1.2
|
||||
esphome-dashboard==20210615.0
|
||||
esphome-dashboard==20210623.0
|
||||
|
||||
1
script/api_protobuf/api_protobuf.py
Normal file → Executable file
1
script/api_protobuf/api_protobuf.py
Normal file → Executable file
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Python 3 script to automatically generate C++ classes for ESPHome's native API.
|
||||
|
||||
It's pretty crappy spaghetti code, but it works.
|
||||
|
||||
@@ -80,7 +80,7 @@ wifi:
|
||||
dns2: 1.2.2.1
|
||||
domain: .local
|
||||
reboot_timeout: 120s
|
||||
power_save_mode: none
|
||||
power_save_mode: light
|
||||
|
||||
http_request:
|
||||
useragent: esphome/device
|
||||
|
||||
Reference in New Issue
Block a user