Merge branch 'api_conditional_memory' into integration

This commit is contained in:
J. Nick Koston
2025-06-29 07:47:17 -05:00
12 changed files with 191 additions and 128 deletions

View File

@@ -28,19 +28,24 @@ static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11;
#endif #endif
#endif // USE_ESP32 #endif // USE_ESP32
enum class SamplingMode : uint8_t { AVG = 0, MIN = 1, MAX = 2 }; enum class SamplingMode : uint8_t {
AVG = 0,
MIN = 1,
MAX = 2,
};
const LogString *sampling_mode_to_str(SamplingMode mode); const LogString *sampling_mode_to_str(SamplingMode mode);
class Aggregator { class Aggregator {
public: public:
Aggregator(SamplingMode mode);
void add_sample(uint32_t value); void add_sample(uint32_t value);
uint32_t aggregate(); uint32_t aggregate();
Aggregator(SamplingMode mode);
protected: protected:
SamplingMode mode_{SamplingMode::AVG};
uint32_t aggr_{0}; uint32_t aggr_{0};
uint32_t samples_{0}; uint32_t samples_{0};
SamplingMode mode_{SamplingMode::AVG};
}; };
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
@@ -81,9 +86,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#endif // USE_RP2040 #endif // USE_RP2040
protected: protected:
InternalGPIOPin *pin_;
bool output_raw_{false};
uint8_t sample_count_{1}; uint8_t sample_count_{1};
bool output_raw_{false};
InternalGPIOPin *pin_;
SamplingMode sampling_mode_{SamplingMode::AVG}; SamplingMode sampling_mode_{SamplingMode::AVG};
#ifdef USE_RP2040 #ifdef USE_RP2040

View File

@@ -61,7 +61,7 @@ uint32_t Aggregator::aggregate() {
void ADCSensor::update() { void ADCSensor::update() {
float value_v = this->sample(); float value_v = this->sample();
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); ESP_LOGV(TAG, "'%s': Voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v); this->publish_state(value_v);
} }

View File

@@ -55,32 +55,40 @@ void ADCSensor::setup() {
} }
void ADCSensor::dump_config() { void ADCSensor::dump_config() {
static const char *const ATTEN_AUTO_STR = "auto";
static const char *const ATTEN_0DB_STR = "0 db";
static const char *const ATTEN_2_5DB_STR = "2.5 db";
static const char *const ATTEN_6DB_STR = "6 db";
static const char *const ATTEN_12DB_STR = "12 db";
const char *atten_str = ATTEN_AUTO_STR;
LOG_SENSOR("", "ADC Sensor", this); LOG_SENSOR("", "ADC Sensor", this);
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
if (this->autorange_) {
ESP_LOGCONFIG(TAG, " Attenuation: auto"); if (!this->autorange_) {
} else {
switch (this->attenuation_) { switch (this->attenuation_) {
case ADC_ATTEN_DB_0: case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db"); atten_str = ATTEN_0DB_STR;
break; break;
case ADC_ATTEN_DB_2_5: case ADC_ATTEN_DB_2_5:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db"); atten_str = ATTEN_2_5DB_STR;
break; break;
case ADC_ATTEN_DB_6: case ADC_ATTEN_DB_6:
ESP_LOGCONFIG(TAG, " Attenuation: 6db"); atten_str = ATTEN_6DB_STR;
break; break;
case ADC_ATTEN_DB_12_COMPAT: case ADC_ATTEN_DB_12_COMPAT:
ESP_LOGCONFIG(TAG, " Attenuation: 12db"); atten_str = ATTEN_12DB_STR;
break; break;
default: // This is to satisfy the unused ADC_ATTEN_MAX default: // This is to satisfy the unused ADC_ATTEN_MAX
break; break;
} }
} }
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Attenuation: %s\n"
" Samples: %i\n" " Samples: %i\n"
" Sampling mode: %s", " Sampling mode: %s",
this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_))); atten_str, this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }

View File

