sensor.template.publish
Action
You can also publish a state to a template sensor from elsewhere in your YAML file with the sensor.template.publish
action.
sensor.template.publish
ActionYou can also publish a state to a template sensor from elsewhere in your YAML file with the sensor.template.publish
action.
I was thinking about this, but dropped the idea, because I couldn’t figure a way to trigger sensor.template.publish
action only when the state changes.
It’s not even clear to me that in which sensor I should fire this? In the sensor from which I want to publish the new state or in the sensor into which I want the state to be published?
I have the following text template sensor what updates and publishes its state every 5s in line with the update_interval. I would like to get the states published only when the state changes. I understand you propose to have another sensor and use sensor.template.publish
to update it, but can you help me how?
text_sensor:
- platform: template
id: drip_progress
name: Drip Progress %
update_interval: 5s
icon: mdi:progress-clock
lambda: !lambda |-
auto ctrl = id(drip);
auto active = ctrl->active_valve();
if (!active.has_value()) {
return std::string("--");
}
auto duration_adjusted = ctrl->valve_run_duration_adjusted(active.value());
return std::to_string((uint32_t)(100.0 * (duration_adjusted - time_remaining.value())) / duration_adjusted);
The state of what? Can you clarify what state you wish to monitor on your lambda? Is it “drip”? Where does this come from? What is it? Is it a sensor, binary_sensor or another template? I do not have visibility on the base state you wish to use to trigger an automation. Automation can be triggered on most things; but you your lambda is completely unfamiliar to me.
The base states of this text template sensor are coming from ESPHome’s sprinkler component.
These are active_valve()
, valve_run_duration_adjusted(active.value()
, time_remaining_active_valve()
, etc.
The text template sensor calculates the progress percentages based on the values getting from these ESPHome native functions.
What I would like to avoid is that the template sensor publish the string “–” every 5 seconds if there is no active sprinkler valve.
I think your question is about the sprinkler component. I suspect I would write a lambda and/ or template to monitor change in state in those components and then trigger publish OR just running your lambda directly after state change. I would have to read how the sprinkler component works. If you don’t get an answer I would repost your question with correct title.
Reading the sprinkler component there is a section on states including running lambda to determine a state and therefore run an automation. Worth reading original on ESPHome and asking on a sprinkler discussion
A number of people have asked about how to determine the sprinkler controller’s state. This section aims to outline how to use the sprinkler controller’s API to determine what it is doing, generally with the goal of indicating this on some form of display hardware. Note that this discussion largely revolves around C++ code (as is used in ESPHome lambdas).
Many of the methods used to determine the sprinkler controller’s state return a type of value known as an optional
. If you are curious, a general reference for the optional
type may be found here, but what is important for now is:
optional
type may or may not contain a value
has_value()
is used to determine if a value is present. For example: id(lawn_sprinkler_ctrlr).active_valve().has_value()
value()
is used to determine the value, if it is determined that a value is present. For example: auto running_valve =id(lawn_sprinkler_ctrlr).active_valve().value()
optional
type can contain a value of any C++ type (bool
, int
, float
, etc.) (In C++ terms, it is a template.)The examples that follow illustrate use of the the sprinkler controller’s methods within adisplay lambda. The examples are intended to illustrate a pattern and (for sake of brevity) are not complete; at very least you’ll need to fill out the display component’s specific configuration details before you can use them.
With these points in mind, let’s discuss some of the methods which indicate the state of the sprinkler controller. We’ll approach this from the angle of “how do I…”
optional<size_t> active_valve()
to check if there is an active valve. If the optional
returned has_value()
, the sprinkler controller is running and you may use the value()
method to check which specific valve is active.Example:
display: - platform: … # …display configuration… lambda: |- if (id(lawn_sprinkler_ctrlr).active_valve().has_value()) { // the controller is running, get the active valve into running_valve and print it auto running_valve = id(lawn_sprinkler_ctrlr).active_valve().value(); it.printf(0, 0, “Valve %u active”, running_valve); } else { // the controller is NOT running it.print(0, 0, “Idle”); }
Hi Julian,
I appreciate your help and time looking into this.
I am almost always doing my homework, before asking, just like I did it in this case.
I studied the documentation and was digging through the forum as well.
With full respect, this was not a sprinkler component question. I was asking if it was somehow possible to use some trick within a text template sensor to only return a new state if the text/string/value/state, whatever we call it has changed compared to the previous value.
For a template sensor, this is easily possible with the delta filter. Consequently one workaround to my issue can be that I setup a template sensor, monitoring the base states, using delta filter on it to filter out values with no change, then run a publish automation to a text template sensor (because I want to do some manipulation of the value).
However, all that would be much simpler, cleaner and easier if there was a native filter in ESPHome for text template sensor to store the last value passed through this filter and only passes incoming values through if incoming value is different from the previously passed one (again, similarly as delta sensor, but for text/ strings).
Why? What does it matter?
My intention is actually decrease the polling time of template sensors internally within ESPHome to every 1 seconds.
It may not causing issues, but feeling clumsy and unnecessary higher footprint/ burden on all participants of this messaging (ESPHome, HA, network, etc.) dealing with all these unnecessary messages. I mean not one particular device, but what if dozens or hundreds of devices are doing the same.
I am absolutely not sure how much impact this is, but again, feels like a lot of waste. Is this really doesn’t matter at all even in case of high number of devices?
I am pretty sure that esphome only publishes a sensor when the value changes.
Well, we are taking about a template text sensor what is running in every X seconds according to the update_interval what has been set.
Please see the below logs of ESPHome, when the update_interval is set to 1s.
Are you saying that ESPHome is not actually sending those states across the network to HA if the state is the same as the previous? I don’t think this is the case.
[13:12:26][D][api.connection:961]: Home Assistant 2023.5.2 (192.168.9.200): Connected successfully
[13:12:26][D][time:044]: Synchronized time: 2023-05-09 13:12:26
[13:12:26][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:27][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:28][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:29][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:30][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:31][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:32][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:33][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:34][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:35][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:36][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:37][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:38][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:39][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:40][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:41][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:42][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:43][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:44][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:45][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:46][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:47][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:48][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:49][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:50][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:51][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:52][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
[13:12:53][D][text_sensor:064]: 'Drip Zone Time Remaining': Sending state '0s'
You should see my wind meter in a gale! Goes crazy with pulses!
If you force it to 1s it will report it every 1s.
Ummm, Nick I think you’ve lost traction here.
How do you think one should set the update_interval to only publishes values on value changes in case of a template sensor what is polling data from native ESPHome functions?
Anyhow, I figured a workaround (actually more) in the meantime… I am just saying it would be really nice if ESPHome would have a filter for text template sensor for such cases.
Normally an Automation. I understand you may not want to do that.
Might be an idea to post your workaround for anybody who is also looking for help; and for all our knowledge
What you want to do makes perfect sense to me and I’m also not aware of a “delta equivalent” which would be my go to for a numerical sensor.
I found on Discord Ssieb suggested adding this as a filter.
filters:
- lambda: |-
static std::string last;
if (x == last)
return {};
last = x;
return x;
Edit: Added filters node for clarity.
The workarounds are specific to my case and are actually involving different, more complex implementation with more bulky code. One of this is what you and I are already discussed: using automation and two sensors. One template sensor that that is monitoring the base states (using sprinkler component native functions) and I can use the delta filter there and I publish data to my text template sensor from there. Another one is that I learned that adding ‘duration’ device class to a sensor will be formatted as hh:mm:ss in HA, so I could even use this (and not publishing the data to the text template sensor), but I would loose the possibility to format the time more human readable (e.g. 1h 10m 10s).
However, finally we seems to have a promising approach from @Mahko_Mahko. This is great! Many thanks for this - I will do some testing tomorrow.
I am just thinking do we still need another text template sensor to publish the data to and do the filtering with this lambda there or can we do within the original lambda whereas there are multiple if conditions? If yes, would you know how to add this to the below sensor?
text_sensor:
- platform: template
id: test_sensor
name: Test Sensor
update_interval: 1s
icon: mdi:progress-clock
lambda: !lambda |-
auto ctrl = id(sprinkler_ctrl);
auto active = ctrl->active_valve();
if (!active.has_value()) {
return std::string("--");
}
auto time_remaining = ctrl->time_remaining_active_valve();
if (!time_remaining.has_value()) {
return std::string("00");
}
int seconds = round(id(sprinkler_ctrl).time_remaining_active_valve().value_or(0));
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
return {
((days ? String(days) + "d " : "") +
(hours ? String(hours) + "h " : "") +
(minutes ? String(minutes) + "m " : "") +
(String(seconds) + "s")
).c_str()};
I think you have options. I think the best solution might just be up to your preference. I’m not sure of performance differences or best practice. I think possibly it all gets optimised during compiles anyway. Dunno much about that though.
Note how the filter: is a different node to the lambda:
During development (and often permanently) , I usually use “copy sensors” (option 2). I mark the “intermediate” ones as internal and mark the one I want to send to HA as external. I find it easier to manage and debug like this.
But sometimes I merge from option 2 to option 1 once everything is running well (less config). But often don’t bother.
I quite like keeping the filters seperate as I find it easier to follow/track the logic (especially when I look at it later).
#Option 1: Filter on original sensor
text_sensor:
- platform: template
id: test_sensor
name: Test Sensor
update_interval: 1s
icon: mdi:progress-clock
lambda: !lambda |-
auto ctrl = id(sprinkler_ctrl);
auto active = ctrl->active_valve();
if (!active.has_value()) {
return std::string("--");
}
auto time_remaining = ctrl->time_remaining_active_valve();
if (!time_remaining.has_value()) {
return std::string("00");
}
int seconds = round(id(sprinkler_ctrl).time_remaining_active_valve().value_or(0));
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
return {
((days ? String(days) + "d " : "") +
(hours ? String(hours) + "h " : "") +
(minutes ? String(minutes) + "m " : "") +
(String(seconds) + "s")
).c_str()};
filters: ##########New
- lambda: |-
static std::string last;
if (x == last)
return {};
last = x;
return x;
#Option 2: Filter on "copy sensor"
text_sensor:
- platform: template
id: test_sensor
name: Test Sensor
internal: true ##########New
update_interval: 1s
icon: mdi:progress-clock
lambda: !lambda |-
auto ctrl = id(sprinkler_ctrl);
auto active = ctrl->active_valve();
if (!active.has_value()) {
return std::string("--");
}
auto time_remaining = ctrl->time_remaining_active_valve();
if (!time_remaining.has_value()) {
return std::string("00");
}
int seconds = round(id(sprinkler_ctrl).time_remaining_active_valve().value_or(0));
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
return {
((days ? String(days) + "d " : "") +
(hours ? String(hours) + "h " : "") +
(minutes ? String(minutes) + "m " : "") +
(String(seconds) + "s")
).c_str()};
- platform: copy
source_id: test_sensor
name: "Test Sensor For HA"
internal: false
filters:
- lambda: |-
static std::string last;
if (x == last)
return {};
last = x;
return x;
#Option3: Merge it into your main lambda. Not sure how to do that from top of my hea
Credit really goes to @ssieb on this solution since I found out on a search from one of his Discord responses for the same query.
Many thanks @Mahko_Mahko
I just realized indeed this morning that we shall use the quoted lambda in a different filter node and not within the main lambda.
I already tested this, but you were quicker. The below works perfectly! Very nice, thank you again!
text_sensor:
- platform: template
id: test_sensor
name: Test Sensor
update_interval: 1s
icon: mdi:progress-clock
filters:
- lambda: |-
static std::string last;
if (x == last)
return {};
last = x;
return x;
lambda: !lambda |-
auto ctrl = id(sprinkler_ctrl);
auto active = ctrl->active_valve();
if (!active.has_value()) {
return std::string("--");
}
auto time_remaining = ctrl->time_remaining_active_valve();
if (!time_remaining.has_value()) {
return std::string("00");
}
int seconds = round(id(sprinkler_ctrl).time_remaining_active_valve().value_or(0));
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
return {
((days ? String(days) + "d " : "") +
(hours ? String(hours) + "h " : "") +
(minutes ? String(minutes) + "m " : "") +
(String(seconds) + "s")
).c_str()};