mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 14:38:10 +01:00
Add text component (#5336)
Co-authored-by: Maurits <maurits@vloop.nl> Co-authored-by: mauritskorse <mauritskorse@gmail.com> Co-authored-by: Daniel Dunn <dannydunn@eternityforest.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
cf7df9e360
commit
e80bd8ed3d
50 changed files with 1545 additions and 7 deletions
|
@ -303,6 +303,7 @@ esphome/components/tcl112/* @glmnet
|
|||
esphome/components/tee501/* @Stock-M
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/template/alarm_control_panel/* @grahambrown11
|
||||
esphome/components/text/* @mauritskorse
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tlc5947/* @rnauber
|
||||
|
|
|
@ -39,6 +39,7 @@ service APIConnection {
|
|||
rpc camera_image (CameraImageRequest) returns (void) {}
|
||||
rpc climate_command (ClimateCommandRequest) returns (void) {}
|
||||
rpc number_command (NumberCommandRequest) returns (void) {}
|
||||
rpc text_command (TextCommandRequest) returns (void) {}
|
||||
rpc select_command (SelectCommandRequest) returns (void) {}
|
||||
rpc button_command (ButtonCommandRequest) returns (void) {}
|
||||
rpc lock_command (LockCommandRequest) returns (void) {}
|
||||
|
@ -1536,3 +1537,48 @@ message AlarmControlPanelCommandRequest {
|
|||
AlarmControlPanelStateCommand command = 2;
|
||||
string code = 3;
|
||||
}
|
||||
|
||||
// ===================== TEXT =====================
|
||||
enum TextMode {
|
||||
TEXT_MODE_TEXT = 0;
|
||||
TEXT_MODE_PASSWORD = 1;
|
||||
}
|
||||
message ListEntitiesTextResponse {
|
||||
option (id) = 97;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
|
||||
uint32 min_length = 8;
|
||||
uint32 max_length = 9;
|
||||
string pattern = 10;
|
||||
TextMode mode = 11;
|
||||
}
|
||||
message TextStateResponse {
|
||||
option (id) = 98;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
// If the Text does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 3;
|
||||
}
|
||||
message TextCommandRequest {
|
||||
option (id) = 99;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_TEXT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "api_connection.h"
|
||||
#include <cerrno>
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
@ -655,6 +656,44 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
bool APIConnection::send_text_state(text::Text *text, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
TextStateResponse resp{};
|
||||
resp.key = text->get_object_id_hash();
|
||||
resp.state = std::move(state);
|
||||
resp.missing_state = !text->has_state();
|
||||
return this->send_text_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_text_info(text::Text *text) {
|
||||
ListEntitiesTextResponse msg;
|
||||
msg.key = text->get_object_id_hash();
|
||||
msg.object_id = text->get_object_id();
|
||||
msg.name = text->get_name();
|
||||
msg.icon = text->get_icon();
|
||||
msg.disabled_by_default = text->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(text->get_entity_category());
|
||||
msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
|
||||
|
||||
msg.min_length = text->traits.get_min_length();
|
||||
msg.max_length = text->traits.get_max_length();
|
||||
msg.pattern = text->traits.get_pattern();
|
||||
|
||||
return this->send_list_entities_text_response(msg);
|
||||
}
|
||||
void APIConnection::text_command(const TextCommandRequest &msg) {
|
||||
text::Text *text = App.get_text_by_key(msg.key);
|
||||
if (text == nullptr)
|
||||
return;
|
||||
|
||||
auto call = text->make_call();
|
||||
call.set_value(msg.state);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool APIConnection::send_select_state(select::Select *select, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
|
|
|
@ -72,6 +72,11 @@ class APIConnection : public APIServerConnection {
|
|||
bool send_number_info(number::Number *number);
|
||||
void number_command(const NumberCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool send_text_state(text::Text *text, std::string state);
|
||||
bool send_text_info(text::Text *text);
|
||||
void text_command(const TextCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool send_select_state(select::Select *select, std::string state);
|
||||
bool send_select_info(select::Select *select);
|
||||
|
|
|
@ -512,6 +512,18 @@ const char *proto_enum_to_string<enums::AlarmControlPanelStateCommand>(enums::Al
|
|||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::TextMode>(enums::TextMode value) {
|
||||
switch (value) {
|
||||
case enums::TEXT_MODE_TEXT:
|
||||
return "TEXT_MODE_TEXT";
|
||||
case enums::TEXT_MODE_PASSWORD:
|
||||
return "TEXT_MODE_PASSWORD";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
|
@ -6795,6 +6807,227 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
|
|||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->min_length = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->max_length = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->mode = value.as_enum<enums::TextMode>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesTextResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->object_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->pattern = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesTextResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_uint32(8, this->min_length);
|
||||
buffer.encode_uint32(9, this->max_length);
|
||||
buffer.encode_string(10, this->pattern);
|
||||
buffer.encode_enum<enums::TextMode>(11, this->mode);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTextResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesTextResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unique_id: ");
|
||||
out.append("'").append(this->unique_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" min_length: ");
|
||||
sprintf(buffer, "%" PRIu32, this->min_length);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" max_length: ");
|
||||
sprintf(buffer, "%" PRIu32, this->max_length);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" pattern: ");
|
||||
out.append("'").append(this->pattern).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" mode: ");
|
||||
out.append(proto_enum_to_string<enums::TextMode>(this->mode));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool TextStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->missing_state = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool TextStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->state = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool TextStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void TextStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void TextStateResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("TextStateResponse {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" state: ");
|
||||
out.append("'").append(this->state).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" missing_state: ");
|
||||
out.append(YESNO(this->missing_state));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->state = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void TextCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void TextCommandRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("TextCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" state: ");
|
||||
out.append("'").append(this->state).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
|
|
@ -208,6 +208,10 @@ enum AlarmControlPanelStateCommand : uint32_t {
|
|||
ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5,
|
||||
ALARM_CONTROL_PANEL_TRIGGER = 6,
|
||||
};
|
||||
enum TextMode : uint32_t {
|
||||
TEXT_MODE_TEXT = 0,
|
||||
TEXT_MODE_PASSWORD = 1,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
|
||||
|
@ -1778,6 +1782,57 @@ class AlarmControlPanelCommandRequest : public ProtoMessage {
|
|||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesTextResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
uint32_t min_length{0};
|
||||
uint32_t max_length{0};
|
||||
std::string pattern{};
|
||||
enums::TextMode mode{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class TextStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class TextCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
|
|
@ -495,6 +495,24 @@ bool APIServerConnectionBase::send_alarm_control_panel_state_response(const Alar
|
|||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool APIServerConnectionBase::send_list_entities_text_response(const ListEntitiesTextResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_text_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesTextResponse>(msg, 97);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool APIServerConnectionBase::send_text_state_response(const TextStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_text_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<TextStateResponse>(msg, 98);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
#endif
|
||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case 1: {
|
||||
|
@ -913,6 +931,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||
ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_alarm_control_panel_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 99: {
|
||||
#ifdef USE_TEXT
|
||||
TextCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_text_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
@ -1124,6 +1153,19 @@ void APIServerConnection::on_number_command_request(const NumberCommandRequest &
|
|||
this->number_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->text_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
|
|
|
@ -248,6 +248,15 @@ class APIServerConnectionBase : public ProtoService {
|
|||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool send_list_entities_text_response(const ListEntitiesTextResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool send_text_state_response(const TextStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
virtual void on_text_command_request(const TextCommandRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
|
@ -288,6 +297,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#ifdef USE_NUMBER
|
||||
virtual void number_command(const NumberCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
virtual void text_command(const TextCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
virtual void select_command(const SelectCommandRequest &msg) = 0;
|
||||
#endif
|
||||
|
@ -371,6 +383,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#ifdef USE_NUMBER
|
||||
void on_number_command_request(const NumberCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
void on_text_command_request(const TextCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_command_request(const SelectCommandRequest &msg) override;
|
||||
#endif
|
||||
|
|
|
@ -254,6 +254,15 @@ void APIServer::on_number_update(number::Number *obj, float state) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void APIServer::on_text_update(text::Text *obj, const std::string &state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_text_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
|
||||
if (obj->is_internal())
|
||||
|
|
|
@ -65,6 +65,9 @@ class APIServer : public Component, public Controller {
|
|||
#ifdef USE_NUMBER
|
||||
void on_number_update(number::Number *obj, float state) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
void on_text_update(text::Text *obj, const std::string &state) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
|
||||
#endif
|
||||
|
|
|
@ -60,6 +60,10 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->
|
|||
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); }
|
||||
#endif
|
||||
|
|
|
@ -46,6 +46,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool on_text(text::Text *text) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
|
|
|
@ -42,6 +42,9 @@ bool InitialStateIterator::on_number(number::Number *number) {
|
|||
return this->client_->send_number_state(number, number->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool InitialStateIterator::on_select(select::Select *select) {
|
||||
return this->client_->send_select_state(select, select->state);
|
||||
|
|
|
@ -43,6 +43,9 @@ class InitialStateIterator : public ComponentIterator {
|
|||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool on_text(text::Text *text) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
|
|
36
esphome/components/copy/text/__init__.py
Normal file
36
esphome/components/copy/text/__init__.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text
|
||||
from esphome.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ICON,
|
||||
CONF_MODE,
|
||||
CONF_SOURCE_ID,
|
||||
)
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
|
||||
from .. import copy_ns
|
||||
|
||||
CopyText = copy_ns.class_("CopyText", text.Text, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CopyText),
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(text.Text),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
|
||||
inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID),
|
||||
inherit_property_from(CONF_MODE, CONF_SOURCE_ID),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await text.new_text(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||
cg.add(var.set_source(source))
|
25
esphome/components/copy/text/copy_text.cpp
Normal file
25
esphome/components/copy/text/copy_text.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "copy_text.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace copy {
|
||||
|
||||
static const char *const TAG = "copy.text";
|
||||
|
||||
void CopyText::setup() {
|
||||
source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); });
|
||||
|
||||
if (source_->has_state())
|
||||
this->publish_state(source_->state);
|
||||
}
|
||||
|
||||
void CopyText::dump_config() { LOG_TEXT("", "Copy Text", this); }
|
||||
|
||||
void CopyText::control(const std::string &value) {
|
||||
auto call2 = source_->make_call();
|
||||
call2.set_value(value);
|
||||
call2.perform();
|
||||
}
|
||||
|
||||
} // namespace copy
|
||||
} // namespace esphome
|
23
esphome/components/copy/text/copy_text.h
Normal file
23
esphome/components/copy/text/copy_text.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/text/text.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace copy {
|
||||
|
||||
class CopyText : public text::Text, public Component {
|
||||
public:
|
||||
void set_source(text::Text *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
|
||||
text::Text *source_;
|
||||
};
|
||||
|
||||
} // namespace copy
|
||||
} // namespace esphome
|
|
@ -111,6 +111,7 @@ MQTTSensorComponent = mqtt_ns.class_("MQTTSensorComponent", MQTTComponent)
|
|||
MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent)
|
||||
MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
|
||||
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
|
||||
MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent)
|
||||
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
|
||||
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
|
||||
MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent)
|
||||
|
|
63
esphome/components/mqtt/mqtt_text.cpp
Normal file
63
esphome/components/mqtt/mqtt_text.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include "mqtt_text.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_TEXT
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
static const char *const TAG = "mqtt.text";
|
||||
|
||||
using namespace esphome::text;
|
||||
|
||||
MQTTTextComponent::MQTTTextComponent(Text *text) : text_(text) {}
|
||||
|
||||
void MQTTTextComponent::setup() {
|
||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) {
|
||||
auto call = this->text_->make_call();
|
||||
call.set_value(state);
|
||||
call.perform();
|
||||
});
|
||||
|
||||
this->text_->add_on_state_callback([this](const std::string &state) { this->publish_state(state); });
|
||||
}
|
||||
|
||||
void MQTTTextComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MQTT text '%s':", this->text_->get_name().c_str());
|
||||
LOG_MQTT_COMPONENT(true, false)
|
||||
}
|
||||
|
||||
std::string MQTTTextComponent::component_type() const { return "text"; }
|
||||
const EntityBase *MQTTTextComponent::get_entity() const { return this->text_; }
|
||||
|
||||
void MQTTTextComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
switch (this->text_->traits.get_mode()) {
|
||||
case TEXT_MODE_TEXT:
|
||||
root[MQTT_MODE] = "text";
|
||||
break;
|
||||
case TEXT_MODE_PASSWORD:
|
||||
root[MQTT_MODE] = "password";
|
||||
break;
|
||||
}
|
||||
|
||||
config.command_topic = true;
|
||||
}
|
||||
bool MQTTTextComponent::send_initial_state() {
|
||||
if (this->text_->has_state()) {
|
||||
return this->publish_state(this->text_->state);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool MQTTTextComponent::publish_state(const std::string &value) {
|
||||
return this->publish(this->get_state_topic_(), value);
|
||||
}
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_MQTT
|
46
esphome/components/mqtt/mqtt_text.h
Normal file
46
esphome/components/mqtt/mqtt_text.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_TEXT
|
||||
|
||||
#include "esphome/components/text/text.h"
|
||||
#include "mqtt_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTTextComponent : public mqtt::MQTTComponent {
|
||||
public:
|
||||
/** Construct this MQTTTextComponent instance with the provided friendly_name and text
|
||||
*
|
||||
* @param text The text input.
|
||||
*/
|
||||
explicit MQTTTextComponent(text::Text *text);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Override setup.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
|
||||
|
||||
bool send_initial_state() override;
|
||||
|
||||
bool publish_state(const std::string &value);
|
||||
|
||||
protected:
|
||||
/// Override for MQTTComponent, returns "text".
|
||||
std::string component_type() const override;
|
||||
const EntityBase *get_entity() const override;
|
||||
|
||||
text::Text *text_;
|
||||
};
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_MQTT
|
|
@ -19,6 +19,7 @@ from esphome.const import (
|
|||
ENTITY_CATEGORY_CONFIG,
|
||||
UNIT_MINUTE,
|
||||
UNIT_SECOND,
|
||||
CONF_SET_ACTION,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["number", "switch"]
|
||||
|
@ -46,7 +47,6 @@ CONF_QUEUE_ENABLE_SWITCH = "queue_enable_switch"
|
|||
CONF_REPEAT_NUMBER = "repeat_number"
|
||||
CONF_REVERSE_SWITCH = "reverse_switch"
|
||||
CONF_RUN_DURATION_NUMBER = "run_duration_number"
|
||||
CONF_SET_ACTION = "set_action"
|
||||
CONF_STANDBY_SWITCH = "standby_switch"
|
||||
CONF_VALVE_NUMBER = "valve_number"
|
||||
CONF_VALVE_OPEN_DELAY = "valve_open_delay"
|
||||
|
|
|
@ -11,6 +11,7 @@ from esphome.const import (
|
|||
CONF_OPTIMISTIC,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_STEP,
|
||||
CONF_SET_ACTION,
|
||||
)
|
||||
from .. import template_ns
|
||||
|
||||
|
@ -18,8 +19,6 @@ TemplateNumber = template_ns.class_(
|
|||
"TemplateNumber", number.Number, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONF_SET_ACTION = "set_action"
|
||||
|
||||
|
||||
def validate_min_max(config):
|
||||
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
|
||||
|
|
|
@ -9,6 +9,7 @@ from esphome.const import (
|
|||
CONF_OPTIONS,
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_SET_ACTION,
|
||||
)
|
||||
from .. import template_ns
|
||||
|
||||
|
@ -16,8 +17,6 @@ TemplateSelect = template_ns.class_(
|
|||
"TemplateSelect", select.Select, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONF_SET_ACTION = "set_action"
|
||||
|
||||
|
||||
def validate(config):
|
||||
if CONF_LAMBDA in config:
|
||||
|
|
92
esphome/components/template/text/__init__.py
Normal file
92
esphome/components/template/text/__init__.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text
|
||||
from esphome.const import (
|
||||
CONF_INITIAL_VALUE,
|
||||
CONF_LAMBDA,
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_MAX_LENGTH,
|
||||
CONF_MIN_LENGTH,
|
||||
CONF_PATTERN,
|
||||
CONF_SET_ACTION,
|
||||
)
|
||||
from .. import template_ns
|
||||
|
||||
TemplateText = template_ns.class_("TemplateText", text.Text, cg.PollingComponent)
|
||||
|
||||
TextSaverBase = template_ns.class_("TemplateTextSaverBase")
|
||||
TextSaverTemplate = template_ns.class_("TextSaver", TextSaverBase)
|
||||
|
||||
CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length"
|
||||
|
||||
|
||||
def validate(config):
|
||||
if CONF_LAMBDA in config:
|
||||
if config[CONF_OPTIMISTIC]:
|
||||
raise cv.Invalid("optimistic cannot be used with lambda")
|
||||
if CONF_INITIAL_VALUE in config:
|
||||
raise cv.Invalid("initial_value cannot be used with lambda")
|
||||
if CONF_RESTORE_VALUE in config:
|
||||
raise cv.Invalid("restore_value cannot be used with lambda")
|
||||
elif CONF_INITIAL_VALUE not in config:
|
||||
config[CONF_INITIAL_VALUE] = ""
|
||||
|
||||
if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config:
|
||||
raise cv.Invalid(
|
||||
"Either optimistic mode must be enabled, or set_action must be set, to handle the text input being set."
|
||||
)
|
||||
|
||||
with cv.prepend_path(CONF_MIN_LENGTH):
|
||||
if config[CONF_MIN_LENGTH] >= config[CONF_MAX_LENGTH]:
|
||||
raise cv.Invalid("min_length must be less than max_length")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
text.TEXT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TemplateText),
|
||||
cv.Optional(CONF_MIN_LENGTH, default=0): cv.int_range(min=0, max=255),
|
||||
cv.Optional(CONF_MAX_LENGTH, default=255): cv.int_range(min=0, max=255),
|
||||
cv.Optional(CONF_PATTERN): cv.string,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s")),
|
||||
validate,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await text.new_text(
|
||||
config,
|
||||
min_length=config[CONF_MIN_LENGTH],
|
||||
max_length=config[CONF_MAX_LENGTH],
|
||||
pattern=config.get(CONF_PATTERN),
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
|
||||
)
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
else:
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
if initial_value_config := config.get(CONF_INITIAL_VALUE):
|
||||
cg.add(var.set_initial_value(initial_value_config))
|
||||
if config[CONF_RESTORE_VALUE]:
|
||||
args = cg.TemplateArguments(config[CONF_MAX_LENGTH])
|
||||
saver = TextSaverTemplate.template(args).new()
|
||||
cg.add(var.set_value_saver(saver))
|
||||
|
||||
if CONF_SET_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_set_trigger(), [(cg.std_string, "x")], config[CONF_SET_ACTION]
|
||||
)
|
64
esphome/components/template/text/template_text.cpp
Normal file
64
esphome/components/template/text/template_text.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#include "template_text.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
static const char *const TAG = "template.text";
|
||||
|
||||
void TemplateText::setup() {
|
||||
if (!(this->f_ == nullptr)) {
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
}
|
||||
|
||||
std::string value;
|
||||
ESP_LOGD(TAG, "Setting up Template Text Input");
|
||||
value = this->initial_value_;
|
||||
if (!this->pref_) {
|
||||
ESP_LOGD(TAG, "State from initial: %s", value.c_str());
|
||||
} else {
|
||||
uint32_t key = this->get_object_id_hash();
|
||||
key += this->traits.get_min_length() << 2;
|
||||
key += this->traits.get_max_length() << 4;
|
||||
key += fnv1_hash(this->traits.get_pattern()) << 6;
|
||||
this->pref_->setup(key, value);
|
||||
}
|
||||
if (!value.empty())
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
void TemplateText::update() {
|
||||
if (this->f_ == nullptr)
|
||||
return;
|
||||
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
this->publish_state(*val);
|
||||
}
|
||||
|
||||
void TemplateText::control(const std::string &value) {
|
||||
this->set_trigger_->trigger(value);
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(value);
|
||||
|
||||
if (this->pref_) {
|
||||
if (!this->pref_->save(value)) {
|
||||
ESP_LOGW(TAG, "Text value too long to save");
|
||||
}
|
||||
}
|
||||
}
|
||||
void TemplateText::dump_config() {
|
||||
LOG_TEXT("", "Template Text Input", this);
|
||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
} // namespace template_
|
||||
} // namespace esphome
|
87
esphome/components/template/text/template_text.h
Normal file
87
esphome/components/template/text/template_text.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/text/text.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
// We keep this separate so we don't have to template and duplicate
|
||||
// the text input for each different size flash allocation.
|
||||
class TemplateTextSaverBase {
|
||||
public:
|
||||
virtual bool save(const std::string &value) { return true; }
|
||||
|
||||
virtual void setup(uint32_t id, std::string &value) {}
|
||||
|
||||
protected:
|
||||
ESPPreferenceObject pref_;
|
||||
std::string prev_;
|
||||
};
|
||||
|
||||
template<uint8_t SZ> class TextSaver : public TemplateTextSaverBase {
|
||||
public:
|
||||
bool save(const std::string &value) override {
|
||||
int diff = value.compare(this->prev_);
|
||||
if (diff != 0) {
|
||||
// If string is bigger than the allocation, do not save it.
|
||||
// We don't need to waste ram setting prev_value either.
|
||||
int size = value.size();
|
||||
if (size <= SZ) {
|
||||
// Make it into a length prefixed thing
|
||||
unsigned char temp[SZ + 1];
|
||||
memcpy(temp + 1, value.c_str(), size);
|
||||
// SZ should be pre checked at the schema level, it can't go past the char range.
|
||||
temp[0] = ((unsigned char) size);
|
||||
this->pref_.save(&temp);
|
||||
this->prev_.assign(value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make the preference object. Fill the provided location with the saved data
|
||||
// If it is available, else leave it alone
|
||||
void setup(uint32_t id, std::string &value) override {
|
||||
this->pref_ = global_preferences->make_preference<uint8_t[SZ + 1]>(id);
|
||||
|
||||
char temp[SZ + 1];
|
||||
bool hasdata = this->pref_.load(&temp);
|
||||
|
||||
if (hasdata) {
|
||||
value.assign(temp + 1, (size_t) temp[0]);
|
||||
}
|
||||
|
||||
this->prev_.assign(value);
|
||||
}
|
||||
};
|
||||
|
||||
class TemplateText : public text::Text, public PollingComponent {
|
||||
public:
|
||||
void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
|
||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void set_initial_value(const std::string &initial_value) { this->initial_value_ = initial_value; }
|
||||
void set_value_saver(TemplateTextSaverBase *restore_value_saver) { this->pref_ = restore_value_saver; }
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
bool optimistic_ = false;
|
||||
std::string initial_value_;
|
||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||
optional<std::function<optional<std::string>()>> f_{nullptr};
|
||||
|
||||
TemplateTextSaverBase *pref_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
} // namespace esphome
|
138
esphome/components/text/__init__.py
Normal file
138
esphome/components/text/__init__.py
Normal file
|
@ -0,0 +1,138 @@
|
|||
from typing import Optional
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MODE,
|
||||
CONF_ON_VALUE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_VALUE,
|
||||
)
|
||||
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@mauritskorse"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
text_ns = cg.esphome_ns.namespace("text")
|
||||
Text = text_ns.class_("Text", cg.EntityBase)
|
||||
TextPtr = Text.operator("ptr")
|
||||
|
||||
# Triggers
|
||||
TextStateTrigger = text_ns.class_(
|
||||
"TextStateTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
# Actions
|
||||
TextSetAction = text_ns.class_("TextSetAction", automation.Action)
|
||||
|
||||
# Conditions
|
||||
TextMode = text_ns.enum("TextMode")
|
||||
|
||||
TEXT_MODES = {
|
||||
"TEXT": TextMode.TEXT_MODE_TEXT,
|
||||
"PASSWORD": TextMode.TEXT_MODE_PASSWORD, # to be implemented for keys, passwords, etc.
|
||||
}
|
||||
|
||||
TEXT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextComponent),
|
||||
cv.GenerateID(): cv.declare_id(Text),
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextStateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Required(CONF_MODE): cv.enum(TEXT_MODES, upper=True),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_text_core_(
|
||||
var,
|
||||
config,
|
||||
*,
|
||||
min_length: Optional[int],
|
||||
max_length: Optional[int],
|
||||
pattern: Optional[str],
|
||||
):
|
||||
await setup_entity(var, config)
|
||||
|
||||
cg.add(var.traits.set_min_length(min_length))
|
||||
cg.add(var.traits.set_max_length(max_length))
|
||||
if pattern is not None:
|
||||
cg.add(var.traits.set_pattern(pattern))
|
||||
|
||||
cg.add(var.traits.set_mode(config[CONF_MODE]))
|
||||
|
||||
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)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
async def register_text(
|
||||
var,
|
||||
config,
|
||||
*,
|
||||
min_length: Optional[int] = 0,
|
||||
max_length: Optional[int] = 255,
|
||||
pattern: Optional[str] = None,
|
||||
):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_text(var))
|
||||
await setup_text_core_(
|
||||
var, config, min_length=min_length, max_length=max_length, pattern=pattern
|
||||
)
|
||||
|
||||
|
||||
async def new_text(
|
||||
config,
|
||||
*,
|
||||
min_length: Optional[int] = 0,
|
||||
max_length: Optional[int] = 255,
|
||||
pattern: Optional[str] = None,
|
||||
):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await register_text(
|
||||
var, config, min_length=min_length, max_length=max_length, pattern=pattern
|
||||
)
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(40.0)
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_TEXT")
|
||||
cg.add_global(text_ns.using)
|
||||
|
||||
|
||||
OPERATION_BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Text),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"text.set",
|
||||
TextSetAction,
|
||||
OPERATION_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_VALUE): cv.templatable(cv.string_strict),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def text_set_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_VALUE], args, cg.std_string)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
33
esphome/components/text/automation.h
Normal file
33
esphome/components/text/automation.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "text.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace text {
|
||||
|
||||
class TextStateTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit TextStateTrigger(Text *parent) {
|
||||
parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class TextSetAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit TextSetAction(Text *text) : text_(text) {}
|
||||
TEMPLATABLE_VALUE(std::string, value)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->text_->make_call();
|
||||
call.set_value(this->value_.value(x...));
|
||||
call.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
Text *text_;
|
||||
};
|
||||
|
||||
} // namespace text
|
||||
} // namespace esphome
|
26
esphome/components/text/text.cpp
Normal file
26
esphome/components/text/text.cpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#include "text.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace text {
|
||||
|
||||
static const char *const TAG = "text";
|
||||
|
||||
void Text::publish_state(const std::string &state) {
|
||||
this->has_state_ = true;
|
||||
this->state = state;
|
||||
if (this->traits.get_mode() == TEXT_MODE_PASSWORD) {
|
||||
ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), state.c_str());
|
||||
|
||||
} else {
|
||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str());
|
||||
}
|
||||
this->state_callback_.call(state);
|
||||
}
|
||||
|
||||
void Text::add_on_state_callback(std::function<void(std::string)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
} // namespace text
|
||||
} // namespace esphome
|
55
esphome/components/text/text.h
Normal file
55
esphome/components/text/text.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "text_call.h"
|
||||
#include "text_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace text {
|
||||
|
||||
#define LOG_TEXT(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
/** Base-class for all text inputs.
|
||||
*
|
||||
* A text input can use publish_state to send out a new value.
|
||||
*/
|
||||
class Text : public EntityBase {
|
||||
public:
|
||||
std::string state;
|
||||
TextTraits traits;
|
||||
|
||||
void publish_state(const std::string &state);
|
||||
|
||||
/// Return whether this text input has gotten a full state yet.
|
||||
bool has_state() const { return has_state_; }
|
||||
|
||||
/// Instantiate a TextCall object to modify this text component's state.
|
||||
TextCall make_call() { return TextCall(this); }
|
||||
|
||||
void add_on_state_callback(std::function<void(std::string)> &&callback);
|
||||
|
||||
protected:
|
||||
friend class TextCall;
|
||||
|
||||
/** Set the value of the text input, this is a virtual method that each text input integration must implement.
|
||||
*
|
||||
* This method is called by the TextCall.
|
||||
*
|
||||
* @param value The value as validated by the TextCall.
|
||||
*/
|
||||
virtual void control(const std::string &value) = 0;
|
||||
|
||||
CallbackManager<void(std::string)> state_callback_;
|
||||
bool has_state_{false};
|
||||
};
|
||||
|
||||
} // namespace text
|
||||
} // namespace esphome
|
56
esphome/components/text/text_call.cpp
Normal file
56
esphome/components/text/text_call.cpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include "text_call.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "text.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace text {
|
||||
|
||||
static const char *const TAG = "text";
|
||||
|
||||
TextCall &TextCall::set_value(const std::string &value) {
|
||||
this->value_ = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void TextCall::validate_() {
|
||||
const auto *name = this->parent_->get_name().c_str();
|
||||
|
||||
if (!this->value_.has_value()) {
|
||||
ESP_LOGW(TAG, "'%s' - No value set for TextCall", name);
|
||||
return;
|
||||
}
|
||||
|
||||
int sz = this->value_.value().size();
|
||||
|
||||
if (sz > this->parent_->traits.get_max_length()) {
|
||||
ESP_LOGW(TAG, "'%s' - Value set for TextCall is too long", name);
|
||||
this->value_.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sz < this->parent_->traits.get_min_length()) {
|
||||
ESP_LOGW(TAG, "'%s' - Value set for TextCall is too short", name);
|
||||
this->value_.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TextCall::perform() {
|
||||
this->validate_();
|
||||
if (!this->value_.has_value()) {
|
||||
ESP_LOGW(TAG, "'%s' - No value set for TextCall", this->parent_->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
std::string target_value = this->value_.value();
|
||||
|
||||
if (this->parent_->traits.get_mode() == TEXT_MODE_PASSWORD) {
|
||||
ESP_LOGD(TAG, "'%s' - Setting password value: " LOG_SECRET("'%s'"), this->parent_->get_name().c_str(),
|
||||
target_value.c_str());
|
||||
} else {
|
||||
ESP_LOGD(TAG, "'%s' - Setting text value: %s", this->parent_->get_name().c_str(), target_value.c_str());
|
||||
}
|
||||
this->parent_->control(target_value);
|
||||
}
|
||||
|
||||
} // namespace text
|
||||
} // namespace esphome
|
25
esphome/components/text/text_call.h
Normal file
25
esphome/components/text/text_call.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "text_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace text {
|
||||
|
||||
class Text;
|
||||
|
||||
class TextCall {
|
||||
public:
|
||||
explicit TextCall(Text *parent) : parent_(parent) {}
|
||||
void perform();
|
||||
|
||||
TextCall &set_value(const std::string &value);
|
||||
|
||||
protected:
|
||||
Text *const parent_;
|
||||
optional<std::string> value_;
|
||||
void validate_();
|
||||
};
|
||||
|
||||
} // namespace text
|
||||
} // namespace esphome
|
39
esphome/components/text/text_traits.h
Normal file
39
esphome/components/text/text_traits.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace text {
|
||||
|
||||
enum TextMode : uint8_t {
|
||||
TEXT_MODE_TEXT = 0,
|
||||
TEXT_MODE_PASSWORD = 1,
|
||||
};
|
||||
|
||||
class TextTraits {
|
||||
public:
|
||||
// Set/get the number value boundaries.
|
||||
void set_min_length(int min_length) { this->min_length_ = min_length; }
|
||||
int get_min_length() const { return this->min_length_; }
|
||||
void set_max_length(int max_length) { this->max_length_ = max_length; }
|
||||
int get_max_length() const { return this->max_length_; }
|
||||
|
||||
// Set/get the pattern.
|
||||
void set_pattern(std::string pattern) { this->pattern_ = std::move(pattern); }
|
||||
std::string get_pattern() const { return this->pattern_; }
|
||||
|
||||
// Set/get the frontend mode.
|
||||
void set_mode(TextMode mode) { this->mode_ = mode; }
|
||||
TextMode get_mode() const { return this->mode_; }
|
||||
|
||||
protected:
|
||||
int min_length_;
|
||||
int max_length_;
|
||||
std::string pattern_;
|
||||
TextMode mode_{TEXT_MODE_TEXT};
|
||||
};
|
||||
|
||||
} // namespace text
|
||||
} // namespace esphome
|
|
@ -82,6 +82,13 @@ bool ListEntitiesIterator::on_number(number::Number *number) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
bool ListEntitiesIterator::on_text(text::Text *text) {
|
||||
this->web_server_->events_.send(this->web_server_->text_json(text, text->state, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool ListEntitiesIterator::on_select(select::Select *select) {
|
||||
this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state");
|
||||
|
|
|
@ -41,6 +41,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool on_text(text::Text *text) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
|
|
|
@ -264,6 +264,32 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
for (auto *obj : App.get_texts()) {
|
||||
if (this->include_internal_ || !obj->is_internal()) {
|
||||
write_row(stream, obj, "text", "", [](AsyncResponseStream &stream, EntityBase *obj) {
|
||||
text::Text *text = (text::Text *) obj;
|
||||
auto mode = (int) text->traits.get_mode();
|
||||
stream.print(R"(<input type=")");
|
||||
if (mode == 2) {
|
||||
stream.print(R"(password)");
|
||||
} else { // default
|
||||
stream.print(R"(text)");
|
||||
}
|
||||
stream.print(R"(" minlength=")");
|
||||
stream.print(text->traits.get_min_length());
|
||||
stream.print(R"(" maxlength=")");
|
||||
stream.print(text->traits.get_max_length());
|
||||
stream.print(R"(" pattern=")");
|
||||
stream.print(text->traits.get_pattern().c_str());
|
||||
stream.print(R"(" value=")");
|
||||
stream.print(text->state.c_str());
|
||||
stream.print(R"("/>)");
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
for (auto *obj : App.get_selects()) {
|
||||
if (this->include_internal_ || !obj->is_internal()) {
|
||||
|
@ -795,6 +821,57 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void WebServer::on_text_update(text::Text *obj, const std::string &state) {
|
||||
this->events_.send(this->text_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (auto *obj : App.get_texts()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->text_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
if (match.method != "set") {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
|
||||
auto call = obj->make_call();
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
call.set_value(value.c_str());
|
||||
}
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
|
||||
std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_id(root, obj, "text-" + obj->get_object_id(), start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
root["mode"] = (int) obj->traits.get_mode();
|
||||
}
|
||||
root["min_length"] = obj->traits.get_min_length();
|
||||
root["max_length"] = obj->traits.get_max_length();
|
||||
root["pattern"] = obj->traits.get_pattern();
|
||||
if (obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD) {
|
||||
root["state"] = "********";
|
||||
} else {
|
||||
root["state"] = value;
|
||||
}
|
||||
root["value"] = value;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
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");
|
||||
|
@ -1116,6 +1193,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
|||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text")
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "select")
|
||||
return true;
|
||||
|
@ -1222,6 +1304,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
if (match.domain == "text") {
|
||||
this->handle_text_request(request, match);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
if (match.domain == "select") {
|
||||
this->handle_select_request(request, match);
|
||||
|
|
|
@ -216,6 +216,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
|||
std::string number_json(number::Number *obj, float value, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void on_text_update(text::Text *obj, const std::string &state) override;
|
||||
/// Handle a text input request under '/text/<id>'.
|
||||
void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the text state with its value as a JSON string.
|
||||
std::string text_json(text::Text *obj, const std::string &value, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
|
||||
/// Handle a select request under '/select/<id>'.
|
||||
|
|
|
@ -182,6 +182,7 @@ RESERVED_IDS = [
|
|||
"struct",
|
||||
"switch",
|
||||
"template",
|
||||
"text",
|
||||
"this",
|
||||
"thread_local",
|
||||
"throw",
|
||||
|
|
|
@ -542,6 +542,7 @@ CONF_PANASONIC = "panasonic"
|
|||
CONF_PARAMETERS = "parameters"
|
||||
CONF_PASSWORD = "password"
|
||||
CONF_PATH = "path"
|
||||
CONF_PATTERN = "pattern"
|
||||
CONF_PAYLOAD = "payload"
|
||||
CONF_PAYLOAD_AVAILABLE = "payload_available"
|
||||
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
|
||||
|
@ -679,6 +680,7 @@ CONF_SERVERS = "servers"
|
|||
CONF_SERVICE = "service"
|
||||
CONF_SERVICE_UUID = "service_uuid"
|
||||
CONF_SERVICES = "services"
|
||||
CONF_SET_ACTION = "set_action"
|
||||
CONF_SET_POINT_MINIMUM_DIFFERENTIAL = "set_point_minimum_differential"
|
||||
CONF_SETUP_MODE = "setup_mode"
|
||||
CONF_SETUP_PRIORITY = "setup_priority"
|
||||
|
|
|
@ -39,6 +39,9 @@
|
|||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
#include "esphome/components/text/text.h"
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
|
@ -117,6 +120,10 @@ class Application {
|
|||
void register_number(number::Number *number) { this->numbers_.push_back(number); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void register_text(text::Text *text) { this->texts_.push_back(text); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void register_select(select::Select *select) { this->selects_.push_back(select); }
|
||||
#endif
|
||||
|
@ -277,6 +284,15 @@ class Application {
|
|||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
const std::vector<text::Text *> &get_texts() { return this->texts_; }
|
||||
text::Text *get_text_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->texts_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
const std::vector<select::Select *> &get_selects() { return this->selects_; }
|
||||
select::Select *get_select_by_key(uint32_t key, bool include_internal = false) {
|
||||
|
@ -364,6 +380,9 @@ class Application {
|
|||
#ifdef USE_SELECT
|
||||
std::vector<select::Select *> selects_{};
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
std::vector<text::Text *> texts_{};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
std::vector<lock::Lock *> locks_{};
|
||||
#endif
|
||||
|
|
|
@ -202,6 +202,21 @@ void ComponentIterator::advance() {
|
|||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
case IteratorState::TEXT:
|
||||
if (this->at_ >= App.get_texts().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *text = App.get_texts()[this->at_];
|
||||
if (text->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_text(text);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
case IteratorState::SELECT:
|
||||
if (this->at_ >= App.get_selects().size()) {
|
||||
|
|
|
@ -57,6 +57,9 @@ class ComponentIterator {
|
|||
#ifdef USE_NUMBER
|
||||
virtual bool on_number(number::Number *number) = 0;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
virtual bool on_text(text::Text *text) = 0;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
virtual bool on_select(select::Select *select) = 0;
|
||||
#endif
|
||||
|
@ -111,6 +114,9 @@ class ComponentIterator {
|
|||
#ifdef USE_NUMBER
|
||||
NUMBER,
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
TEXT,
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
SELECT,
|
||||
#endif
|
||||
|
|
|
@ -59,6 +59,12 @@ void Controller::setup_controller(bool include_internal) {
|
|||
obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); });
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
for (auto *obj : App.get_texts()) {
|
||||
if (include_internal || !obj->is_internal())
|
||||
obj->add_on_state_callback([this, obj](const std::string &state) { this->on_text_update(obj, state); });
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
for (auto *obj : App.get_selects()) {
|
||||
if (include_internal || !obj->is_internal()) {
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
#include "esphome/components/text/text.h"
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
|
@ -76,6 +79,9 @@ class Controller {
|
|||
#ifdef USE_NUMBER
|
||||
virtual void on_number_update(number::Number *obj, float state){};
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
virtual void on_text_update(text::Text *obj, const std::string &state){};
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
virtual void on_select_update(select::Select *obj, const std::string &state, size_t index){};
|
||||
#endif
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#define USE_SENSOR
|
||||
#define USE_STATUS_LED
|
||||
#define USE_SWITCH
|
||||
#define USE_TEXT
|
||||
#define USE_TEXT_SENSOR
|
||||
#define USE_TIME
|
||||
#define USE_TOUCHSCREEN
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
It's pretty crappy spaghetti code, but it works.
|
||||
|
||||
you need to install protobuf-compiler:
|
||||
running protc --version should return
|
||||
running protoc --version should return
|
||||
libprotoc 3.6.1
|
||||
|
||||
then run this script with python3 and the files
|
||||
|
|
|
@ -617,6 +617,7 @@ def lint_trailing_whitespace(fname, match):
|
|||
"esphome/components/lock/lock.h",
|
||||
"esphome/components/mqtt/mqtt_component.h",
|
||||
"esphome/components/number/number.h",
|
||||
"esphome/components/text/text.h",
|
||||
"esphome/components/output/binary_output.h",
|
||||
"esphome/components/output/float_output.h",
|
||||
"esphome/components/nextion/nextion_base.h",
|
||||
|
|
56
tests/component_tests/text/test_text.py
Normal file
56
tests/component_tests/text/test_text.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
"""Tests for the binary sensor component."""
|
||||
|
||||
|
||||
def test_text_is_setup(generate_main):
|
||||
"""
|
||||
When the binary sensor is set in the yaml file, it should be registered in main
|
||||
"""
|
||||
# Given
|
||||
|
||||
# When
|
||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||
|
||||
# Then
|
||||
assert "new template_::TemplateText();" in main_cpp
|
||||
assert "App.register_text" in main_cpp
|
||||
|
||||
|
||||
def test_text_sets_mandatory_fields(generate_main):
|
||||
"""
|
||||
When the mandatory fields are set in the yaml, they should be set in main
|
||||
"""
|
||||
# Given
|
||||
|
||||
# When
|
||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||
|
||||
# Then
|
||||
assert 'it_1->set_name("test 1 text");' in main_cpp
|
||||
|
||||
|
||||
def test_text_config_value_internal_set(generate_main):
|
||||
"""
|
||||
Test that the "internal" config value is correctly set
|
||||
"""
|
||||
# Given
|
||||
|
||||
# When
|
||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||
|
||||
# Then
|
||||
assert "it_2->set_internal(false);" in main_cpp
|
||||
assert "it_3->set_internal(true);" in main_cpp
|
||||
|
||||
|
||||
def test_text_config_value_mode_set(generate_main):
|
||||
"""
|
||||
Test that the "internal" config value is correctly set
|
||||
"""
|
||||
# Given
|
||||
|
||||
# When
|
||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||
|
||||
# Then
|
||||
assert "it_1->traits.set_mode(text::TEXT_MODE_TEXT);" in main_cpp
|
||||
assert "it_3->traits.set_mode(text::TEXT_MODE_PASSWORD);" in main_cpp
|
33
tests/component_tests/text/test_text.yaml
Normal file
33
tests/component_tests/text/test_text.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
text:
|
||||
- platform: template
|
||||
name: "test 1 text"
|
||||
id: "it_1"
|
||||
optimistic: true
|
||||
mode: text
|
||||
|
||||
- platform: template
|
||||
name: "test 2 text"
|
||||
id: "it_2"
|
||||
icon: "mdi:text"
|
||||
optimistic: true
|
||||
min_length: 5
|
||||
max_length: 255
|
||||
internal: false
|
||||
initial_value: "Welcome ESPHOME"
|
||||
restore_value: true
|
||||
mode: text
|
||||
|
||||
- platform: template
|
||||
name: "test 3 key"
|
||||
id: "it_3"
|
||||
icon: "mdi:text"
|
||||
mode: "password"
|
||||
optimistic: true
|
||||
internal: true
|
||||
max_length: 255
|
|
@ -795,7 +795,23 @@ switch:
|
|||
value: !lambda |-
|
||||
return {0x13, 0x37};
|
||||
|
||||
|
||||
esp32_ble_server:
|
||||
id: ble
|
||||
manufacturer_data: [0x72, 0x4, 0x00, 0x23]
|
||||
|
||||
text:
|
||||
- platform: template
|
||||
name: My Text
|
||||
id: my_text
|
||||
min_length: 0
|
||||
max_length: 20
|
||||
mode: text
|
||||
pattern: "[a-z]+"
|
||||
optimistic: true
|
||||
restore_value: true
|
||||
initial_value: "Hello World"
|
||||
- platform: copy
|
||||
name: My Text Copy
|
||||
id: my_text_copy
|
||||
source_id: my_text
|
||||
mode: password
|
||||
|
|
Loading…
Reference in a new issue