@@ -135,23 +135,26 @@ async def to_code(config):
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
for conf in config.get(CONF_ACTIONS, []): if actions := config.get(CONF_ACTIONS, []):
template_args = [] cg.add_define("USE_API_YAML_SERVICES")
func_args = [] for conf in actions:
service_arg_names = [] template_args = []
for name, var_ in conf[CONF_VARIABLES].items(): func_args = []
native = SERVICE_ARG_NATIVE_TYPES[var_] service_arg_names = []
template_args.append(native) for name, var_ in conf[CONF_VARIABLES].items():
func_args.append((native, name)) native = SERVICE_ARG_NATIVE_TYPES[var_]
service_arg_names.append(name) template_args.append(native)
templ = cg.TemplateArguments(*template_args) func_args.append((native, name))
trigger = cg.new_Pvariable( service_arg_names.append(name)
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names templ = cg.TemplateArguments(*template_args)
) trigger = cg.new_Pvariable(
cg.add(var.register_user_service(trigger)) conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
await automation.build_automation(trigger, func_args, conf) )
cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf)
if CONF_ON_CLIENT_CONNECTED in config: if CONF_ON_CLIENT_CONNECTED in config:
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")
await automation.build_automation( await automation.build_automation(
var.get_client_connected_trigger(), var.get_client_connected_trigger(),
[(cg.std_string, "client_info"), (cg.std_string, "client_address")], [(cg.std_string, "client_info"), (cg.std_string, "client_address")],
@@ -159,6 +162,7 @@ async def to_code(config):
) )
if CONF_ON_CLIENT_DISCONNECTED in config: if CONF_ON_CLIENT_DISCONNECTED in config:
cg.add_define("USE_API_CLIENT_DISCONNECTED_TRIGGER")
await automation.build_automation( await automation.build_automation(
var.get_client_disconnected_trigger(), var.get_client_disconnected_trigger(),
[(cg.std_string, "client_info"), (cg.std_string, "client_address")], [(cg.std_string, "client_info"), (cg.std_string, "client_address")],

View File

@@ -1511,7 +1511,9 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
if (correct) { if (correct) {
ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str());
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED); this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_);
#endif
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
if (homeassistant::global_homeassistant_time != nullptr) { if (homeassistant::global_homeassistant_time != nullptr) {
this->send_time_request(); this->send_time_request();

View File

@@ -184,7 +184,9 @@ void APIServer::loop() {
} }
// Rare case: handle disconnection // Rare case: handle disconnection
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_); this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
#endif
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str()); ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str());
// Swap with the last element and pop (avoids expensive vector shifts) // Swap with the last element and pop (avoids expensive vector shifts)

View File

@@ -105,7 +105,18 @@ class APIServer : public Component, public Controller {
void on_media_player_update(media_player::MediaPlayer *obj) override; void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif #endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } void register_user_service(UserServiceDescriptor *descriptor) {
#ifdef USE_API_YAML_SERVICES
// Vector is pre-allocated when services are defined in YAML
this->user_services_.push_back(descriptor);
#else
// Lazy allocate vector on first use for CustomAPIDevice
if (!this->user_services_) {
this->user_services_ = std::make_unique<std::vector<UserServiceDescriptor *>>();
}
this->user_services_->push_back(descriptor);
#endif
}
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void request_time(); void request_time();
#endif #endif
@@ -134,19 +145,34 @@ class APIServer : public Component, public Controller {
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute, void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f); std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const; const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; } const std::vector<UserServiceDescriptor *> &get_user_services() const {
#ifdef USE_API_YAML_SERVICES
return this->user_services_;
#else
static const std::vector<UserServiceDescriptor *> empty;
return this->user_services_ ? *this->user_services_ : empty;
#endif
}
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; } Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_disconnected_trigger() const { Trigger<std::string, std::string> *get_client_disconnected_trigger() const {
return this->client_disconnected_trigger_; return this->client_disconnected_trigger_;
} }
#endif
protected: protected:
void schedule_reboot_timeout_(); void schedule_reboot_timeout_();
// Pointers and pointer-like types first (4 bytes each) // Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr; std::unique_ptr<socket::Socket> socket_ = nullptr;
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>(); Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>(); Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
#endif
// 4-byte aligned types // 4-byte aligned types
uint32_t reboot_timeout_{300000}; uint32_t reboot_timeout_{300000};
@@ -157,7 +183,15 @@ class APIServer : public Component, public Controller {
std::string password_; std::string password_;
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
std::vector<HomeAssistantStateSubscription> state_subs_; std::vector<HomeAssistantStateSubscription> state_subs_;
#ifdef USE_API_YAML_SERVICES
// When services are defined in YAML, we know at compile time that services will be registered
std::vector<UserServiceDescriptor *> user_services_; std::vector<UserServiceDescriptor *> user_services_;
#else
// Services can still be registered at runtime by CustomAPIDevice components even when not
// defined in YAML. Using unique_ptr allows lazy allocation, saving 12 bytes in the common
// case where no services (YAML or custom) are used.
std::unique_ptr<std::vector<UserServiceDescriptor *>> user_services_;
#endif
// Group smaller types together // Group smaller types together
uint16_t port_{6053}; uint16_t port_{6053};

View File

@@ -69,8 +69,8 @@ class ESPColorCorrection {
protected: protected:
uint8_t gamma_table_[256]; uint8_t gamma_table_[256];
uint8_t gamma_reverse_table_[256]; uint8_t gamma_reverse_table_[256];
Color max_brightness_;
uint8_t local_brightness_{255}; uint8_t local_brightness_{255};
Color max_brightness_;
}; };
} // namespace light } // namespace light

