From 44b68f140e497f0ff88d93389d16c3fe23778618 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 10 May 2022 06:41:16 +0200 Subject: [PATCH] Select enhancement (#3423) Co-authored-by: Maurice Makaay --- esphome/codegen.py | 1 + esphome/components/api/api_server.cpp | 2 +- esphome/components/api/api_server.h | 2 +- .../components/copy/select/copy_select.cpp | 2 +- esphome/components/mqtt/mqtt_select.cpp | 2 +- esphome/components/select/__init__.py | 126 +++++++++++++++++- esphome/components/select/automation.h | 40 +++++- esphome/components/select/select.cpp | 66 +++++---- esphome/components/select/select.h | 48 ++----- esphome/components/select/select_call.cpp | 122 +++++++++++++++++ esphome/components/select/select_call.h | 48 +++++++ esphome/components/select/select_traits.cpp | 11 ++ esphome/components/select/select_traits.h | 19 +++ esphome/components/web_server/web_server.cpp | 2 +- esphome/components/web_server/web_server.h | 2 +- esphome/const.py | 2 + esphome/core/controller.cpp | 6 +- esphome/core/controller.h | 2 +- esphome/cpp_types.py | 1 + tests/test5.yaml | 35 ++++- 20 files changed, 461 insertions(+), 78 deletions(-) create mode 100644 esphome/components/select/select_call.cpp create mode 100644 esphome/components/select/select_call.h create mode 100644 esphome/components/select/select_traits.cpp create mode 100644 esphome/components/select/select_traits.h diff --git a/esphome/codegen.py b/esphome/codegen.py index b862a8ce86..185e6599b1 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -64,6 +64,7 @@ from esphome.cpp_types import ( # noqa uint64, int32, int64, + size_t, const_char_ptr, NAN, esphome_ns, diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 4521cc5bfc..1f2800f298 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -255,7 +255,7 @@ void APIServer::on_number_update(number::Number *obj, float state) { #endif #ifdef USE_SELECT -void APIServer::on_select_update(select::Select *obj, const std::string &state) { +void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { if (obj->is_internal()) return; for (auto &c : this->clients_) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index fdc46922ad..f03a83fc7b 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -64,7 +64,7 @@ class APIServer : public Component, public Controller { void on_number_update(number::Number *obj, float state) override; #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj, const std::string &state) override; + void on_select_update(select::Select *obj, const std::string &state, size_t index) override; #endif #ifdef USE_LOCK void on_lock_update(lock::Lock *obj) override; diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp index 0f01c2692c..bdcbd0b42c 100644 --- a/esphome/components/copy/select/copy_select.cpp +++ b/esphome/components/copy/select/copy_select.cpp @@ -7,7 +7,7 @@ namespace copy { static const char *const TAG = "copy.select"; void CopySelect::setup() { - source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); + source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(value); }); traits.set_options(source_->traits.get_options()); diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index 7ecbf9425e..ea5130f823 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -21,7 +21,7 @@ void MQTTSelectComponent::setup() { call.set_option(state); call.perform(); }); - this->select_->add_on_state_callback([this](const std::string &state) { this->publish_state(state); }); + this->select_->add_on_state_callback([this](const std::string &state, size_t index) { this->publish_state(state); }); } void MQTTSelectComponent::dump_config() { diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index c15036e9f9..a1c73c385e 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -9,6 +9,10 @@ from esphome.const import ( CONF_OPTION, CONF_TRIGGER_ID, CONF_MQTT_ID, + CONF_CYCLE, + CONF_MODE, + CONF_OPERATION, + CONF_INDEX, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -22,14 +26,27 @@ SelectPtr = Select.operator("ptr") # Triggers SelectStateTrigger = select_ns.class_( - "SelectStateTrigger", automation.Trigger.template(cg.float_) + "SelectStateTrigger", + automation.Trigger.template(cg.std_string, cg.size_t), ) # Actions SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) +SelectSetIndexAction = select_ns.class_("SelectSetIndexAction", automation.Action) +SelectOperationAction = select_ns.class_("SelectOperationAction", automation.Action) + +# Enums +SelectOperation = select_ns.enum("SelectOperation") +SELECT_OPERATION_OPTIONS = { + "NEXT": SelectOperation.SELECT_OP_NEXT, + "PREVIOUS": SelectOperation.SELECT_OP_PREVIOUS, + "FIRST": SelectOperation.SELECT_OP_FIRST, + "LAST": SelectOperation.SELECT_OP_LAST, +} icon = cv.icon + SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), @@ -50,7 +67,9 @@ async def setup_select_core_(var, config, *, options: List[str]): for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + await automation.build_automation( + trigger, [(cg.std_string, "x"), (cg.size_t, "i")], conf + ) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) @@ -76,12 +95,18 @@ async def to_code(config): cg.add_global(select_ns.using) +OPERATION_BASE_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Select), + } +) + + @automation.register_action( "select.set", SelectSetAction, - cv.Schema( + OPERATION_BASE_SCHEMA.extend( { - cv.Required(CONF_ID): cv.use_id(Select), cv.Required(CONF_OPTION): cv.templatable(cv.string_strict), } ), @@ -92,3 +117,96 @@ async def select_set_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(config[CONF_OPTION], args, cg.std_string) cg.add(var.set_option(template_)) return var + + +@automation.register_action( + "select.set_index", + SelectSetIndexAction, + OPERATION_BASE_SCHEMA.extend( + { + cv.Required(CONF_INDEX): cv.templatable(cv.positive_int), + } + ), +) +async def select_set_index_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_INDEX], args, cg.size_t) + cg.add(var.set_index(template_)) + return var + + +@automation.register_action( + "select.operation", + SelectOperationAction, + OPERATION_BASE_SCHEMA.extend( + { + cv.Required(CONF_OPERATION): cv.templatable( + cv.enum(SELECT_OPERATION_OPTIONS, upper=True) + ), + cv.Optional(CONF_CYCLE, default=True): cv.templatable(cv.boolean), + } + ), +) +@automation.register_action( + "select.next", + SelectOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="NEXT"): cv.one_of("NEXT", upper=True), + cv.Optional(CONF_CYCLE, default=True): cv.boolean, + } + ) + ), +) +@automation.register_action( + "select.previous", + SelectOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="PREVIOUS"): cv.one_of( + "PREVIOUS", upper=True + ), + cv.Optional(CONF_CYCLE, default=True): cv.boolean, + } + ) + ), +) +@automation.register_action( + "select.first", + SelectOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="FIRST"): cv.one_of("FIRST", upper=True), + } + ) + ), +) +@automation.register_action( + "select.last", + SelectOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="LAST"): cv.one_of("LAST", upper=True), + } + ) + ), +) +async def select_operation_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + if CONF_OPERATION in config: + op_ = await cg.templatable(config[CONF_OPERATION], args, SelectOperation) + cg.add(var.set_operation(op_)) + if CONF_CYCLE in config: + cycle_ = await cg.templatable(config[CONF_CYCLE], args, bool) + cg.add(var.set_cycle(cycle_)) + if CONF_MODE in config: + cg.add(var.set_operation(SELECT_OPERATION_OPTIONS[config[CONF_MODE]])) + if CONF_CYCLE in config: + cg.add(var.set_cycle(config[CONF_CYCLE])) + return var diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h index 1e0bfed63d..1250665188 100644 --- a/esphome/components/select/automation.h +++ b/esphome/components/select/automation.h @@ -7,16 +7,16 @@ namespace esphome { namespace select { -class SelectStateTrigger : public Trigger { +class SelectStateTrigger : public Trigger { public: explicit SelectStateTrigger(Select *parent) { - parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); }); + parent->add_on_state_callback([this](const std::string &value, size_t index) { this->trigger(value, index); }); } }; template class SelectSetAction : public Action { public: - SelectSetAction(Select *select) : select_(select) {} + explicit SelectSetAction(Select *select) : select_(select) {} TEMPLATABLE_VALUE(std::string, option) void play(Ts... x) override { @@ -29,5 +29,39 @@ template class SelectSetAction : public Action { Select *select_; }; +template class SelectSetIndexAction : public Action { + public: + explicit SelectSetIndexAction(Select *select) : select_(select) {} + TEMPLATABLE_VALUE(size_t, index) + + void play(Ts... x) override { + auto call = this->select_->make_call(); + call.set_index(this->index_.value(x...)); + call.perform(); + } + + protected: + Select *select_; +}; + +template class SelectOperationAction : public Action { + public: + explicit SelectOperationAction(Select *select) : select_(select) {} + TEMPLATABLE_VALUE(bool, cycle) + TEMPLATABLE_VALUE(SelectOperation, operation) + + void play(Ts... x) override { + auto call = this->select_->make_call(); + call.with_operation(this->operation_.value(x...)); + if (this->cycle_.has_value()) { + call.with_cycle(this->cycle_.value(x...)); + } + call.perform(); + } + + protected: + Select *select_; +}; + } // namespace select } // namespace esphome diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 14f4d9277d..75edb5c8ba 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -6,37 +6,53 @@ namespace select { static const char *const TAG = "select"; -void SelectCall::perform() { - ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); - if (!this->option_.has_value()) { - ESP_LOGW(TAG, "No value set for SelectCall"); - return; - } - - const auto &traits = this->parent_->traits; - auto value = *this->option_; - auto options = traits.get_options(); - - if (std::find(options.begin(), options.end(), value) == options.end()) { - ESP_LOGW(TAG, " Option %s is not a valid option.", value.c_str()); - return; - } - - ESP_LOGD(TAG, " Option: %s", (*this->option_).c_str()); - this->parent_->control(*this->option_); -} - void Select::publish_state(const std::string &state) { - this->has_state_ = true; - this->state = state; - ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); - this->state_callback_.call(state); + auto index = this->index_of(state); + const auto *name = this->get_name().c_str(); + if (index.has_value()) { + this->has_state_ = true; + this->state = state; + ESP_LOGD(TAG, "'%s': Sending state %s (index %d)", name, state.c_str(), index.value()); + this->state_callback_.call(state, index.value()); + } else { + ESP_LOGE(TAG, "'%s': invalid state for publish_state(): %s", name, state.c_str()); + } } -void Select::add_on_state_callback(std::function &&callback) { +void Select::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +size_t Select::size() const { + auto options = traits.get_options(); + return options.size(); +} + +optional Select::index_of(const std::string &option) const { + auto options = traits.get_options(); + auto it = std::find(options.begin(), options.end(), option); + if (it == options.end()) { + return {}; + } + return std::distance(options.begin(), it); +} + +optional Select::active_index() const { + if (this->has_state()) { + return this->index_of(this->state); + } else { + return {}; + } +} + +optional Select::at(size_t index) const { + auto options = traits.get_options(); + if (index >= options.size()) { + return {}; + } + return options.at(index); +} + uint32_t Select::hash_base() { return 2812997003UL; } } // namespace select diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index db655ea34e..64870fc9a3 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -1,10 +1,10 @@ #pragma once -#include -#include #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" +#include "select_call.h" +#include "select_traits.h" namespace esphome { namespace select { @@ -17,33 +17,6 @@ namespace select { } \ } -class Select; - -class SelectCall { - public: - explicit SelectCall(Select *parent) : parent_(parent) {} - void perform(); - - SelectCall &set_option(const std::string &option) { - option_ = option; - return *this; - } - const optional &get_option() const { return option_; } - - protected: - Select *const parent_; - optional option_; -}; - -class SelectTraits { - public: - void set_options(std::vector options) { this->options_ = std::move(options); } - std::vector get_options() const { return this->options_; } - - protected: - std::vector options_; -}; - /** Base-class for all selects. * * A select can use publish_state to send out a new value. @@ -51,18 +24,23 @@ class SelectTraits { class Select : public EntityBase { public: std::string state; + SelectTraits traits; void publish_state(const std::string &state); + /// Return whether this select has gotten a full state yet. + bool has_state() const { return has_state_; } + SelectCall make_call() { return SelectCall(this); } void set(const std::string &value) { make_call().set_option(value).perform(); } - void add_on_state_callback(std::function &&callback); + // Methods that provide an API to index-based access. + size_t size() const; + optional index_of(const std::string &option) const; + optional active_index() const; + optional at(size_t index) const; - SelectTraits traits; - - /// Return whether this select has gotten a full state yet. - bool has_state() const { return has_state_; } + void add_on_state_callback(std::function &&callback); protected: friend class SelectCall; @@ -77,7 +55,7 @@ class Select : public EntityBase { uint32_t hash_base() override; - CallbackManager state_callback_; + CallbackManager state_callback_; bool has_state_{false}; }; diff --git a/esphome/components/select/select_call.cpp b/esphome/components/select/select_call.cpp new file mode 100644 index 0000000000..9442598740 --- /dev/null +++ b/esphome/components/select/select_call.cpp @@ -0,0 +1,122 @@ +#include "select_call.h" +#include "select.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace select { + +static const char *const TAG = "select"; + +SelectCall &SelectCall::set_option(const std::string &option) { + return with_operation(SELECT_OP_SET).with_option(option); +} + +SelectCall &SelectCall::set_index(size_t index) { return with_operation(SELECT_OP_SET_INDEX).with_index(index); } + +const optional &SelectCall::get_option() const { return option_; } + +SelectCall &SelectCall::select_next(bool cycle) { return with_operation(SELECT_OP_NEXT).with_cycle(cycle); } + +SelectCall &SelectCall::select_previous(bool cycle) { return with_operation(SELECT_OP_PREVIOUS).with_cycle(cycle); } + +SelectCall &SelectCall::select_first() { return with_operation(SELECT_OP_FIRST); } + +SelectCall &SelectCall::select_last() { return with_operation(SELECT_OP_LAST); } + +SelectCall &SelectCall::with_operation(SelectOperation operation) { + this->operation_ = operation; + return *this; +} + +SelectCall &SelectCall::with_cycle(bool cycle) { + this->cycle_ = cycle; + return *this; +} + +SelectCall &SelectCall::with_option(const std::string &option) { + this->option_ = option; + return *this; +} + +SelectCall &SelectCall::with_index(size_t index) { + this->index_ = index; + return *this; +} + +void SelectCall::perform() { + auto *parent = this->parent_; + const auto *name = parent->get_name().c_str(); + const auto &traits = parent->traits; + auto options = traits.get_options(); + + if (this->operation_ == SELECT_OP_NONE) { + ESP_LOGW(TAG, "'%s' - SelectCall performed without selecting an operation", name); + return; + } + if (options.empty()) { + ESP_LOGW(TAG, "'%s' - Cannot perform SelectCall, select has no options", name); + return; + } + + std::string target_value; + + if (this->operation_ == SELECT_OP_SET) { + ESP_LOGD(TAG, "'%s' - Setting", name); + if (!this->option_.has_value()) { + ESP_LOGW(TAG, "'%s' - No option value set for SelectCall", name); + return; + } + target_value = this->option_.value(); + } else if (this->operation_ == SELECT_OP_SET_INDEX) { + if (!this->index_.has_value()) { + ESP_LOGW(TAG, "'%s' - No index value set for SelectCall", name); + return; + } + if (this->index_.value() >= options.size()) { + ESP_LOGW(TAG, "'%s' - Index value %d out of bounds", name, this->index_.value()); + return; + } + target_value = options[this->index_.value()]; + } else if (this->operation_ == SELECT_OP_FIRST) { + target_value = options.front(); + } else if (this->operation_ == SELECT_OP_LAST) { + target_value = options.back(); + } else if (this->operation_ == SELECT_OP_NEXT || this->operation_ == SELECT_OP_PREVIOUS) { + auto cycle = this->cycle_; + ESP_LOGD(TAG, "'%s' - Selecting %s, with%s cycling", name, this->operation_ == SELECT_OP_NEXT ? "next" : "previous", + cycle ? "" : "out"); + if (!parent->has_state()) { + target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back(); + } else { + auto index = parent->index_of(parent->state); + if (index.has_value()) { + auto size = options.size(); + if (cycle) { + auto use_index = (size + index.value() + (this->operation_ == SELECT_OP_NEXT ? +1 : -1)) % size; + target_value = options[use_index]; + } else { + if (this->operation_ == SELECT_OP_PREVIOUS && index.value() > 0) { + target_value = options[index.value() - 1]; + } else if (this->operation_ == SELECT_OP_NEXT && index.value() < options.size() - 1) { + target_value = options[index.value() + 1]; + } else { + return; + } + } + } else { + target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back(); + } + } + } + + if (std::find(options.begin(), options.end(), target_value) == options.end()) { + ESP_LOGW(TAG, "'%s' - Option %s is not a valid option", name, target_value.c_str()); + return; + } + + ESP_LOGD(TAG, "'%s' - Set selected option to: %s", name, target_value.c_str()); + parent->control(target_value); +} + +} // namespace select +} // namespace esphome diff --git a/esphome/components/select/select_call.h b/esphome/components/select/select_call.h new file mode 100644 index 0000000000..ea4d34ab5f --- /dev/null +++ b/esphome/components/select/select_call.h @@ -0,0 +1,48 @@ +#pragma once + +#include "esphome/core/helpers.h" + +namespace esphome { +namespace select { + +class Select; + +enum SelectOperation { + SELECT_OP_NONE, + SELECT_OP_SET, + SELECT_OP_SET_INDEX, + SELECT_OP_NEXT, + SELECT_OP_PREVIOUS, + SELECT_OP_FIRST, + SELECT_OP_LAST +}; + +class SelectCall { + public: + explicit SelectCall(Select *parent) : parent_(parent) {} + void perform(); + + SelectCall &set_option(const std::string &option); + SelectCall &set_index(size_t index); + const optional &get_option() const; + + SelectCall &select_next(bool cycle); + SelectCall &select_previous(bool cycle); + SelectCall &select_first(); + SelectCall &select_last(); + + SelectCall &with_operation(SelectOperation operation); + SelectCall &with_cycle(bool cycle); + SelectCall &with_option(const std::string &option); + SelectCall &with_index(size_t index); + + protected: + Select *const parent_; + optional option_; + optional index_; + SelectOperation operation_{SELECT_OP_NONE}; + bool cycle_; +}; + +} // namespace select +} // namespace esphome diff --git a/esphome/components/select/select_traits.cpp b/esphome/components/select/select_traits.cpp new file mode 100644 index 0000000000..89da30c405 --- /dev/null +++ b/esphome/components/select/select_traits.cpp @@ -0,0 +1,11 @@ +#include "select_traits.h" + +namespace esphome { +namespace select { + +void SelectTraits::set_options(std::vector options) { this->options_ = std::move(options); } + +std::vector SelectTraits::get_options() const { return this->options_; } + +} // namespace select +} // namespace esphome diff --git a/esphome/components/select/select_traits.h b/esphome/components/select/select_traits.h new file mode 100644 index 0000000000..ccf23dc6d0 --- /dev/null +++ b/esphome/components/select/select_traits.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace esphome { +namespace select { + +class SelectTraits { + public: + void set_options(std::vector options); + std::vector get_options() const; + + protected: + std::vector options_; +}; + +} // namespace select +} // namespace esphome diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 0dfd608661..6822ce9953 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -755,7 +755,7 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail #endif #ifdef USE_SELECT -void WebServer::on_select_update(select::Select *obj, const std::string &state) { +void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index bd7acd91a0..78d0597e61 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -185,7 +185,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj, const std::string &state) override; + void on_select_update(select::Select *obj, const std::string &state, size_t index) override; /// Handle a select request under '/select/'. void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match); diff --git a/esphome/const.py b/esphome/const.py index 9f2bed28d1..fc928dc530 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -138,6 +138,7 @@ CONF_CUSTOM_FAN_MODE = "custom_fan_mode" CONF_CUSTOM_FAN_MODES = "custom_fan_modes" CONF_CUSTOM_PRESET = "custom_preset" CONF_CUSTOM_PRESETS = "custom_presets" +CONF_CYCLE = "cycle" CONF_DALLAS_ID = "dallas_id" CONF_DATA = "data" CONF_DATA_PIN = "data_pin" @@ -458,6 +459,7 @@ CONF_OPEN_DRAIN = "open_drain" CONF_OPEN_DRAIN_INTERRUPT = "open_drain_interrupt" CONF_OPEN_DURATION = "open_duration" CONF_OPEN_ENDSTOP = "open_endstop" +CONF_OPERATION = "operation" CONF_OPTIMISTIC = "optimistic" CONF_OPTION = "option" CONF_OPTIONS = "options" diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index dfcef5e4c1..7d63a3d143 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -61,8 +61,10 @@ void Controller::setup_controller(bool include_internal) { #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); }); + if (include_internal || !obj->is_internal()) { + obj->add_on_state_callback( + [this, obj](const std::string &state, size_t index) { this->on_select_update(obj, state, index); }); + } } #endif #ifdef USE_LOCK diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 0be854828b..419624a2ae 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -71,7 +71,7 @@ class Controller { virtual void on_number_update(number::Number *obj, float state){}; #endif #ifdef USE_SELECT - virtual void on_select_update(select::Select *obj, const std::string &state){}; + virtual void on_select_update(select::Select *obj, const std::string &state, size_t index){}; #endif #ifdef USE_LOCK virtual void on_lock_update(lock::Lock *obj){}; diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 2323b2578f..aafe765111 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -16,6 +16,7 @@ uint32 = global_ns.namespace("uint32_t") uint64 = global_ns.namespace("uint64_t") int32 = global_ns.namespace("int32_t") int64 = global_ns.namespace("int64_t") +size_t = global_ns.namespace("size_t") const_char_ptr = global_ns.namespace("const char *") NAN = global_ns.namespace("NAN") esphome_ns = global_ns # using namespace esphome; diff --git a/tests/test5.yaml b/tests/test5.yaml index ee90cc1149..e38f39eab6 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -154,8 +154,8 @@ select: restore_value: true on_value: - logger.log: - format: "Select changed to %s" - args: ["x.c_str()"] + format: "Select changed to %s (index %d)" + args: ["x.c_str()", "i"] set_action: - logger.log: format: "Template Select set to %s" @@ -163,11 +163,42 @@ select: - select.set: id: template_select_id option: two + - select.first: template_select_id + - select.last: + id: template_select_id + - select.previous: template_select_id + - select.next: + id: template_select_id + cycle: false + - select.operation: + id: template_select_id + operation: Previous + cycle: false + - select.operation: + id: template_select_id + operation: !lambda "return SELECT_OP_PREVIOUS;" + cycle: !lambda "return true;" + - select.set_index: + id: template_select_id + index: 1 + - select.set_index: + id: template_select_id + index: !lambda "return 1 + 1;" options: - one - two - three + - platform: modbus_controller + name: "Modbus Select Register 1000" + address: 1000 + value_type: U_WORD + optionsmap: + "Zero": 0 + "One": 1 + "Two": 2 + "Three": 3 + sensor: - platform: selec_meter total_active_energy: