mirror of
https://github.com/esphome/esphome.git
synced 2025-10-31 23:21:54 +00:00
select by index
This commit is contained in:
@@ -877,7 +877,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *select = static_cast<select::Select *>(entity);
|
auto *select = static_cast<select::Select *>(entity);
|
||||||
SelectStateResponse resp;
|
SelectStateResponse resp;
|
||||||
resp.set_state(StringRef(select->state));
|
resp.set_state(StringRef(select->current_option()));
|
||||||
resp.missing_state = !select->has_state();
|
resp.missing_state = !select->has_state();
|
||||||
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
namespace esphome::logger {
|
namespace esphome::logger {
|
||||||
|
|
||||||
void LoggerLevelSelect::publish_state(int level) {
|
void LoggerLevelSelect::publish_state(int level) {
|
||||||
const auto &option = this->at(level_to_index(level));
|
auto index = level_to_index(level);
|
||||||
if (!option)
|
if (!this->has_index(index))
|
||||||
return;
|
return;
|
||||||
Select::publish_state(option.value());
|
Select::publish_state(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoggerLevelSelect::setup() {
|
void LoggerLevelSelect::setup() {
|
||||||
|
|||||||
@@ -7,24 +7,39 @@ namespace select {
|
|||||||
|
|
||||||
static const char *const TAG = "select";
|
static const char *const TAG = "select";
|
||||||
|
|
||||||
void Select::publish_state(const std::string &state) {
|
void Select::publish_state(const std::string &state) { this->publish_state(state.c_str()); }
|
||||||
|
|
||||||
|
void Select::publish_state(const char *state) {
|
||||||
auto index = this->index_of(state);
|
auto index = this->index_of(state);
|
||||||
const auto *name = this->get_name().c_str();
|
|
||||||
if (index.has_value()) {
|
if (index.has_value()) {
|
||||||
this->set_has_state(true);
|
this->publish_state(index.value());
|
||||||
this->state = state;
|
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", name, state.c_str(), index.value());
|
|
||||||
this->state_callback_.call(state, index.value());
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "'%s': invalid state for publish_state(): %s", name, state.c_str());
|
ESP_LOGE(TAG, "'%s': invalid state for publish_state(): %s", this->get_name().c_str(), state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Select::publish_state(size_t index) {
|
||||||
|
if (!this->has_index(index)) {
|
||||||
|
ESP_LOGE(TAG, "'%s': invalid index for publish_state(): %zu", this->get_name().c_str(), index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const char *option = this->option_at(index);
|
||||||
|
this->set_has_state(true);
|
||||||
|
this->active_index_ = index;
|
||||||
|
ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", this->get_name().c_str(), option, index);
|
||||||
|
// Callback signature requires std::string, create temporary for compatibility
|
||||||
|
this->state_callback_.call(std::string(option), index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Select::current_option() const { return this->option_at(this->active_index_); }
|
||||||
|
|
||||||
void Select::add_on_state_callback(std::function<void(std::string, size_t)> &&callback) {
|
void Select::add_on_state_callback(std::function<void(std::string, size_t)> &&callback) {
|
||||||
this->state_callback_.add(std::move(callback));
|
this->state_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Select::has_option(const std::string &option) const { return this->index_of(option).has_value(); }
|
bool Select::has_option(const std::string &option) const { return this->index_of(option.c_str()).has_value(); }
|
||||||
|
|
||||||
|
bool Select::has_option(const char *option) const { return this->index_of(option).has_value(); }
|
||||||
|
|
||||||
bool Select::has_index(size_t index) const { return index < this->size(); }
|
bool Select::has_index(size_t index) const { return index < this->size(); }
|
||||||
|
|
||||||
@@ -33,10 +48,12 @@ size_t Select::size() const {
|
|||||||
return options.size();
|
return options.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<size_t> Select::index_of(const std::string &option) const {
|
optional<size_t> Select::index_of(const std::string &option) const { return this->index_of(option.c_str()); }
|
||||||
|
|
||||||
|
optional<size_t> Select::index_of(const char *option) const {
|
||||||
const auto &options = traits.get_options();
|
const auto &options = traits.get_options();
|
||||||
for (size_t i = 0; i < options.size(); i++) {
|
for (size_t i = 0; i < options.size(); i++) {
|
||||||
if (strcmp(options[i], option.c_str()) == 0) {
|
if (strcmp(options[i], option) == 0) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,7 +62,7 @@ optional<size_t> Select::index_of(const std::string &option) const {
|
|||||||
|
|
||||||
optional<size_t> Select::active_index() const {
|
optional<size_t> Select::active_index() const {
|
||||||
if (this->has_state()) {
|
if (this->has_state()) {
|
||||||
return this->index_of(this->state);
|
return this->active_index_;
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,16 +30,21 @@ namespace select {
|
|||||||
*/
|
*/
|
||||||
class Select : public EntityBase {
|
class Select : public EntityBase {
|
||||||
public:
|
public:
|
||||||
std::string state;
|
|
||||||
SelectTraits traits;
|
SelectTraits traits;
|
||||||
|
|
||||||
void publish_state(const std::string &state);
|
void publish_state(const std::string &state);
|
||||||
|
void publish_state(const char *state);
|
||||||
|
void publish_state(size_t index);
|
||||||
|
|
||||||
|
/// Return the currently selected option (as const char* from flash).
|
||||||
|
const char *current_option() const;
|
||||||
|
|
||||||
/// Instantiate a SelectCall object to modify this select component's state.
|
/// Instantiate a SelectCall object to modify this select component's state.
|
||||||
SelectCall make_call() { return SelectCall(this); }
|
SelectCall make_call() { return SelectCall(this); }
|
||||||
|
|
||||||
/// Return whether this select component contains the provided option.
|
/// Return whether this select component contains the provided option.
|
||||||
bool has_option(const std::string &option) const;
|
bool has_option(const std::string &option) const;
|
||||||
|
bool has_option(const char *option) const;
|
||||||
|
|
||||||
/// Return whether this select component contains the provided index offset.
|
/// Return whether this select component contains the provided index offset.
|
||||||
bool has_index(size_t index) const;
|
bool has_index(size_t index) const;
|
||||||
@@ -49,6 +54,7 @@ class Select : public EntityBase {
|
|||||||
|
|
||||||
/// Find the (optional) index offset of the provided option value.
|
/// Find the (optional) index offset of the provided option value.
|
||||||
optional<size_t> index_of(const std::string &option) const;
|
optional<size_t> index_of(const std::string &option) const;
|
||||||
|
optional<size_t> index_of(const char *option) const;
|
||||||
|
|
||||||
/// Return the (optional) index offset of the currently active option.
|
/// Return the (optional) index offset of the currently active option.
|
||||||
optional<size_t> active_index() const;
|
optional<size_t> active_index() const;
|
||||||
@@ -64,6 +70,18 @@ class Select : public EntityBase {
|
|||||||
protected:
|
protected:
|
||||||
friend class SelectCall;
|
friend class SelectCall;
|
||||||
|
|
||||||
|
size_t active_index_{0};
|
||||||
|
|
||||||
|
/** Set the value of the select by index, this is an optional virtual method.
|
||||||
|
*
|
||||||
|
* This method is called by the SelectCall when the index is already known.
|
||||||
|
* Default implementation converts to string and calls control().
|
||||||
|
* Override this to work directly with indices and avoid string conversions.
|
||||||
|
*
|
||||||
|
* @param index The index as validated by the SelectCall.
|
||||||
|
*/
|
||||||
|
virtual void control(size_t index) { this->control(this->option_at(index)); }
|
||||||
|
|
||||||
/** Set the value of the select, this is a virtual method that each select integration must implement.
|
/** Set the value of the select, this is a virtual method that each select integration must implement.
|
||||||
*
|
*
|
||||||
* This method is called by the SelectCall.
|
* This method is called by the SelectCall.
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ SelectCall &SelectCall::set_option(const std::string &option) {
|
|||||||
return with_operation(SELECT_OP_SET).with_option(option);
|
return with_operation(SELECT_OP_SET).with_option(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectCall &SelectCall::set_option(const char *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); }
|
SelectCall &SelectCall::set_index(size_t index) { return with_operation(SELECT_OP_SET_INDEX).with_index(index); }
|
||||||
|
|
||||||
SelectCall &SelectCall::select_next(bool cycle) { return with_operation(SELECT_OP_NEXT).with_cycle(cycle); }
|
SelectCall &SelectCall::select_next(bool cycle) { return with_operation(SELECT_OP_NEXT).with_cycle(cycle); }
|
||||||
@@ -31,8 +33,11 @@ SelectCall &SelectCall::with_cycle(bool cycle) {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectCall &SelectCall::with_option(const std::string &option) {
|
SelectCall &SelectCall::with_option(const std::string &option) { return this->with_option(option.c_str()); }
|
||||||
this->option_ = option;
|
|
||||||
|
SelectCall &SelectCall::with_option(const char *option) {
|
||||||
|
// Find the option index - this validates the option exists
|
||||||
|
this->index_ = this->parent_->index_of(option);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,64 +61,52 @@ void SelectCall::perform() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string target_value;
|
size_t target_index;
|
||||||
|
|
||||||
if (this->operation_ == SELECT_OP_SET) {
|
if (this->operation_ == SELECT_OP_SET || this->operation_ == SELECT_OP_SET_INDEX) {
|
||||||
ESP_LOGD(TAG, "'%s' - Setting", name);
|
if (this->operation_ == SELECT_OP_SET) {
|
||||||
if (!this->option_.has_value()) {
|
ESP_LOGD(TAG, "'%s' - Setting", name);
|
||||||
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()) {
|
if (!this->index_.has_value()) {
|
||||||
ESP_LOGW(TAG, "'%s' - No index value set for SelectCall", name);
|
ESP_LOGW(TAG, "'%s' - No option value set for SelectCall", name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->index_.value() >= options.size()) {
|
if (this->index_.value() >= options.size()) {
|
||||||
ESP_LOGW(TAG, "'%s' - Index value %zu out of bounds", name, this->index_.value());
|
ESP_LOGW(TAG, "'%s' - Index value %zu out of bounds", name, this->index_.value());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
target_value = options[this->index_.value()];
|
target_index = this->index_.value();
|
||||||
} else if (this->operation_ == SELECT_OP_FIRST) {
|
} else if (this->operation_ == SELECT_OP_FIRST) {
|
||||||
target_value = options.front();
|
target_index = 0;
|
||||||
} else if (this->operation_ == SELECT_OP_LAST) {
|
} else if (this->operation_ == SELECT_OP_LAST) {
|
||||||
target_value = options.back();
|
target_index = options.size() - 1;
|
||||||
} else if (this->operation_ == SELECT_OP_NEXT || this->operation_ == SELECT_OP_PREVIOUS) {
|
} else if (this->operation_ == SELECT_OP_NEXT || this->operation_ == SELECT_OP_PREVIOUS) {
|
||||||
auto cycle = this->cycle_;
|
auto cycle = this->cycle_;
|
||||||
ESP_LOGD(TAG, "'%s' - Selecting %s, with%s cycling", name, this->operation_ == SELECT_OP_NEXT ? "next" : "previous",
|
ESP_LOGD(TAG, "'%s' - Selecting %s, with%s cycling", name, this->operation_ == SELECT_OP_NEXT ? "next" : "previous",
|
||||||
cycle ? "" : "out");
|
cycle ? "" : "out");
|
||||||
if (!parent->has_state()) {
|
if (!parent->has_state()) {
|
||||||
target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back();
|
target_index = this->operation_ == SELECT_OP_NEXT ? 0 : options.size() - 1;
|
||||||
} else {
|
} else {
|
||||||
auto index = parent->index_of(parent->state);
|
// Use cached active_index_ instead of index_of() lookup
|
||||||
if (index.has_value()) {
|
auto index = parent->active_index_;
|
||||||
auto size = options.size();
|
auto size = options.size();
|
||||||
if (cycle) {
|
if (cycle) {
|
||||||
auto use_index = (size + index.value() + (this->operation_ == SELECT_OP_NEXT ? +1 : -1)) % size;
|
target_index = (size + index + (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 {
|
} else {
|
||||||
target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back();
|
if (this->operation_ == SELECT_OP_PREVIOUS && index > 0) {
|
||||||
|
target_index = index - 1;
|
||||||
|
} else if (this->operation_ == SELECT_OP_NEXT && index < options.size() - 1) {
|
||||||
|
target_index = index + 1;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parent->has_option(target_value)) {
|
// All operations use indices, call control() by index to avoid string conversion
|
||||||
ESP_LOGW(TAG, "'%s' - Option %s is not a valid option", name, target_value.c_str());
|
ESP_LOGD(TAG, "'%s' - Set selected option to: %s", name, options[target_index]);
|
||||||
return;
|
parent->control(target_index);
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "'%s' - Set selected option to: %s", name, target_value.c_str());
|
|
||||||
parent->control(target_value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace select
|
} // namespace select
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class SelectCall {
|
|||||||
void perform();
|
void perform();
|
||||||
|
|
||||||
SelectCall &set_option(const std::string &option);
|
SelectCall &set_option(const std::string &option);
|
||||||
|
SelectCall &set_option(const char *option);
|
||||||
SelectCall &set_index(size_t index);
|
SelectCall &set_index(size_t index);
|
||||||
|
|
||||||
SelectCall &select_next(bool cycle);
|
SelectCall &select_next(bool cycle);
|
||||||
@@ -33,11 +34,11 @@ class SelectCall {
|
|||||||
SelectCall &with_operation(SelectOperation operation);
|
SelectCall &with_operation(SelectOperation operation);
|
||||||
SelectCall &with_cycle(bool cycle);
|
SelectCall &with_cycle(bool cycle);
|
||||||
SelectCall &with_option(const std::string &option);
|
SelectCall &with_option(const std::string &option);
|
||||||
|
SelectCall &with_option(const char *option);
|
||||||
SelectCall &with_index(size_t index);
|
SelectCall &with_index(size_t index);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Select *const parent_;
|
Select *const parent_;
|
||||||
optional<std::string> option_;
|
|
||||||
optional<size_t> index_;
|
optional<size_t> index_;
|
||||||
SelectOperation operation_{SELECT_OP_NONE};
|
SelectOperation operation_{SELECT_OP_NONE};
|
||||||
bool cycle_;
|
bool cycle_;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ void TemplateSelect::setup() {
|
|||||||
ESP_LOGD(TAG, "State from initial: %s", this->option_at(index));
|
ESP_LOGD(TAG, "State from initial: %s", this->option_at(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
this->publish_state(this->at(index).value());
|
this->publish_state(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateSelect::update() {
|
void TemplateSelect::update() {
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ void TuyaSelect::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
size_t mapping_idx = std::distance(mappings.cbegin(), it);
|
size_t mapping_idx = std::distance(mappings.cbegin(), it);
|
||||||
auto value = this->at(mapping_idx);
|
this->publish_state(mapping_idx);
|
||||||
this->publish_state(value.value());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user