View File

@@ -136,7 +136,7 @@ LightColorValues LightCall::validate_() {
// Color mode check // Color mode check
if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) { if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) {
ESP_LOGW(TAG, "'%s' - This light does not support color mode %s!", name, ESP_LOGW(TAG, "'%s' does not support color mode %s", name,
LOG_STR_ARG(color_mode_to_human(this->color_mode_.value()))); LOG_STR_ARG(color_mode_to_human(this->color_mode_.value())));
this->color_mode_.reset(); this->color_mode_.reset();
} }
@@ -152,20 +152,20 @@ LightColorValues LightCall::validate_() {
// Brightness exists check // Brightness exists check
if (this->brightness_.has_value() && *this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) { if (this->brightness_.has_value() && *this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name); ESP_LOGW(TAG, "'%s': setting brightness not supported", name);
this->brightness_.reset(); this->brightness_.reset();
} }
// Transition length possible check // Transition length possible check
if (this->transition_length_.has_value() && *this->transition_length_ != 0 && if (this->transition_length_.has_value() && *this->transition_length_ != 0 &&
!(color_mode & ColorCapability::BRIGHTNESS)) { !(color_mode & ColorCapability::BRIGHTNESS)) {
ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name); ESP_LOGW(TAG, "'%s': transitions not supported", name);
this->transition_length_.reset(); this->transition_length_.reset();
} }
// Color brightness exists check // Color brightness exists check
if (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) { if (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB brightness!", name); ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name);
this->color_brightness_.reset(); this->color_brightness_.reset();
} }
@@ -173,7 +173,7 @@ LightColorValues LightCall::validate_() {
if ((this->red_.has_value() && *this->red_ > 0.0f) || (this->green_.has_value() && *this->green_ > 0.0f) || if ((this->red_.has_value() && *this->red_ > 0.0f) || (this->green_.has_value() && *this->green_ > 0.0f) ||
(this->blue_.has_value() && *this->blue_ > 0.0f)) { (this->blue_.has_value() && *this->blue_ > 0.0f)) {
if (!(color_mode & ColorCapability::RGB)) { if (!(color_mode & ColorCapability::RGB)) {
ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB color!", name); ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name);
this->red_.reset(); this->red_.reset();
this->green_.reset(); this->green_.reset();
this->blue_.reset(); this->blue_.reset();
@@ -183,14 +183,14 @@ LightColorValues LightCall::validate_() {
// White value exists check // White value exists check
if (this->white_.has_value() && *this->white_ > 0.0f && if (this->white_.has_value() && *this->white_ > 0.0f &&
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) { !(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s' - This color mode does not support setting white value!", name); ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name);
this->white_.reset(); this->white_.reset();
} }
// Color temperature exists check // Color temperature exists check
if (this->color_temperature_.has_value() && if (this->color_temperature_.has_value() &&
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) { !(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s' - This color mode does not support setting color temperature!", name); ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name);
this->color_temperature_.reset(); this->color_temperature_.reset();
} }
@@ -198,7 +198,7 @@ LightColorValues LightCall::validate_() {
if ((this->cold_white_.has_value() && *this->cold_white_ > 0.0f) || if ((this->cold_white_.has_value() && *this->cold_white_ > 0.0f) ||
(this->warm_white_.has_value() && *this->warm_white_ > 0.0f)) { (this->warm_white_.has_value() && *this->warm_white_ > 0.0f)) {
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) { if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s' - This color mode does not support setting cold/warm white value!", name); ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name);
this->cold_white_.reset(); this->cold_white_.reset();
this->warm_white_.reset(); this->warm_white_.reset();
} }
@@ -208,7 +208,7 @@ LightColorValues LightCall::validate_() {
if (name_##_.has_value()) { \ if (name_##_.has_value()) { \
auto val = *name_##_; \ auto val = *name_##_; \
if (val < (min) || val > (max)) { \ if (val < (min) || val > (max)) { \
ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [%.1f - %.1f]!", name, LOG_STR_LITERAL(upper_name), val, \ ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \
(min), (max)); \ (min), (max)); \
name_##_ = clamp(val, (min), (max)); \ name_##_ = clamp(val, (min), (max)); \
} \ } \
@@ -270,7 +270,7 @@ LightColorValues LightCall::validate_() {
// Flash length check // Flash length check
if (this->has_flash_() && *this->flash_length_ == 0) { if (this->has_flash_() && *this->flash_length_ == 0) {
ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name); ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name);
this->flash_length_.reset(); this->flash_length_.reset();
} }
@@ -284,18 +284,18 @@ LightColorValues LightCall::validate_() {
// validate effect index // validate effect index
if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) { if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
ESP_LOGW(TAG, "'%s' - Invalid effect index %" PRIu32 "!", name, *this->effect_); ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, *this->effect_);
this->effect_.reset(); this->effect_.reset();
} }
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name); ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name);
this->transition_length_.reset(); this->transition_length_.reset();
this->flash_length_.reset(); this->flash_length_.reset();
} }
if (this->has_flash_() && this->has_transition_()) { if (this->has_flash_() && this->has_transition_()) {
ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name); ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name);
this->transition_length_.reset(); this->transition_length_.reset();
} }
@@ -311,7 +311,7 @@ LightColorValues LightCall::validate_() {
} }
if (this->has_transition_() && !supports_transition) { if (this->has_transition_() && !supports_transition) {
ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name); ESP_LOGW(TAG, "'%s': transitions not supported", name);
this->transition_length_.reset(); this->transition_length_.reset();
} }
@@ -320,7 +320,7 @@ LightColorValues LightCall::validate_() {
// Reason: When user turns off the light in frontend, the effect should also stop // Reason: When user turns off the light in frontend, the effect should also stop
if (!this->has_flash_() && !this->state_.value_or(v.is_on())) { if (!this->has_flash_() && !this->state_.value_or(v.is_on())) {
if (this->has_effect_()) { if (this->has_effect_()) {
ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name); ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name);
this->effect_.reset(); this->effect_.reset();
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) { } else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
// Auto turn off effect // Auto turn off effect
@@ -348,7 +348,7 @@ void LightCall::transform_parameters_() {
!(*this->color_mode_ & ColorCapability::WHITE) && // !(*this->color_mode_ & ColorCapability::WHITE) && //
!(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && // !(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && //
traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) { traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) {
ESP_LOGD(TAG, "'%s' - Setting cold/warm white channels using white/color temperature values.", ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
this->parent_->get_name().c_str()); this->parent_->get_name().c_str());
if (this->color_temperature_.has_value()) { if (this->color_temperature_.has_value()) {
const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds());
@@ -388,8 +388,8 @@ ColorMode LightCall::compute_color_mode_() {
// Don't change if the current mode is suitable. // Don't change if the current mode is suitable.
if (suitable_modes.count(current_mode) > 0) { if (suitable_modes.count(current_mode) > 0) {
ESP_LOGI(TAG, "'%s' - Keeping current color mode %s for call without color mode.", ESP_LOGI(TAG, "'%s': color mode not specified; retaining %s", this->parent_->get_name().c_str(),
this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(current_mode))); LOG_STR_ARG(color_mode_to_human(current_mode)));
return current_mode; return current_mode;
} }
@@ -398,7 +398,7 @@ ColorMode LightCall::compute_color_mode_() {
if (supported_modes.count(mode) == 0) if (supported_modes.count(mode) == 0)
continue; continue;
ESP_LOGI(TAG, "'%s' - Using color mode %s for call without color mode.", this->parent_->get_name().c_str(), ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(),
LOG_STR_ARG(color_mode_to_human(mode))); LOG_STR_ARG(color_mode_to_human(mode)));
return mode; return mode;
} }
@@ -406,8 +406,8 @@ ColorMode LightCall::compute_color_mode_() {
// There's no supported mode for this call, so warn, use the current more or a mode at random and let validation strip // There's no supported mode for this call, so warn, use the current more or a mode at random and let validation strip
// out whatever we don't support. // out whatever we don't support.
auto color_mode = current_mode != ColorMode::UNKNOWN ? current_mode : *supported_modes.begin(); auto color_mode = current_mode != ColorMode::UNKNOWN ? current_mode : *supported_modes.begin();
ESP_LOGW(TAG, "'%s' - No color mode suitable for this call supported, defaulting to %s!", ESP_LOGW(TAG, "'%s': no suitable color mode supported; defaulting to %s", this->parent_->get_name().c_str(),
this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(color_mode))); LOG_STR_ARG(color_mode_to_human(color_mode)));
return color_mode; return color_mode;
} }
std::set<ColorMode> LightCall::get_suitable_color_modes_() { std::set<ColorMode> LightCall::get_suitable_color_modes_() {
@@ -472,7 +472,7 @@ LightCall &LightCall::set_effect(const std::string &effect) {
} }
} }
if (!found) { if (!found) {
ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str()); ESP_LOGW(TAG, "'%s': no such effect '%s'", this->parent_->get_name().c_str(), effect.c_str());
} }
return *this; return *this;
} }

