mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
[thermostat] Keep presets in shared flash tables (#20080)
This commit is contained in:
@@ -39,7 +39,6 @@ from esphome.const import (
|
||||
CONF_HEAT_MODE,
|
||||
CONF_HEAT_OVERRUN,
|
||||
CONF_HUMIDITY_SENSOR,
|
||||
CONF_ID,
|
||||
CONF_IDLE_ACTION,
|
||||
CONF_MAX_COOLING_RUN_TIME,
|
||||
CONF_MAX_HEATING_RUN_TIME,
|
||||
@@ -72,6 +71,7 @@ from esphome.const import (
|
||||
CONF_TARGET_TEMPERATURE_CHANGE_ACTION,
|
||||
CONF_VISUAL,
|
||||
)
|
||||
from esphome.helpers import cpp_string_escape
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CONF_DEFAULT_PRESET = "default_preset"
|
||||
@@ -93,6 +93,8 @@ ThermostatClimate = thermostat_ns.class_(
|
||||
ThermostatClimateTargetTempConfig = thermostat_ns.struct(
|
||||
"ThermostatClimateTargetTempConfig"
|
||||
)
|
||||
ThermostatPresetEntry = thermostat_ns.struct("ThermostatPresetEntry")
|
||||
ThermostatCustomPresetEntry = thermostat_ns.struct("ThermostatCustomPresetEntry")
|
||||
OnBootRestoreFrom = thermostat_ns.enum("OnBootRestoreFrom")
|
||||
ON_BOOT_RESTORE_FROM = {
|
||||
"MEMORY": OnBootRestoreFrom.MEMORY,
|
||||
@@ -116,6 +118,7 @@ ClimatePreset = climate_ns.enum("ClimatePreset")
|
||||
|
||||
PRESET_CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
# Still accepted for existing configs; presets are flash table entries, not variables.
|
||||
cv.GenerateID(): cv.declare_id(ThermostatClimateTargetTempConfig),
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_MODE): validate_climate_mode,
|
||||
@@ -674,7 +677,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESET): cv.ensure_list(PRESET_CONFIG_SCHEMA),
|
||||
cv.Optional(CONF_PRESET): cv.All(
|
||||
cv.ensure_list(PRESET_CONFIG_SCHEMA), cv.Length(max=255)
|
||||
),
|
||||
cv.Optional(CONF_ON_BOOT_RESTORE_FROM): validate_on_boot_restore_from,
|
||||
cv.Optional(CONF_PRESET_CHANGE): automation.validate_automation(
|
||||
single=True
|
||||
@@ -964,85 +969,51 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_humidity_hysteresis(config[CONF_HUMIDITY_HYSTERESIS]))
|
||||
|
||||
if CONF_PRESET in config:
|
||||
# Separate standard and custom presets, and build preset config variables
|
||||
standard_presets: list[tuple[cg.MockObj, cg.MockObj]] = []
|
||||
custom_presets: list[tuple[str, cg.MockObj]] = []
|
||||
# Presets live in shared PROGMEM tables; the thermostat keeps a pointer and a count.
|
||||
standard_presets: list[cg.StructInitializer] = []
|
||||
custom_presets: list[cg.StructInitializer] = []
|
||||
nan = float("nan")
|
||||
unset = cg.RawExpression("std::nullopt")
|
||||
|
||||
for preset_config in config[CONF_PRESET]:
|
||||
name = preset_config[CONF_NAME]
|
||||
standard_preset = None
|
||||
if name.upper() in climate.CLIMATE_PRESETS:
|
||||
standard_preset = climate.CLIMATE_PRESETS[name.upper()]
|
||||
|
||||
low = preset_config.get(CONF_DEFAULT_TARGET_TEMPERATURE_LOW)
|
||||
high = preset_config.get(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH)
|
||||
if two_points_available:
|
||||
preset_target_config = ThermostatClimateTargetTempConfig(
|
||||
preset_config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
|
||||
preset_config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
|
||||
)
|
||||
elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in preset_config:
|
||||
preset_target_config = ThermostatClimateTargetTempConfig(
|
||||
preset_config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
|
||||
)
|
||||
elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in preset_config:
|
||||
preset_target_config = ThermostatClimateTargetTempConfig(
|
||||
preset_config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]
|
||||
temperatures = (nan, low, high)
|
||||
else:
|
||||
temperatures = (high if high is not None else low, nan, nan)
|
||||
target = ThermostatClimateTargetTempConfig(
|
||||
*(nan if t is None else t for t in temperatures),
|
||||
preset_config.get(CONF_MODE, unset),
|
||||
preset_config.get(CONF_FAN_MODE, unset),
|
||||
preset_config.get(CONF_SWING_MODE, unset),
|
||||
)
|
||||
if (standard := climate.CLIMATE_PRESETS.get(name.upper())) is not None:
|
||||
standard_presets.append(
|
||||
cg.StructInitializer(
|
||||
ThermostatPresetEntry, ("preset", standard), ("config", target)
|
||||
)
|
||||
)
|
||||
else:
|
||||
preset_target_config = None
|
||||
|
||||
preset_target_variable = cg.new_variable(
|
||||
preset_config[CONF_ID], preset_target_config
|
||||
)
|
||||
|
||||
if CONF_MODE in preset_config:
|
||||
cg.add(preset_target_variable.set_mode(preset_config[CONF_MODE]))
|
||||
|
||||
if CONF_FAN_MODE in preset_config:
|
||||
cg.add(
|
||||
preset_target_variable.set_fan_mode(preset_config[CONF_FAN_MODE])
|
||||
)
|
||||
|
||||
if CONF_SWING_MODE in preset_config:
|
||||
cg.add(
|
||||
preset_target_variable.set_swing_mode(
|
||||
preset_config[CONF_SWING_MODE]
|
||||
custom_presets.append(
|
||||
cg.StructInitializer(
|
||||
ThermostatCustomPresetEntry,
|
||||
("name", cg.RawExpression(cpp_string_escape(name))),
|
||||
("config", target),
|
||||
)
|
||||
)
|
||||
|
||||
if standard_preset is not None:
|
||||
standard_presets.append((standard_preset, preset_target_variable))
|
||||
else:
|
||||
custom_presets.append((name, preset_target_variable))
|
||||
|
||||
# Build initializer list for standard presets
|
||||
if standard_presets:
|
||||
cg.add(
|
||||
var.set_preset_config(
|
||||
[
|
||||
cg.StructInitializer(
|
||||
thermostat_ns.struct("ThermostatPresetEntry"),
|
||||
("preset", preset),
|
||||
("config", preset_var),
|
||||
)
|
||||
for preset, preset_var in standard_presets
|
||||
]
|
||||
)
|
||||
table = cg.shared_progmem_array(
|
||||
"thermostat_presets", ThermostatPresetEntry, standard_presets
|
||||
)
|
||||
|
||||
# Build initializer list for custom presets
|
||||
cg.add(var.set_preset_config(table, len(standard_presets)))
|
||||
if custom_presets:
|
||||
cg.add(
|
||||
var.set_custom_preset_config(
|
||||
[
|
||||
cg.StructInitializer(
|
||||
thermostat_ns.struct("ThermostatCustomPresetEntry"),
|
||||
("name", cg.RawExpression(f'"{name}"')),
|
||||
("config", preset_var),
|
||||
)
|
||||
for name, preset_var in custom_presets
|
||||
]
|
||||
)
|
||||
table = cg.shared_progmem_array(
|
||||
"thermostat_custom_presets", ThermostatCustomPresetEntry, custom_presets
|
||||
)
|
||||
cg.add(var.set_custom_preset_config(table, len(custom_presets)))
|
||||
|
||||
if CONF_DEFAULT_PRESET in config:
|
||||
default_preset_name = config[CONF_DEFAULT_PRESET]
|
||||
|
||||
@@ -322,8 +322,8 @@ climate::ClimateTraits ThermostatClimate::traits() {
|
||||
if (this->supports_swing_mode_vertical_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL);
|
||||
|
||||
for (const auto &entry : this->preset_config_) {
|
||||
traits.add_supported_preset(entry.preset);
|
||||
for (uint8_t i = 0; i < this->preset_count_; i++) {
|
||||
traits.add_supported_preset(this->preset_entry_(i).preset);
|
||||
}
|
||||
|
||||
// Custom presets are stored on Climate base class and wired via get_traits()
|
||||
@@ -1175,17 +1175,30 @@ void ThermostatClimate::dump_preset_config_(const char *preset_name, const Therm
|
||||
}
|
||||
}
|
||||
|
||||
ThermostatPresetEntry ThermostatClimate::preset_entry_(uint8_t index) const {
|
||||
ThermostatPresetEntry entry;
|
||||
progmem_memcpy(&entry, &this->preset_config_[index], sizeof(entry));
|
||||
return entry;
|
||||
}
|
||||
|
||||
ThermostatCustomPresetEntry ThermostatClimate::custom_preset_entry_(uint8_t index) const {
|
||||
ThermostatCustomPresetEntry entry;
|
||||
progmem_memcpy(&entry, &this->custom_preset_config_[index], sizeof(entry));
|
||||
return entry;
|
||||
}
|
||||
|
||||
void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
|
||||
// Linear search through preset configurations
|
||||
const ThermostatClimateTargetTempConfig *config = nullptr;
|
||||
for (const auto &entry : this->preset_config_) {
|
||||
optional<ThermostatClimateTargetTempConfig> config;
|
||||
for (uint8_t i = 0; i < this->preset_count_; i++) {
|
||||
auto entry = this->preset_entry_(i);
|
||||
if (entry.preset == preset) {
|
||||
config = &entry.config;
|
||||
config = entry.config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (config != nullptr) {
|
||||
if (config.has_value()) {
|
||||
ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
|
||||
if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
|
||||
// Fire preset changed trigger
|
||||
@@ -1205,16 +1218,17 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
|
||||
|
||||
void ThermostatClimate::change_custom_preset_(const char *custom_preset, size_t len) {
|
||||
// Linear search through custom preset configurations
|
||||
const ThermostatClimateTargetTempConfig *config = nullptr;
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
optional<ThermostatClimateTargetTempConfig> config;
|
||||
for (uint8_t i = 0; i < this->custom_preset_count_; i++) {
|
||||
auto entry = this->custom_preset_entry_(i);
|
||||
// Compare first len chars, then verify entry.name ends there (same length)
|
||||
if (strncmp(entry.name, custom_preset, len) == 0 && entry.name[len] == '\0') {
|
||||
config = &entry.config;
|
||||
config = entry.config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (config != nullptr) {
|
||||
if (config.has_value()) {
|
||||
ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
|
||||
if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
|
||||
this->get_custom_preset() != custom_preset) {
|
||||
@@ -1279,13 +1293,14 @@ bool ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTem
|
||||
return something_changed;
|
||||
}
|
||||
|
||||
void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets) {
|
||||
void ThermostatClimate::set_custom_preset_config(const CustomPresetEntry *presets, uint8_t count) {
|
||||
this->custom_preset_config_ = presets;
|
||||
this->custom_preset_count_ = count;
|
||||
// Populate Climate base class custom presets vector
|
||||
std::vector<const char *> names;
|
||||
names.reserve(presets.size());
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
names.push_back(entry.name);
|
||||
names.reserve(count);
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
names.push_back(this->custom_preset_entry_(i).name);
|
||||
}
|
||||
this->set_supported_custom_presets(names);
|
||||
}
|
||||
@@ -1294,7 +1309,8 @@ ThermostatClimate::ThermostatClimate() = default;
|
||||
|
||||
void ThermostatClimate::set_default_preset(const char *custom_preset) {
|
||||
// Find the preset in custom_preset_config_ and store pointer from there
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
for (uint8_t i = 0; i < this->custom_preset_count_; i++) {
|
||||
auto entry = this->custom_preset_entry_(i);
|
||||
if (strcmp(entry.name, custom_preset) == 0) {
|
||||
this->default_custom_preset_ = entry.name;
|
||||
return;
|
||||
@@ -1545,9 +1561,10 @@ void ThermostatClimate::dump_config() {
|
||||
YESNO(this->supports_dehumidification_ || this->supports_humidification_),
|
||||
YESNO(this->supports_dehumidification_), YESNO(this->supports_humidification_));
|
||||
|
||||
if (!this->preset_config_.empty()) {
|
||||
if (this->preset_count_ > 0) {
|
||||
ESP_LOGCONFIG(TAG, " Supported PRESETS:");
|
||||
for (const auto &entry : this->preset_config_) {
|
||||
for (uint8_t i = 0; i < this->preset_count_; i++) {
|
||||
auto entry = this->preset_entry_(i);
|
||||
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset));
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
|
||||
entry.preset == this->default_preset_ ? LOG_STR_LITERAL(" (default)") : "");
|
||||
@@ -1555,9 +1572,10 @@ void ThermostatClimate::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->custom_preset_config_.empty()) {
|
||||
if (this->custom_preset_count_ > 0) {
|
||||
ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
for (uint8_t i = 0; i < this->custom_preset_count_; i++) {
|
||||
auto entry = this->custom_preset_entry_(i);
|
||||
const auto *preset_name = entry.name;
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
|
||||
(this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0)
|
||||
@@ -1568,13 +1586,4 @@ void ThermostatClimate::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig() = default;
|
||||
|
||||
ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig(float default_temperature)
|
||||
: default_temperature(default_temperature) {}
|
||||
|
||||
ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig(float default_temperature_low,
|
||||
float default_temperature_high)
|
||||
: default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {}
|
||||
|
||||
} // namespace esphome::thermostat
|
||||
|
||||
@@ -49,9 +49,21 @@ struct ThermostatClimateTimer {
|
||||
|
||||
struct ThermostatClimateTargetTempConfig {
|
||||
public:
|
||||
ThermostatClimateTargetTempConfig();
|
||||
ThermostatClimateTargetTempConfig(float default_temperature);
|
||||
ThermostatClimateTargetTempConfig(float default_temperature_low, float default_temperature_high);
|
||||
constexpr ThermostatClimateTargetTempConfig() = default;
|
||||
constexpr ThermostatClimateTargetTempConfig(float default_temperature) : default_temperature(default_temperature) {}
|
||||
constexpr ThermostatClimateTargetTempConfig(float default_temperature_low, float default_temperature_high)
|
||||
: default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {}
|
||||
/// Full form used by the generated preset tables in flash.
|
||||
constexpr ThermostatClimateTargetTempConfig(float default_temperature, float default_temperature_low,
|
||||
float default_temperature_high, optional<climate::ClimateMode> mode,
|
||||
optional<climate::ClimateFanMode> fan_mode,
|
||||
optional<climate::ClimateSwingMode> swing_mode)
|
||||
: default_temperature(default_temperature),
|
||||
default_temperature_low(default_temperature_low),
|
||||
default_temperature_high(default_temperature_high),
|
||||
fan_mode_(fan_mode),
|
||||
swing_mode_(swing_mode),
|
||||
mode_(mode) {}
|
||||
|
||||
void set_fan_mode(climate::ClimateFanMode fan_mode) { this->fan_mode_ = fan_mode; }
|
||||
void set_swing_mode(climate::ClimateSwingMode swing_mode) { this->swing_mode_ = swing_mode; }
|
||||
@@ -60,10 +72,6 @@ struct ThermostatClimateTargetTempConfig {
|
||||
float default_temperature{NAN};
|
||||
float default_temperature_low{NAN};
|
||||
float default_temperature_high{NAN};
|
||||
float cool_deadband_{NAN};
|
||||
float cool_overrun_{NAN};
|
||||
float heat_deadband_{NAN};
|
||||
float heat_overrun_{NAN};
|
||||
optional<climate::ClimateFanMode> fan_mode_{};
|
||||
optional<climate::ClimateSwingMode> swing_mode_{};
|
||||
optional<climate::ClimateMode> mode_{};
|
||||
@@ -175,8 +183,12 @@ class ThermostatClimate final : public climate::Climate, public Component {
|
||||
void set_supports_humidification(bool supports_humidification);
|
||||
void set_supports_two_points(bool supports_two_points) { this->supports_two_points_ = supports_two_points; }
|
||||
|
||||
void set_preset_config(std::initializer_list<PresetEntry> presets) { this->preset_config_ = presets; }
|
||||
void set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets);
|
||||
/// `presets` is a PROGMEM table that outlives the thermostat.
|
||||
void set_preset_config(const PresetEntry *presets, uint8_t count) {
|
||||
this->preset_config_ = presets;
|
||||
this->preset_count_ = count;
|
||||
}
|
||||
void set_custom_preset_config(const CustomPresetEntry *presets, uint8_t count);
|
||||
|
||||
Trigger<> *get_cool_action_trigger();
|
||||
Trigger<> *get_supplemental_cool_action_trigger();
|
||||
@@ -328,6 +340,9 @@ class ThermostatClimate final : public climate::Climate, public Component {
|
||||
bool humidification_required_();
|
||||
|
||||
void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config);
|
||||
/// Copies of table entries; the tables live in PROGMEM and hold byte-sized fields.
|
||||
PresetEntry preset_entry_(uint8_t index) const;
|
||||
CustomPresetEntry custom_preset_entry_(uint8_t index) const;
|
||||
|
||||
/// Minimum allowable duration in seconds for action timers
|
||||
const uint8_t min_timer_duration_{1};
|
||||
@@ -349,6 +364,9 @@ class ThermostatClimate final : public climate::Climate, public Component {
|
||||
/// state will attempt to be restored if possible
|
||||
OnBootRestoreFrom on_boot_restore_from_{OnBootRestoreFrom::MEMORY};
|
||||
|
||||
uint8_t preset_count_{0};
|
||||
uint8_t custom_preset_count_{0};
|
||||
|
||||
/// Whether the controller supports auto/cooling/drying/fanning/heating.
|
||||
///
|
||||
/// A false value for any given attribute means that the controller has no such action
|
||||
@@ -522,13 +540,13 @@ class ThermostatClimate final : public climate::Climate, public Component {
|
||||
/// Climate action timers
|
||||
std::array<ThermostatClimateTimer, THERMOSTAT_TIMER_COUNT> timer_{};
|
||||
|
||||
/// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc)
|
||||
FixedVector<PresetEntry> preset_config_{};
|
||||
/// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset")
|
||||
FixedVector<CustomPresetEntry> custom_preset_config_{};
|
||||
/// Standard preset configurations (Eg. AWAY, ECO, etc), a PROGMEM table of preset_count_ entries
|
||||
const PresetEntry *preset_config_{nullptr};
|
||||
/// Custom preset configurations (eg. "My Custom Preset"), a PROGMEM table of custom_preset_count_ entries
|
||||
const CustomPresetEntry *custom_preset_config_{nullptr};
|
||||
|
||||
private:
|
||||
/// Default custom preset to use on start up (pointer to entry in custom_preset_config_)
|
||||
/// Default custom preset to use on start up (name pointer from an entry in custom_preset_config_)
|
||||
const char *default_custom_preset_{nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
logger:
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
id: temp_sensor
|
||||
lambda: return 21.0;
|
||||
|
||||
climate:
|
||||
- platform: thermostat
|
||||
id: thermo_a
|
||||
name: Thermo A
|
||||
sensor: temp_sensor
|
||||
min_heating_off_time: 300s
|
||||
min_heating_run_time: 300s
|
||||
min_idle_time: 30s
|
||||
heat_action:
|
||||
- logger.log: heat
|
||||
idle_action:
|
||||
- logger.log: idle
|
||||
preset:
|
||||
- name: Away
|
||||
default_target_temperature_low: 15°C
|
||||
mode: HEAT
|
||||
- name: Eco
|
||||
default_target_temperature_low: 17°C
|
||||
- name: Night Mode
|
||||
default_target_temperature_low: 16°C
|
||||
- platform: thermostat
|
||||
id: thermo_b
|
||||
name: Thermo B
|
||||
sensor: temp_sensor
|
||||
min_heating_off_time: 300s
|
||||
min_heating_run_time: 300s
|
||||
min_idle_time: 30s
|
||||
heat_action:
|
||||
- logger.log: heat
|
||||
idle_action:
|
||||
- logger.log: idle
|
||||
preset:
|
||||
- name: Away
|
||||
default_target_temperature_low: 15°C
|
||||
mode: HEAT
|
||||
- name: Eco
|
||||
default_target_temperature_low: 17°C
|
||||
- name: Night Mode
|
||||
default_target_temperature_low: 16°C
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Tests for the thermostat preset codegen."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
def test_presets_shared_progmem_tables(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Thermostats with the same presets share one PROGMEM table per kind."""
|
||||
main_cpp = generate_main(component_config_path("presets.yaml"))
|
||||
|
||||
standard = re.findall(
|
||||
r"static constexpr thermostat::ThermostatPresetEntry (\w+)\[\] PROGMEM",
|
||||
main_cpp,
|
||||
)
|
||||
custom = re.findall(
|
||||
r"static constexpr thermostat::ThermostatCustomPresetEntry (\w+)\[\] PROGMEM",
|
||||
main_cpp,
|
||||
)
|
||||
assert len(standard) == 1
|
||||
assert len(custom) == 1
|
||||
for var in ("thermo_a", "thermo_b"):
|
||||
assert f"{var}->set_preset_config({standard[0]}, 2);" in main_cpp
|
||||
assert f"{var}->set_custom_preset_config({custom[0]}, 1);" in main_cpp
|
||||
assert "climate::CLIMATE_MODE_HEAT" in main_cpp
|
||||
Reference in New Issue
Block a user