mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Add device class support to text sensor (#6202)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
a8ab745479
commit
323849c821
10 changed files with 133 additions and 1 deletions
|
@ -600,6 +600,7 @@ message ListEntitiesTextSensorResponse {
|
|||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
|
|
|
@ -543,6 +543,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
|
|||
msg.icon = text_sensor->get_icon();
|
||||
msg.disabled_by_default = text_sensor->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
|
||||
msg.device_class = text_sensor->get_device_class();
|
||||
return this->send_list_entities_text_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2721,6 +2721,10 @@ bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengt
|
|||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -2743,6 +2747,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
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_string(8, this->device_class);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
|
@ -2776,6 +2781,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
|||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -713,6 +713,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
|
|||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "mqtt_text_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
|
||||
|
@ -13,6 +15,8 @@ using namespace esphome::text_sensor;
|
|||
|
||||
MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {}
|
||||
void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (!this->sensor_->get_device_class().empty())
|
||||
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
|
||||
config.command_topic = false;
|
||||
}
|
||||
void MQTTTextSensor::setup() {
|
||||
|
|
|
@ -3,6 +3,7 @@ import esphome.config_validation as cv
|
|||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_FILTERS,
|
||||
CONF_ICON,
|
||||
|
@ -14,12 +15,21 @@ from esphome.const import (
|
|||
CONF_STATE,
|
||||
CONF_FROM,
|
||||
CONF_TO,
|
||||
DEVICE_CLASS_DATE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.util import Registry
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_DATE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
]
|
||||
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
|
@ -112,10 +122,13 @@ async def map_filter_to_code(config, filter_id):
|
|||
)
|
||||
|
||||
|
||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor),
|
||||
cv.GenerateID(): cv.declare_id(TextSensor),
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
|
@ -140,12 +153,21 @@ def text_sensor_schema(
|
|||
*,
|
||||
icon: str = _UNDEF,
|
||||
entity_category: str = _UNDEF,
|
||||
device_class: str = _UNDEF,
|
||||
) -> cv.Schema:
|
||||
schema = TEXT_SENSOR_SCHEMA
|
||||
if class_ is not _UNDEF:
|
||||
schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)})
|
||||
if icon is not _UNDEF:
|
||||
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
|
||||
if device_class is not _UNDEF:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, default=device_class
|
||||
): validate_device_class
|
||||
}
|
||||
)
|
||||
if entity_category is not _UNDEF:
|
||||
schema = schema.extend(
|
||||
{
|
||||
|
@ -164,6 +186,9 @@ async def build_filters(config):
|
|||
async def setup_text_sensor_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
|
||||
if config.get(CONF_FILTERS): # must exist and not be empty
|
||||
filters = await build_filters(config[CONF_FILTERS])
|
||||
cg.add(var.set_filters(filters))
|
||||
|
|
|
@ -13,6 +13,9 @@ namespace text_sensor {
|
|||
#define LOG_TEXT_SENSOR(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_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
} \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
|
@ -28,7 +31,7 @@ namespace text_sensor {
|
|||
public: \
|
||||
void set_##name##_text_sensor(text_sensor::TextSensor *text_sensor) { this->name##_text_sensor_ = text_sensor; }
|
||||
|
||||
class TextSensor : public EntityBase {
|
||||
class TextSensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
/// Getter-syntax for .state.
|
||||
std::string get_state() const;
|
||||
|
|
58
tests/component_tests/text_sensor/test_text_sensor.py
Normal file
58
tests/component_tests/text_sensor/test_text_sensor.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
"""Tests for the text sensor component."""
|
||||
|
||||
|
||||
def test_text_sensor_is_setup(generate_main):
|
||||
"""
|
||||
When the text is set in the yaml file, it should be registered in main
|
||||
"""
|
||||
# Given
|
||||
|
||||
# When
|
||||
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert "new template_::TemplateTextSensor();" in main_cpp
|
||||
assert "App.register_text_sensor" in main_cpp
|
||||
|
||||
|
||||
def test_text_sensor_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_sensor/test_text_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert 'ts_1->set_name("Template Text Sensor 1");' in main_cpp
|
||||
assert 'ts_2->set_name("Template Text Sensor 2");' in main_cpp
|
||||
assert 'ts_3->set_name("Template Text Sensor 3");' in main_cpp
|
||||
|
||||
|
||||
def test_text_sensor_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_sensor/test_text_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert "ts_2->set_internal(true);" in main_cpp
|
||||
assert "ts_3->set_internal(false);" in main_cpp
|
||||
|
||||
|
||||
def test_text_sensor_device_class_set(generate_main):
|
||||
"""
|
||||
When the device_class of text_sensor is set in the yaml file, it should be registered in main
|
||||
"""
|
||||
# Given
|
||||
|
||||
# When
|
||||
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert 'ts_2->set_device_class("timestamp");' in main_cpp
|
||||
assert 'ts_3->set_device_class("date");' in main_cpp
|
26
tests/component_tests/text_sensor/test_text_sensor.yaml
Normal file
26
tests/component_tests/text_sensor/test_text_sensor.yaml
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
esphome:
|
||||
name: test
|
||||
platform: ESP8266
|
||||
board: d1_mini_lite
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
id: ts_1
|
||||
name: "Template Text Sensor 1"
|
||||
lambda: |-
|
||||
return {"Hello World"};
|
||||
- platform: template
|
||||
id: ts_2
|
||||
name: "Template Text Sensor 2"
|
||||
lambda: |-
|
||||
return {"2023-06-22T18:43:52+00:00"};
|
||||
device_class: timestamp
|
||||
internal: true
|
||||
- platform: template
|
||||
id: ts_3
|
||||
name: "Template Text Sensor 3"
|
||||
lambda: |-
|
||||
return {"2023-06-22T18:43:52+00:00"};
|
||||
device_class: date
|
||||
internal: false
|
|
@ -3923,6 +3923,10 @@ text_sensor:
|
|||
- platform: template
|
||||
name: Template Text Sensor
|
||||
id: ${textname}_text
|
||||
- platform: template
|
||||
name: Template Text Sensor Timestamp
|
||||
id: ${textname}_text_timestamp
|
||||
device_class: timestamp
|
||||
- platform: wifi_info
|
||||
scan_results:
|
||||
name: Scan Results
|
||||
|
|
Loading…
Reference in a new issue