View File

@@ -225,6 +225,11 @@ class LightState : public EntityBase, public Component {
/// Gamma correction factor for the light. /// Gamma correction factor for the light.
float gamma_correct_{}; float gamma_correct_{};
/// Whether the light value should be written in the next cycle.
bool next_write_{true};
// for effects, true if a transformer (transition) is active.
bool is_transformer_active_ = false;
/// Object used to store the persisted values of the light. /// Object used to store the persisted values of the light.
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
@@ -247,10 +252,6 @@ class LightState : public EntityBase, public Component {
/// Restore mode of the light. /// Restore mode of the light.
LightRestoreMode restore_mode_; LightRestoreMode restore_mode_;
/// Whether the light value should be written in the next cycle.
bool next_write_{true};
// for effects, true if a transformer (transition) is active.
bool is_transformer_active_ = false;
}; };
} // namespace light } // namespace light

View File

@@ -997,7 +997,7 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
auto config = this->preset_config_.find(preset); auto config = this->preset_config_.find(preset);
if (config != this->preset_config_.end()) { if (config != this->preset_config_.end()) {
ESP_LOGI(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset))); ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) || if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) ||
this->preset.value() != preset) { this->preset.value() != preset) {
// Fire any preset changed trigger if defined // Fire any preset changed trigger if defined
@@ -1015,7 +1015,7 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
this->custom_preset.reset(); this->custom_preset.reset();
this->preset = preset; this->preset = preset;
} else { } else {
ESP_LOGE(TAG, "Preset %s is not configured, ignoring.", LOG_STR_ARG(climate::climate_preset_to_string(preset))); ESP_LOGW(TAG, "Preset %s not configured; ignoring", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
} }
} }
@@ -1023,7 +1023,7 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset)
auto config = this->custom_preset_config_.find(custom_preset); auto config = this->custom_preset_config_.find(custom_preset);
if (config != this->custom_preset_config_.end()) { if (config != this->custom_preset_config_.end()) {
ESP_LOGI(TAG, "Custom preset %s requested", custom_preset.c_str()); ESP_LOGV(TAG, "Custom preset %s requested", custom_preset.c_str());
if (this->change_preset_internal_(config->second) || (!this->custom_preset.has_value()) || if (this->change_preset_internal_(config->second) || (!this->custom_preset.has_value()) ||
this->custom_preset.value() != custom_preset) { this->custom_preset.value() != custom_preset) {
// Fire any preset changed trigger if defined // Fire any preset changed trigger if defined
@@ -1041,7 +1041,7 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset)
this->preset.reset(); this->preset.reset();
this->custom_preset = custom_preset; this->custom_preset = custom_preset;
} else { } else {
ESP_LOGE(TAG, "Custom Preset %s is not configured, ignoring.", custom_preset.c_str()); ESP_LOGW(TAG, "Custom preset %s not configured; ignoring", custom_preset.c_str());
} }
} }
@@ -1298,7 +1298,7 @@ void ThermostatClimate::dump_config() {
if (this->supports_two_points_) { if (this->supports_two_points_) {
ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
} }
ESP_LOGCONFIG(TAG, " Start-up Delay Enabled: %s", YESNO(this->use_startup_delay_)); ESP_LOGCONFIG(TAG, " Use Start-up Delay: %s", YESNO(this->use_startup_delay_));
if (this->supports_cool_) { if (this->supports_cool_) {
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Cooling Parameters:\n" " Cooling Parameters:\n"
@@ -1353,44 +1353,47 @@ void ThermostatClimate::dump_config() {
} }
ESP_LOGCONFIG(TAG, " Minimum Idle Time: %" PRIu32 "s", this->timer_[thermostat::TIMER_IDLE_ON].time / 1000); ESP_LOGCONFIG(TAG, " Minimum Idle Time: %" PRIu32 "s", this->timer_[thermostat::TIMER_IDLE_ON].time / 1000);
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Supports AUTO: %s\n" " Supported MODES:\n"
" Supports HEAT/COOL: %s\n" " AUTO: %s\n"
" Supports COOL: %s\n" " HEAT/COOL: %s\n"
" Supports DRY: %s\n" " HEAT: %s\n"
" Supports FAN_ONLY: %s\n" " COOL: %s\n"
" Supports FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n" " DRY: %s\n"
" Supports FAN_ONLY_COOLING: %s", " FAN_ONLY: %s\n"
YESNO(this->supports_auto_), YESNO(this->supports_heat_cool_), YESNO(this->supports_cool_), " FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n"
YESNO(this->supports_dry_), YESNO(this->supports_fan_only_), " FAN_ONLY_COOLING: %s",
YESNO(this->supports_auto_), YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_),
YESNO(this->supports_cool_), YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
YESNO(this->supports_fan_only_action_uses_fan_mode_timer_), YESNO(this->supports_fan_only_cooling_)); YESNO(this->supports_fan_only_action_uses_fan_mode_timer_), YESNO(this->supports_fan_only_cooling_));
if (this->supports_cool_) { if (this->supports_cool_) {
ESP_LOGCONFIG(TAG, " Supports FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_)); ESP_LOGCONFIG(TAG, " FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_));
} }
if (this->supports_heat_) { if (this->supports_heat_) {
ESP_LOGCONFIG(TAG, " Supports FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_)); ESP_LOGCONFIG(TAG, " FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_));
} }
ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_));
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Supports FAN MODE ON: %s\n" " Supported FAN MODES:\n"
" Supports FAN MODE OFF: %s\n" " ON: %s\n"
" Supports FAN MODE AUTO: %s\n" " OFF: %s\n"
" Supports FAN MODE LOW: %s\n" " AUTO: %s\n"
" Supports FAN MODE MEDIUM: %s\n" " LOW: %s\n"
" Supports FAN MODE HIGH: %s\n" " MEDIUM: %s\n"
" Supports FAN MODE MIDDLE: %s\n" " HIGH: %s\n"
" Supports FAN MODE FOCUS: %s\n" " MIDDLE: %s\n"
" Supports FAN MODE DIFFUSE: %s\n" " FOCUS: %s\n"
" Supports FAN MODE QUIET: %s", " DIFFUSE: %s\n"
" QUIET: %s",
YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_), YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_), YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_), YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_), YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_)); YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_));
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Supports SWING MODE BOTH: %s\n" " Supported SWING MODES:\n"
" Supports SWING MODE OFF: %s\n" " BOTH: %s\n"
" Supports SWING MODE HORIZONTAL: %s\n" " OFF: %s\n"
" Supports SWING MODE VERTICAL: %s\n" " HORIZONTAL: %s\n"
" VERTICAL: %s\n"
" Supports TWO SET POINTS: %s", " Supports TWO SET POINTS: %s",
YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_), YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_),
YESNO(this->supports_swing_mode_horizontal_), YESNO(this->supports_swing_mode_vertical_), YESNO(this->supports_swing_mode_horizontal_), YESNO(this->supports_swing_mode_vertical_),

