mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 16:37:52 +01:00
Select enhancement (#3423)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
This commit is contained in:
parent
3a3d97dfa7
commit
44b68f140e
@ -64,6 +64,7 @@ from esphome.cpp_types import ( # noqa
|
||||
uint64,
|
||||
int32,
|
||||
int64,
|
||||
size_t,
|
||||
const_char_ptr,
|
||||
NAN,
|
||||
esphome_ns,
|
||||
|
@ -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_)
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -7,16 +7,16 @@
|
||||
namespace esphome {
|
||||
namespace select {
|
||||
|
||||
class SelectStateTrigger : public Trigger<std::string> {
|
||||
class SelectStateTrigger : public Trigger<std::string, size_t> {
|
||||
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<typename... Ts> class SelectSetAction : public Action<Ts...> {
|
||||
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<typename... Ts> class SelectSetAction : public Action<Ts...> {
|
||||
Select *select_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SelectSetIndexAction : public Action<Ts...> {
|
||||
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<typename... Ts> class SelectOperationAction : public Action<Ts...> {
|
||||
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
|
||||
|
@ -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<void(std::string)> &&callback) {
|
||||
void Select::add_on_state_callback(std::function<void(std::string, size_t)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
size_t Select::size() const {
|
||||
auto options = traits.get_options();
|
||||
return options.size();
|
||||
}
|
||||
|
||||
optional<size_t> 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<size_t> Select::active_index() const {
|
||||
if (this->has_state()) {
|
||||
return this->index_of(this->state);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
optional<std::string> 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
|
||||
|
@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#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<std::string> &get_option() const { return option_; }
|
||||
|
||||
protected:
|
||||
Select *const parent_;
|
||||
optional<std::string> option_;
|
||||
};
|
||||
|
||||
class SelectTraits {
|
||||
public:
|
||||
void set_options(std::vector<std::string> options) { this->options_ = std::move(options); }
|
||||
std::vector<std::string> get_options() const { return this->options_; }
|
||||
|
||||
protected:
|
||||
std::vector<std::string> 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<void(std::string)> &&callback);
|
||||
// Methods that provide an API to index-based access.
|
||||
size_t size() const;
|
||||
optional<size_t> index_of(const std::string &option) const;
|
||||
optional<size_t> active_index() const;
|
||||
optional<std::string> 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<void(std::string, size_t)> &&callback);
|
||||
|
||||
protected:
|
||||
friend class SelectCall;
|
||||
@ -77,7 +55,7 @@ class Select : public EntityBase {
|
||||
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void(std::string)> state_callback_;
|
||||
CallbackManager<void(std::string, size_t)> state_callback_;
|
||||
bool has_state_{false};
|
||||
};
|
||||
|
||||
|
122
esphome/components/select/select_call.cpp
Normal file
122
esphome/components/select/select_call.cpp
Normal file
@ -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<std::string> &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
|
48
esphome/components/select/select_call.h
Normal file
48
esphome/components/select/select_call.h
Normal file
@ -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<std::string> &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<std::string> option_;
|
||||
optional<size_t> index_;
|
||||
SelectOperation operation_{SELECT_OP_NONE};
|
||||
bool cycle_;
|
||||
};
|
||||
|
||||
} // namespace select
|
||||
} // namespace esphome
|
11
esphome/components/select/select_traits.cpp
Normal file
11
esphome/components/select/select_traits.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "select_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace select {
|
||||
|
||||
void SelectTraits::set_options(std::vector<std::string> options) { this->options_ = std::move(options); }
|
||||
|
||||
std::vector<std::string> SelectTraits::get_options() const { return this->options_; }
|
||||
|
||||
} // namespace select
|
||||
} // namespace esphome
|
19
esphome/components/select/select_traits.h
Normal file
19
esphome/components/select/select_traits.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace select {
|
||||
|
||||
class SelectTraits {
|
||||
public:
|
||||
void set_options(std::vector<std::string> options);
|
||||
std::vector<std::string> get_options() const;
|
||||
|
||||
protected:
|
||||
std::vector<std::string> options_;
|
||||
};
|
||||
|
||||
} // namespace select
|
||||
} // namespace esphome
|
@ -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) {
|
||||
|
@ -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/<id>'.
|
||||
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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){};
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user