mirror of
https://github.com/esphome/esphome.git
synced 2026-02-28 01:44:20 -07:00
Implemented support for On/Off and Away modes in the template water heater platform, including optimistic control and lambda-based state reporting. Refactored the base 'WaterHeaterCall' to replace the 'state_' bitmask with 'optional<bool>' for 'on' and 'away' fields. This change was necessary to enable partial (delta) updates. The previous bitmask implementation did not distinguish between a field being "set to false" and "not set at all," causing unintended state resets (e.g., turning the device off when only adjusting temperature).
484 lines
12 KiB
YAML
484 lines
12 KiB
YAML
esphome:
|
|
on_boot:
|
|
- sensor.template.publish:
|
|
id: template_sens
|
|
state: 42.0
|
|
|
|
# Templated
|
|
- sensor.template.publish:
|
|
id: template_sens
|
|
state: !lambda "return 42.0;"
|
|
|
|
- water_heater.template.publish:
|
|
id: template_water_heater
|
|
target_temperature: 50.0
|
|
mode: ECO
|
|
|
|
# Templated
|
|
- water_heater.template.publish:
|
|
id: template_water_heater
|
|
current_temperature: !lambda "return 45.0;"
|
|
target_temperature: !lambda "return 55.0;"
|
|
mode: !lambda "return water_heater::WATER_HEATER_MODE_GAS;"
|
|
|
|
# Test C++ API: set_template() with stateless lambda (no captures)
|
|
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
|
|
- lambda: |-
|
|
id(template_sens).set_template([]() -> esphome::optional<float> {
|
|
return 123.0f;
|
|
});
|
|
|
|
- datetime.date.set:
|
|
id: test_date
|
|
date:
|
|
year: 2021
|
|
month: 1
|
|
day: 1
|
|
- datetime.date.set:
|
|
id: test_date
|
|
date: !lambda "return {.day_of_month = 1, .month = 1, .year = 2021};"
|
|
- datetime.date.set:
|
|
id: test_date
|
|
date: "2021-01-01"
|
|
|
|
binary_sensor:
|
|
- platform: template
|
|
id: some_binary_sensor
|
|
name: "Garage Door Open"
|
|
lambda: |-
|
|
if (id(template_sens).state > 30) {
|
|
// Garage Door is open.
|
|
return true;
|
|
} else {
|
|
// Garage Door is closed.
|
|
return false;
|
|
}
|
|
- platform: template
|
|
id: select_binary_sensor
|
|
name: Select is one or two
|
|
condition:
|
|
any:
|
|
- select.is:
|
|
id: template_select
|
|
options: [one, two]
|
|
- select.is:
|
|
id: template_select
|
|
lambda: return current == id(template_text).state;
|
|
- platform: template
|
|
id: other_binary_sensor
|
|
name: "Garage Door Closed"
|
|
condition:
|
|
sensor.in_range:
|
|
id: template_sens
|
|
below: 30.0
|
|
filters:
|
|
- invert:
|
|
- delayed_on: 100ms
|
|
- delayed_off: 100ms
|
|
- delayed_on_off: !lambda "if (id(test_switch).state) return 1000; else return 0;"
|
|
- delayed_on_off:
|
|
time_on: 10s
|
|
time_off: !lambda "if (id(test_switch).state) return 1000; else return 0;"
|
|
- autorepeat:
|
|
- delay: 1s
|
|
time_off: 100ms
|
|
time_on: 900ms
|
|
- delay: 5s
|
|
time_off: 100ms
|
|
time_on: 400ms
|
|
- lambda: |-
|
|
if (id(other_binary_sensor).state) {
|
|
return x;
|
|
}
|
|
return {};
|
|
- settle: 500ms
|
|
- timeout: 5s
|
|
|
|
sensor:
|
|
- platform: template
|
|
name: "Template Sensor"
|
|
id: template_sens
|
|
lambda: |-
|
|
if (id(some_binary_sensor).state) {
|
|
return 42.0;
|
|
}
|
|
return 0.0;
|
|
update_interval: 60s
|
|
filters:
|
|
- calibrate_linear:
|
|
- 0.0 -> 0.0
|
|
- 40.0 -> 45.0
|
|
- 100.0 -> 102.5
|
|
- calibrate_polynomial:
|
|
degree: 2
|
|
datapoints:
|
|
# Map 0.0 (from sensor) to 0.0 (true value)
|
|
- 0.0 -> 0.0
|
|
- 10.0 -> 12.1
|
|
- 13.0 -> 14.0
|
|
- clamp:
|
|
# Infinity and NaN will be clamped (NaN -> min_value, +Infinity -> max_value, -Infinity -> min_value)
|
|
max_value: 10.0
|
|
min_value: -10.0
|
|
- debounce: 0.1s
|
|
- delta: 5.0
|
|
- delta:
|
|
max_value: 2%
|
|
- exponential_moving_average:
|
|
alpha: 0.1
|
|
send_every: 15
|
|
- filter_out:
|
|
- 10
|
|
- 20
|
|
- !lambda return 10;
|
|
- filter_out: 10
|
|
- filter_out: !lambda return NAN;
|
|
- heartbeat: 5s
|
|
- heartbeat:
|
|
period: 5s
|
|
optimistic: true
|
|
- lambda: return x * (9.0/5.0) + 32.0;
|
|
- max:
|
|
window_size: 10
|
|
send_every: 2
|
|
send_first_at: 1
|
|
- median:
|
|
window_size: 7
|
|
send_every: 4
|
|
send_first_at: 3
|
|
- min:
|
|
window_size: 10
|
|
send_every: 2
|
|
send_first_at: 1
|
|
- multiply: 1
|
|
- multiply: !lambda return 2;
|
|
- offset: 10
|
|
- offset: !lambda return 10;
|
|
- or:
|
|
- quantile:
|
|
window_size: 7
|
|
send_every: 4
|
|
send_first_at: 3
|
|
quantile: .9
|
|
- round: 1
|
|
- round_to_multiple_of: 0.25
|
|
- skip_initial: 3
|
|
- sliding_window_moving_average:
|
|
window_size: 15
|
|
send_every: 15
|
|
- throttle: 1s
|
|
- throttle_average: 2s
|
|
- throttle_with_priority: 5s
|
|
- throttle_with_priority:
|
|
timeout: 3s
|
|
value: 42.0
|
|
- throttle_with_priority:
|
|
timeout: 3s
|
|
value: !lambda return 1.0f / 2.0f;
|
|
- throttle_with_priority:
|
|
timeout: 3s
|
|
value:
|
|
- 42.0
|
|
- !lambda return 2.0f / 2.0f;
|
|
- nan
|
|
- timeout:
|
|
timeout: 10s
|
|
value: !lambda return 10;
|
|
- timeout:
|
|
timeout: 1h
|
|
value: 20.0
|
|
- timeout:
|
|
timeout: 1min
|
|
value: last
|
|
- timeout:
|
|
timeout: 1d
|
|
- to_ntc_resistance:
|
|
calibration:
|
|
- 10.0kOhm -> 25°C
|
|
- 27.219kOhm -> 0°C
|
|
- 14.674kOhm -> 15°C
|
|
- to_ntc_temperature:
|
|
calibration:
|
|
- 10.0kOhm -> 25°C
|
|
- 27.219kOhm -> 0°C
|
|
- 14.674kOhm -> 15°C
|
|
|
|
output:
|
|
- platform: template
|
|
id: outputsplit
|
|
type: float
|
|
write_action:
|
|
- logger.log: "write_action"
|
|
|
|
switch:
|
|
- platform: template
|
|
id: test_switch
|
|
name: "Template Switch"
|
|
lambda: |-
|
|
if (id(some_binary_sensor).state) {
|
|
return true;
|
|
}
|
|
return false;
|
|
turn_on_action:
|
|
- logger.log: "turn_on_action"
|
|
turn_off_action:
|
|
- logger.log: "turn_off_action"
|
|
|
|
button:
|
|
- platform: template
|
|
name: "Template Button"
|
|
on_press:
|
|
- logger.log: Button Pressed
|
|
|
|
cover:
|
|
- platform: template
|
|
name: "Template Cover"
|
|
lambda: |-
|
|
if (id(some_binary_sensor).state) {
|
|
return COVER_OPEN;
|
|
}
|
|
return COVER_CLOSED;
|
|
open_action:
|
|
- logger.log: open_action
|
|
close_action:
|
|
- logger.log: close_action
|
|
stop_action:
|
|
- logger.log: stop_action
|
|
optimistic: true
|
|
- platform: template
|
|
name: "Template Cover with Triggers"
|
|
id: template_cover_with_triggers
|
|
lambda: |-
|
|
if (id(some_binary_sensor).state) {
|
|
return COVER_OPEN;
|
|
}
|
|
return COVER_CLOSED;
|
|
open_action:
|
|
- logger.log: open_action
|
|
close_action:
|
|
- logger.log: close_action
|
|
stop_action:
|
|
- logger.log: stop_action
|
|
optimistic: true
|
|
on_open:
|
|
- logger.log: "Cover on_open (deprecated)"
|
|
on_opened:
|
|
- logger.log: "Cover fully opened"
|
|
on_closed:
|
|
- logger.log: "Cover fully closed"
|
|
on_opening:
|
|
- logger.log: "Cover started opening"
|
|
on_closing:
|
|
- logger.log: "Cover started closing"
|
|
on_idle:
|
|
- logger.log: "Cover stopped moving"
|
|
- logger.log: "Cover stopped moving"
|
|
- if:
|
|
condition:
|
|
cover.is_open: template_cover_with_triggers
|
|
then:
|
|
logger.log: Cover is open
|
|
- if:
|
|
condition:
|
|
cover.is_closed: template_cover_with_triggers
|
|
then:
|
|
logger.log: Cover is closed
|
|
|
|
number:
|
|
- platform: template
|
|
id: template_number
|
|
name: "Template number"
|
|
optimistic: true
|
|
min_value: 0
|
|
max_value: 100
|
|
step: 1
|
|
|
|
select:
|
|
- platform: template
|
|
id: template_select
|
|
name: "Template select"
|
|
optimistic: true
|
|
options:
|
|
- one
|
|
- two
|
|
- three
|
|
initial_option: two
|
|
# Test current_option() returning std::string_view - migration guide examples
|
|
on_value:
|
|
- lambda: |-
|
|
// Migration guide: Check if select has a state
|
|
// OLD: if (id(template_select).current_option() != nullptr)
|
|
// NEW: Check with .empty()
|
|
if (!id(template_select).current_option().empty()) {
|
|
ESP_LOGI("test", "Select has state");
|
|
}
|
|
|
|
// Migration guide: Compare option values
|
|
// OLD: if (strcmp(id(template_select).current_option(), "one") == 0)
|
|
// NEW: Direct comparison works safely even when empty
|
|
if (id(template_select).current_option() == "one") {
|
|
ESP_LOGI("test", "Option is 'one'");
|
|
}
|
|
if (id(template_select).current_option() != "two") {
|
|
ESP_LOGI("test", "Option is not 'two'");
|
|
}
|
|
|
|
// Migration guide: Logging options
|
|
// Option 1: Using .c_str() - StringRef guarantees null-termination
|
|
ESP_LOGI("test", "Current option: %s", id(template_select).current_option().c_str());
|
|
|
|
// Option 2: Using %.*s format with size
|
|
auto option = id(template_select).current_option();
|
|
ESP_LOGI("test", "Current option (safe): %.*s", (int) option.size(), option.c_str());
|
|
|
|
// Migration guide: Store in std::string
|
|
std::string stored_option(id(template_select).current_option());
|
|
ESP_LOGI("test", "Stored: %s", stored_option.c_str());
|
|
- platform: template
|
|
id: template_select_with_action
|
|
name: "Template select with action"
|
|
options:
|
|
- option_a
|
|
- option_b
|
|
set_action:
|
|
- logger.log:
|
|
format: "Selected: %s"
|
|
args: ["x.c_str()"]
|
|
|
|
lock:
|
|
- platform: template
|
|
name: "Template Lock"
|
|
lambda: |-
|
|
if (id(some_binary_sensor).state) {
|
|
return LOCK_STATE_LOCKED;
|
|
}
|
|
return LOCK_STATE_UNLOCKED;
|
|
lock_action:
|
|
- logger.log: lock_action
|
|
unlock_action:
|
|
- logger.log: unlock_action
|
|
open_action:
|
|
- logger.log: open_action
|
|
|
|
valve:
|
|
- platform: template
|
|
id: template_valve
|
|
name: "Template Valve"
|
|
lambda: |-
|
|
if (id(some_binary_sensor).state) {
|
|
return VALVE_OPEN;
|
|
}
|
|
return VALVE_CLOSED;
|
|
open_action:
|
|
- logger.log: open_action
|
|
close_action:
|
|
- logger.log: close_action
|
|
- valve.template.publish:
|
|
id: template_valve
|
|
state: CLOSED
|
|
stop_action:
|
|
- logger.log: stop_action
|
|
optimistic: true
|
|
|
|
text:
|
|
- platform: template
|
|
id: template_text
|
|
name: "Template text"
|
|
optimistic: true
|
|
min_length: 0
|
|
max_length: 100
|
|
mode: text
|
|
- platform: template
|
|
name: "Template text lambda"
|
|
mode: text
|
|
update_interval: 1s
|
|
lambda: |
|
|
return std::string{"Hello!"};
|
|
set_action:
|
|
then:
|
|
- logger.log:
|
|
format: Template Text set to %s
|
|
args: ["x.c_str()"]
|
|
|
|
alarm_control_panel:
|
|
- platform: template
|
|
name: Alarm Panel
|
|
codes:
|
|
- "1234"
|
|
|
|
water_heater:
|
|
- platform: template
|
|
id: template_water_heater
|
|
name: "Template Water Heater"
|
|
optimistic: true
|
|
current_temperature: !lambda "return 42.0f;"
|
|
target_temperature: !lambda "return 60.0f;"
|
|
mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;"
|
|
on: !lambda "return true;"
|
|
away: !lambda "return false;"
|
|
supported_modes:
|
|
- "OFF"
|
|
- ECO
|
|
- GAS
|
|
- ELECTRIC
|
|
- HEAT_PUMP
|
|
- HIGH_DEMAND
|
|
- PERFORMANCE
|
|
set_action:
|
|
- logger.log: "set_action"
|
|
- water_heater.template.publish:
|
|
id: template_water_heater
|
|
on: false
|
|
away: true
|
|
- water_heater.template.publish:
|
|
id: template_water_heater
|
|
on: !lambda "return true;"
|
|
away: !lambda "return false;"
|
|
|
|
datetime:
|
|
- platform: template
|
|
name: Date
|
|
id: test_date
|
|
type: date
|
|
initial_value: "2000-1-2"
|
|
set_action:
|
|
- logger.log: "set_value"
|
|
on_value:
|
|
- logger.log:
|
|
format: "Date: %04d-%02d-%02d"
|
|
args:
|
|
- x.year
|
|
- x.month
|
|
- x.day_of_month
|
|
- platform: template
|
|
name: Time
|
|
id: test_time
|
|
type: time
|
|
initial_value: "12:34:56am"
|
|
set_action:
|
|
- logger.log: "set_value"
|
|
on_value:
|
|
- logger.log:
|
|
format: "Time: %02d:%02d:%02d"
|
|
args:
|
|
- x.hour
|
|
- x.minute
|
|
- x.second
|
|
- platform: template
|
|
name: DateTime
|
|
id: test_datetime
|
|
type: datetime
|
|
initial_value: "2000-1-2 12:34:56"
|
|
set_action:
|
|
- logger.log: "set_value"
|
|
on_value:
|
|
- logger.log:
|
|
format: "DateTime: %04d-%02d-%02d %02d:%02d:%02d"
|
|
args:
|
|
- x.year
|
|
- x.month
|
|
- x.day_of_month
|
|
- x.hour
|
|
- x.minute
|
|
- x.second
|