View File

@@ -13,7 +13,7 @@
namespace esphome { namespace esphome {
namespace thermostat { namespace thermostat {
enum ThermostatClimateTimerIndex : size_t { enum ThermostatClimateTimerIndex : uint8_t {
TIMER_COOLING_MAX_RUN_TIME = 0, TIMER_COOLING_MAX_RUN_TIME = 0,
TIMER_COOLING_OFF = 1, TIMER_COOLING_OFF = 1,
TIMER_COOLING_ON = 2, TIMER_COOLING_ON = 2,
@@ -26,7 +26,11 @@ enum ThermostatClimateTimerIndex : size_t {
TIMER_IDLE_ON = 9, TIMER_IDLE_ON = 9,
}; };
enum OnBootRestoreFrom : size_t { MEMORY = 0, DEFAULT_PRESET = 1 }; enum OnBootRestoreFrom : uint8_t {
MEMORY = 0,
DEFAULT_PRESET = 1,
};
struct ThermostatClimateTimer { struct ThermostatClimateTimer {
bool active; bool active;
uint32_t time; uint32_t time;
@@ -65,7 +69,7 @@ class ThermostatClimate : public climate::Climate, public Component {
void set_default_preset(const std::string &custom_preset); void set_default_preset(const std::string &custom_preset);
void set_default_preset(climate::ClimatePreset preset); void set_default_preset(climate::ClimatePreset preset);
void set_on_boot_restore_from(thermostat::OnBootRestoreFrom on_boot_restore_from); void set_on_boot_restore_from(OnBootRestoreFrom on_boot_restore_from);
void set_set_point_minimum_differential(float differential); void set_set_point_minimum_differential(float differential);
void set_cool_deadband(float deadband); void set_cool_deadband(float deadband);
void set_cool_overrun(float overrun); void set_cool_overrun(float overrun);
@@ -240,10 +244,8 @@ class ThermostatClimate : public climate::Climate, public Component {
void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config, void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config,
bool is_default_preset); bool is_default_preset);
/// The sensor used for getting the current temperature /// Minimum allowable duration in seconds for action timers
sensor::Sensor *sensor_{nullptr}; const uint8_t min_timer_duration_{1};
/// The sensor used for getting the current humidity
sensor::Sensor *humidity_sensor_{nullptr};
/// Whether the controller supports auto/cooling/drying/fanning/heating. /// Whether the controller supports auto/cooling/drying/fanning/heating.
/// ///
@@ -310,6 +312,31 @@ class ThermostatClimate : public climate::Climate, public Component {
/// setup_complete_ blocks modifying/resetting the temps immediately after boot /// setup_complete_ blocks modifying/resetting the temps immediately after boot
bool setup_complete_{false}; bool setup_complete_{false};
/// Store previously-known temperatures
///
/// These are used to determine when the temperature change trigger/action needs to be called
float prev_target_temperature_{NAN};
float prev_target_temperature_low_{NAN};
float prev_target_temperature_high_{NAN};
/// Minimum differential required between set points
float set_point_minimum_differential_{0};
/// Hysteresis values used for computing climate actions
float cooling_deadband_{0};
float cooling_overrun_{0};
float heating_deadband_{0};
float heating_overrun_{0};
/// Maximum allowable temperature deltas before engaging supplemental cooling/heating actions
float supplemental_cool_delta_{0};
float supplemental_heat_delta_{0};
/// The sensor used for getting the current temperature
sensor::Sensor *sensor_{nullptr};
/// The sensor used for getting the current humidity
sensor::Sensor *humidity_sensor_{nullptr};
/// The trigger to call when the controller should switch to cooling action/mode. /// The trigger to call when the controller should switch to cooling action/mode.
/// ///
/// A null value for this attribute means that the controller has no cooling action /// A null value for this attribute means that the controller has no cooling action
@@ -399,7 +426,7 @@ class ThermostatClimate : public climate::Climate, public Component {
/// The trigger to call when the target temperature(s) change(es). /// The trigger to call when the target temperature(s) change(es).
Trigger<> *temperature_change_trigger_{nullptr}; Trigger<> *temperature_change_trigger_{nullptr};
/// The triggr to call when the preset mode changes /// The trigger to call when the preset mode changes
Trigger<> *preset_change_trigger_{nullptr}; Trigger<> *preset_change_trigger_{nullptr};
/// A reference to the trigger that was previously active. /// A reference to the trigger that was previously active.
@@ -411,6 +438,10 @@ class ThermostatClimate : public climate::Climate, public Component {
Trigger<> *prev_mode_trigger_{nullptr}; Trigger<> *prev_mode_trigger_{nullptr};
Trigger<> *prev_swing_mode_trigger_{nullptr}; Trigger<> *prev_swing_mode_trigger_{nullptr};
/// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior
/// state will attempt to be restored if possible
OnBootRestoreFrom on_boot_restore_from_{OnBootRestoreFrom::MEMORY};
/// Store previously-known states /// Store previously-known states
/// ///
/// These are used to determine when a trigger/action needs to be called /// These are used to determine when a trigger/action needs to be called
@@ -419,28 +450,10 @@ class ThermostatClimate : public climate::Climate, public Component {
climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF}; climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF};
climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF}; climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF};
/// Store previously-known temperatures /// Default standard preset to use on start up
/// climate::ClimatePreset default_preset_{};
/// These are used to determine when the temperature change trigger/action needs to be called /// Default custom preset to use on start up
float prev_target_temperature_{NAN}; std::string default_custom_preset_{};
float prev_target_temperature_low_{NAN};
float prev_target_temperature_high_{NAN};
/// Minimum differential required between set points
float set_point_minimum_differential_{0};
/// Hysteresis values used for computing climate actions
float cooling_deadband_{0};
float cooling_overrun_{0};
float heating_deadband_{0};
float heating_overrun_{0};
/// Maximum allowable temperature deltas before engauging supplemental cooling/heating actions
float supplemental_cool_delta_{0};
float supplemental_heat_delta_{0};
/// Minimum allowable duration in seconds for action timers
const uint8_t min_timer_duration_{1};
/// Climate action timers /// Climate action timers
std::vector<ThermostatClimateTimer> timer_{ std::vector<ThermostatClimateTimer> timer_{
@@ -460,15 +473,6 @@ class ThermostatClimate : public climate::Climate, public Component {
std::map<climate::ClimatePreset, ThermostatClimateTargetTempConfig> preset_config_{}; std::map<climate::ClimatePreset, ThermostatClimateTargetTempConfig> preset_config_{};
/// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset") /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset")
std::map<std::string, ThermostatClimateTargetTempConfig> custom_preset_config_{}; std::map<std::string, ThermostatClimateTargetTempConfig> custom_preset_config_{};
/// Default standard preset to use on start up
climate::ClimatePreset default_preset_{};
/// Default custom preset to use on start up
std::string default_custom_preset_{};
/// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior
/// state will attempt to be restored if possible
thermostat::OnBootRestoreFrom on_boot_restore_from_{thermostat::OnBootRestoreFrom::MEMORY};
}; };
} // namespace thermostat } // namespace thermostat