mirror of
https://github.com/esphome/esphome.git
synced 2026-10-25 15:35:34 +00:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0abc7ad3ec | ||
|
|
0a3993639e | ||
|
|
b1342196c3 | ||
|
|
9f5ff8ef6e | ||
|
|
e95f2d8fe5 | ||
|
|
79c86833ee | ||
|
|
4ed6599b7e | ||
|
|
de1a854266 | ||
|
|
11b73fa6eb | ||
|
|
7f5898279c | ||
|
|
e995e12ad2 | ||
|
|
916fc01a6a | ||
|
|
08f015b832 | ||
|
|
de49ad67fb | ||
|
|
297208aab0 | ||
|
|
984fa28cb7 | ||
|
|
4ee6141b02 | ||
|
|
0a97ed5706 | ||
|
|
8359b10cf8 | ||
|
|
f00b11a48a | ||
|
|
9749698201 | ||
|
|
6f4bdf49fd | ||
|
|
772acacb9e | ||
|
|
c1e1555918 | ||
|
|
e647b2acc1 | ||
|
|
3af8fec562 |
No files matched your search
@@ -13,19 +13,10 @@ from esphome.const import (
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import copy_ns
|
||||
|
||||
CopySensor = copy_ns.class_("CopySensor", sensor.Sensor, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(CopySensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(sensor.Sensor),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(sensor.Sensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(sensor.Sensor),
|
||||
}
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
@@ -40,6 +31,4 @@ FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
sensor.follow_sensor(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "copy_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "copy.sensor");
|
||||
|
||||
void CopySensor::setup() {
|
||||
source_->add_on_state_callback([this](float value) { this->publish_state(value); });
|
||||
if (source_->has_state())
|
||||
this->publish_state(source_->state);
|
||||
}
|
||||
|
||||
void CopySensor::dump_config() { LOG_SENSOR("", "Copy Sensor", this); }
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopySensor final : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void set_source(sensor::Sensor *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -5,19 +5,10 @@ from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_SOURCE_ID
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import copy_ns
|
||||
|
||||
CopyTextSensor = copy_ns.class_("CopyTextSensor", text_sensor.TextSensor, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.text_sensor_schema(CopyTextSensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(text_sensor.TextSensor),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(text_sensor.TextSensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(text_sensor.TextSensor),
|
||||
}
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
@@ -28,6 +19,4 @@ FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
text_sensor.follow_text_sensor(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "copy_text_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "copy.text_sensor");
|
||||
|
||||
void CopyTextSensor::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 CopyTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Copy Sensor", this); }
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyTextSensor final : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
void set_source(text_sensor::TextSensor *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
text_sensor::TextSensor *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -2,24 +2,18 @@ import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_SOURCE_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import Number, number_ns
|
||||
from .. import Number
|
||||
|
||||
NumberSensor = number_ns.class_("NumberSensor", sensor.Sensor, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(NumberSensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Number),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(sensor.Sensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Number),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||
var = await sensor.new_sensor(config, source)
|
||||
await cg.register_component(var, config)
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await sensor.new_sensor(config)
|
||||
# Number has the same float state callback as Sensor
|
||||
sensor.follow_sensor(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "number_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::number {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "number.sensor");
|
||||
|
||||
void NumberSensor::setup() {
|
||||
this->source_->add_on_state_callback([this](float value) { this->publish_state(value); });
|
||||
if (this->source_->has_state())
|
||||
this->publish_state(this->source_->state);
|
||||
}
|
||||
|
||||
void NumberSensor::dump_config() { LOG_SENSOR("", "Number Sensor", this); }
|
||||
|
||||
} // namespace esphome::number
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../number.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome::number {
|
||||
|
||||
class NumberSensor final : public sensor::Sensor, public Component {
|
||||
public:
|
||||
explicit NumberSensor(Number *source) : source_(source) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
Number *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::number
|
||||
@@ -124,6 +124,7 @@ from esphome.core import (
|
||||
)
|
||||
from esphome.core.config import UNIT_OF_MEASUREMENT_MAX_LENGTH
|
||||
from esphome.core.entity_helpers import (
|
||||
FollowEntity,
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
lazy_load_validator,
|
||||
@@ -1109,6 +1110,10 @@ def sub_sensors(
|
||||
return SubEntities(new_sensor, config, parent)
|
||||
|
||||
|
||||
# Makes a plain sensor follow another sensor: follow_sensor(entity, source)
|
||||
follow_sensor = FollowEntity("add_on_state_callback", ((cg.float_, "x"),), "x")
|
||||
|
||||
|
||||
SENSOR_IN_RANGE_CONDITION_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Sensor),
|
||||
|
||||
@@ -2,29 +2,22 @@ import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_SOURCE_ID
|
||||
from esphome.core.entity_helpers import FollowEntity
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import Switch, switch_ns
|
||||
from .. import Switch
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
SwitchBinarySensor = switch_ns.class_(
|
||||
"SwitchBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||
_FOLLOW_SWITCH = FollowEntity("add_on_state_callback", ((cg.bool_, "x"),), "x")
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(binary_sensor.BinarySensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Switch),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
binary_sensor.binary_sensor_schema(SwitchBinarySensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Switch),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
_FOLLOW_SWITCH(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,15 +0,0 @@
|
||||
#include "switch_binary_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::switch_ {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "switch.binary_sensor");
|
||||
|
||||
void SwitchBinarySensor::setup() {
|
||||
source_->add_on_state_callback([this](bool value) { this->publish_state(value); });
|
||||
this->publish_state(source_->state);
|
||||
}
|
||||
|
||||
void SwitchBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Switch Binary Sensor", this); }
|
||||
|
||||
} // namespace esphome::switch_
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../switch.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome::switch_ {
|
||||
|
||||
class SwitchBinarySensor final : public binary_sensor::BinarySensor, public Component {
|
||||
public:
|
||||
void set_source(Switch *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
Switch *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::switch_
|
||||
@@ -4,23 +4,16 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_SOURCE_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import Text, text_ns
|
||||
from .. import Text
|
||||
|
||||
TextTextSensor = text_ns.class_("TextTextSensor", text_sensor.TextSensor, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.text_sensor_schema(TextTextSensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Text),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(text_sensor.TextSensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Text),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||
var = await text_sensor.new_text_sensor(config, source)
|
||||
await cg.register_component(var, config)
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
# Text has the same string state callback as TextSensor
|
||||
text_sensor.follow_text_sensor(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "text_text_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::text {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "text.text_sensor");
|
||||
|
||||
void TextTextSensor::setup() {
|
||||
this->source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); });
|
||||
if (this->source_->has_state())
|
||||
this->publish_state(this->source_->state);
|
||||
}
|
||||
|
||||
void TextTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Text Text Sensor", this); }
|
||||
|
||||
} // namespace esphome::text
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../text.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome::text {
|
||||
|
||||
class TextTextSensor final : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
explicit TextTextSensor(Text *source) : source_(source) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
Text *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::text
|
||||
@@ -22,6 +22,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
FollowEntity,
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
@@ -238,6 +239,12 @@ def sub_text_sensors(
|
||||
return SubEntities(new_text_sensor, config, parent)
|
||||
|
||||
|
||||
# Makes a plain text sensor follow another text sensor: follow_text_sensor(entity, source)
|
||||
follow_text_sensor = FollowEntity(
|
||||
"add_on_state_callback", ((cg.std_string_ref.operator("const"), "x"),), "x"
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
async def to_code(config):
|
||||
cg.add_global(text_sensor_ns.using)
|
||||
|
||||
@@ -45,6 +45,7 @@ from esphome.core.config import (
|
||||
UNIT_OF_MEASUREMENT_MAX_LENGTH,
|
||||
)
|
||||
from esphome.cpp_generator import (
|
||||
LambdaExpression,
|
||||
MockObj,
|
||||
MockObjClass,
|
||||
RawExpression,
|
||||
@@ -519,6 +520,23 @@ class ApplyEntity:
|
||||
_APPLY_DOMAIN = "entity_apply"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class FollowEntity:
|
||||
"""A state callback, its parameters and the value an entity republishes from it."""
|
||||
|
||||
callback: str
|
||||
params: tuple[tuple[SafeExpType, str], ...]
|
||||
value: str
|
||||
|
||||
def __call__(self, entity: MockObj, source: MockObj, *args: SafeExpType) -> None:
|
||||
"""Make ``entity`` publish every state of ``source``; ``args`` precede the callback."""
|
||||
publish = f"{parent_ref(entity).publish_state(RawExpression(self.value))};"
|
||||
callback = LambdaExpression(
|
||||
[publish], list(self.params), capture="", return_type=cg.void
|
||||
)
|
||||
add(getattr(source, self.callback)(*args, callback))
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SubEntities:
|
||||
"""Create the child entity configured under key, set parent (if bound) and pass it to setter.
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
esphome:
|
||||
name: test-copy-entity-state
|
||||
on_boot:
|
||||
# Before the copies' setup
|
||||
- priority: 800
|
||||
then:
|
||||
- sensor.template.publish:
|
||||
id: early_sensor
|
||||
state: 1.5
|
||||
- binary_sensor.template.publish:
|
||||
id: early_binary
|
||||
state: true
|
||||
- text_sensor.template.publish:
|
||||
id: early_text
|
||||
state: early
|
||||
# After the copies' setup
|
||||
- priority: -100
|
||||
then:
|
||||
- sensor.template.publish:
|
||||
id: late_sensor
|
||||
state: 2.5
|
||||
- text_sensor.template.publish:
|
||||
id: late_text
|
||||
state: late
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
logger:
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
id: early_sensor
|
||||
update_interval: never
|
||||
- platform: template
|
||||
id: late_sensor
|
||||
update_interval: never
|
||||
- platform: copy
|
||||
source_id: early_sensor
|
||||
name: Copy Early Sensor
|
||||
- platform: copy
|
||||
source_id: late_sensor
|
||||
name: Copy Late Sensor
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
id: early_binary
|
||||
- platform: copy
|
||||
source_id: early_binary
|
||||
name: Copy Early Binary
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
id: early_text
|
||||
update_interval: never
|
||||
- platform: template
|
||||
id: late_text
|
||||
update_interval: never
|
||||
- platform: copy
|
||||
source_id: early_text
|
||||
name: Copy Early Text
|
||||
- platform: copy
|
||||
source_id: late_text
|
||||
name: Copy Late Text
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: Update Sources
|
||||
on_press:
|
||||
- sensor.template.publish:
|
||||
id: early_sensor
|
||||
state: 3.5
|
||||
- sensor.template.publish:
|
||||
id: late_sensor
|
||||
state: 7.25
|
||||
- binary_sensor.template.publish:
|
||||
id: early_binary
|
||||
state: false
|
||||
- text_sensor.template.publish:
|
||||
id: early_text
|
||||
state: changed
|
||||
- text_sensor.template.publish:
|
||||
id: late_text
|
||||
state: updated
|
||||
@@ -0,0 +1,45 @@
|
||||
esphome:
|
||||
name: test-copy-initially-off
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
logger:
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: EXTERNAL_COMPONENT_PATH
|
||||
|
||||
# Dummy uart entry to satisfy DEPENDENCIES = ["uart"]
|
||||
uart:
|
||||
baud_rate: 9600
|
||||
port: /dev/null
|
||||
|
||||
uart_mock:
|
||||
id: mock_uart
|
||||
baud_rate: 9600
|
||||
|
||||
globals:
|
||||
- id: changes
|
||||
type: int
|
||||
|
||||
binary_sensor:
|
||||
# Holds off from its constructor, so it never publishes a first state
|
||||
- platform: uart
|
||||
id: source_binary
|
||||
uart_id: mock_uart
|
||||
data: [0x01]
|
||||
- platform: copy
|
||||
source_id: source_binary
|
||||
name: Copy Binary
|
||||
on_state_change:
|
||||
- lambda: id(changes)++;
|
||||
- component.update: change_count
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
id: change_count
|
||||
name: Change Count
|
||||
update_interval: never
|
||||
lambda: return id(changes);
|
||||
@@ -0,0 +1,56 @@
|
||||
esphome:
|
||||
name: test-source-entity-follow
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
logger:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
id: source_switch
|
||||
name: Source Switch
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_ON
|
||||
# Publishes nothing until commanded
|
||||
- platform: template
|
||||
id: quiet_switch
|
||||
name: Quiet Switch
|
||||
optimistic: true
|
||||
restore_mode: DISABLED
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
id: source_number
|
||||
name: Source Number
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 10
|
||||
step: 0.5
|
||||
initial_value: 4
|
||||
|
||||
text:
|
||||
- platform: template
|
||||
id: source_text
|
||||
name: Source Text
|
||||
optimistic: true
|
||||
mode: text
|
||||
initial_value: hello
|
||||
|
||||
binary_sensor:
|
||||
- platform: switch
|
||||
source_id: source_switch
|
||||
name: Switch State
|
||||
- platform: switch
|
||||
source_id: quiet_switch
|
||||
name: Quiet Switch State
|
||||
|
||||
sensor:
|
||||
- platform: number
|
||||
source_id: source_number
|
||||
name: Number Value
|
||||
|
||||
text_sensor:
|
||||
- platform: text
|
||||
source_id: source_text
|
||||
name: Text Value
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Integration test for the copy sensor, binary sensor and text sensor platforms."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from aioesphomeapi import (
|
||||
BinarySensorState,
|
||||
ButtonInfo,
|
||||
EntityState,
|
||||
SensorState,
|
||||
TextSensorState,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from .state_utils import require_entity, subscribe_after_initial_states
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
INITIAL: dict[str, tuple[type[EntityState], float | bool | str]] = {
|
||||
"copy_early_sensor": (SensorState, 1.5),
|
||||
"copy_late_sensor": (SensorState, 2.5),
|
||||
"copy_early_binary": (BinarySensorState, True),
|
||||
"copy_early_text": (TextSensorState, "early"),
|
||||
"copy_late_text": (TextSensorState, "late"),
|
||||
}
|
||||
UPDATED: dict[str, tuple[type[EntityState], float | bool | str]] = {
|
||||
"copy_early_sensor": (SensorState, 3.5),
|
||||
"copy_late_sensor": (SensorState, 7.25),
|
||||
"copy_early_binary": (BinarySensorState, False),
|
||||
"copy_early_text": (TextSensorState, "changed"),
|
||||
"copy_late_text": (TextSensorState, "updated"),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_copy_entity_state(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Copies hold the state their source published before and after their setup, then follow it."""
|
||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||
entities, _ = await client.list_entities_services()
|
||||
keys = {name: require_entity(entities, name).key for name in INITIAL}
|
||||
button = require_entity(entities, "update_sources", ButtonInfo)
|
||||
|
||||
sub = await subscribe_after_initial_states(client, entities)
|
||||
for name, (cls, state) in INITIAL.items():
|
||||
key = keys[name]
|
||||
assert sub.initial_states[key] == cls(key=key, state=state), name
|
||||
|
||||
names = list(UPDATED)
|
||||
waits = [sub.waiter.next_state(keys[name]) for name in names]
|
||||
client.button_command(button.key)
|
||||
for name, got in zip(names, await asyncio.gather(*waits), strict=True):
|
||||
cls, state = UPDATED[name]
|
||||
assert got == cls(key=keys[name], state=state), name
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Integration test for a copy binary sensor whose source starts off."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from aioesphomeapi import BinarySensorInfo, BinarySensorState, SensorInfo, SensorState
|
||||
import pytest
|
||||
|
||||
from .state_utils import require_entity, subscribe_after_initial_states
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_copy_initially_off(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""The copy takes the source's constructor state and its on_state_change sees it."""
|
||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||
entities, _ = await client.list_entities_services()
|
||||
copy = require_entity(entities, "copy_binary", BinarySensorInfo)
|
||||
count = require_entity(entities, "change_count", SensorInfo)
|
||||
|
||||
sub = await subscribe_after_initial_states(client, entities)
|
||||
assert sub.initial_states[copy.key] == BinarySensorState(
|
||||
key=copy.key, state=False
|
||||
)
|
||||
assert sub.initial_states[count.key] == SensorState(key=count.key, state=1.0)
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Integration test for the switch binary sensor, number sensor and text text sensor platforms."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from aioesphomeapi import (
|
||||
BinarySensorState,
|
||||
EntityState,
|
||||
NumberInfo,
|
||||
SensorState,
|
||||
SwitchInfo,
|
||||
TextInfo,
|
||||
TextSensorState,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from .state_utils import require_entity, subscribe_after_initial_states
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
INITIAL: dict[str, tuple[type[EntityState], float | bool | str | None]] = {
|
||||
"switch_state": (BinarySensorState, True),
|
||||
# A switch that has not published leaves its binary sensor unknown
|
||||
"quiet_switch_state": (BinarySensorState, None),
|
||||
"number_value": (SensorState, 4.0),
|
||||
"text_value": (TextSensorState, "hello"),
|
||||
}
|
||||
UPDATED: dict[str, tuple[type[EntityState], float | bool | str | None]] = {
|
||||
"switch_state": (BinarySensorState, False),
|
||||
"quiet_switch_state": (BinarySensorState, True),
|
||||
"number_value": (SensorState, 6.5),
|
||||
"text_value": (TextSensorState, "bye"),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_source_entity_follow(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Each entity holds its source's state from boot, then follows it."""
|
||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||
entities, _ = await client.list_entities_services()
|
||||
keys = {
|
||||
name: require_entity(entities, name, exact=True).key for name in INITIAL
|
||||
}
|
||||
switch = require_entity(entities, "source_switch", SwitchInfo)
|
||||
quiet = require_entity(entities, "quiet_switch", SwitchInfo)
|
||||
number = require_entity(entities, "source_number", NumberInfo)
|
||||
text = require_entity(entities, "source_text", TextInfo)
|
||||
|
||||
sub = await subscribe_after_initial_states(client, entities)
|
||||
for name, (cls, state) in INITIAL.items():
|
||||
key = keys[name]
|
||||
expected = (
|
||||
cls(key=key, missing_state=True)
|
||||
if state is None
|
||||
else cls(key=key, state=state)
|
||||
)
|
||||
assert sub.initial_states[key] == expected, name
|
||||
|
||||
names = list(UPDATED)
|
||||
waits = [sub.waiter.next_state(keys[name]) for name in names]
|
||||
client.switch_command(switch.key, False)
|
||||
client.switch_command(quiet.key, True)
|
||||
client.number_command(number.key, 6.5)
|
||||
client.text_command(text.key, "bye")
|
||||
for name, got in zip(names, await asyncio.gather(*waits), strict=True):
|
||||
cls, state = UPDATED[name]
|
||||
assert got == cls(key=keys[name], state=state), name
|
||||
@@ -11,7 +11,7 @@ import pytest
|
||||
|
||||
from esphome import final_validate as fv
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button, number, select, switch
|
||||
from esphome.components import button, number, select, sensor, switch, text_sensor
|
||||
from esphome.config import Config
|
||||
import esphome.config_validation as cv
|
||||
from esphome.config_validation import Invalid
|
||||
@@ -1883,6 +1883,53 @@ async def test_sub_entities_create_from_whole_config(
|
||||
assert setup_test_environment == ["hub->set_rf_switch(rf_sw)"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("follow", "expected"),
|
||||
[
|
||||
(
|
||||
sensor.follow_sensor,
|
||||
[
|
||||
(
|
||||
"src->add_on_state_callback([](float x) -> void {\n"
|
||||
" ::dst->publish_state(x);\n})"
|
||||
)
|
||||
],
|
||||
),
|
||||
(
|
||||
text_sensor.follow_text_sensor,
|
||||
[
|
||||
(
|
||||
"src->add_on_state_callback([](const std::string & x) -> void {\n"
|
||||
" ::dst->publish_state(x);\n})"
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_follow_entity(
|
||||
setup_test_environment: list[str],
|
||||
follow: Callable[[MockObj, MockObj], None],
|
||||
expected: list[str],
|
||||
) -> None:
|
||||
"""The entity follows its source through a lambda that captures nothing."""
|
||||
follow(MockObj("dst", "->"), MockObj("src", "->"))
|
||||
assert setup_test_environment == expected
|
||||
|
||||
|
||||
def test_follow_entity_hub_callback(setup_test_environment: list[str]) -> None:
|
||||
"""A hub callback takes leading arguments and the published value is an expression."""
|
||||
follow = entity_helpers.FollowEntity(
|
||||
"register_listener", ((cg.uint8, "x"),), "x == 3"
|
||||
)
|
||||
follow(MockObj("dst", "->"), MockObj("hub", "->"), 7)
|
||||
assert setup_test_environment == [
|
||||
(
|
||||
"hub->register_listener(7, [](uint8_t x) -> void {\n"
|
||||
" ::dst->publish_state(x == 3);\n})"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_entity_duplicate_validator_without_name() -> None:
|
||||
"""A config with no name is returned untouched and not tracked."""
|
||||
validator = entity_duplicate_validator("sensor")
|
||||
|
||||
Reference in new issue
Block a user