Edit esp-gasheater.yaml
This commit is contained in:
+115
-45
@@ -6,6 +6,10 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-gasheater.yaml
|
||||
#:########################################################################################:#
|
||||
# VERSIONS:
|
||||
# V1.12 2026-06-12 Added managed OFF mode separate from BYPASS (Remote Only).
|
||||
# OFF is now the normal startup/thermostat-off mode and will
|
||||
# take over if the heater is locally started via the physical button.
|
||||
# BYPASS remains a true hands-off/remote-only service mode.
|
||||
# V1.11 2026-04-26 Restored Heater Operation after regression; restored fast optimistic
|
||||
# master setpoint number with debounced MQTT publish; restored proportional
|
||||
# flame tuning; kept current GPIO12/13/14 relay mapping
|
||||
@@ -65,12 +69,19 @@
|
||||
# - AUTO may still be selected manually later for testing
|
||||
# - Heater Operation is a high-level thermostat enable helper:
|
||||
# - ON = turn Test Mode OFF (if needed) and set Mode to AUTO
|
||||
# - OFF = force heater Power OFF and set Mode to BYPASS (Remote Only)
|
||||
# - In any mode except BYPASS (Remote Only), if the controller intends heater power OFF
|
||||
# but either heater LED still shows the heater ON, ESPHome will pulse the heater ON/OFF
|
||||
# button to force it fully OFF.
|
||||
# - OFF = force heater Power OFF and set Mode to OFF
|
||||
# - OFF mode is the normal thermostat-off / managed-standby mode:
|
||||
# - The controller will not call for heat by itself.
|
||||
# - If the heater is locally started using the physical heater button, the LEDs will
|
||||
# show that the heater is ON and ESPHome will switch to AUTO and take over.
|
||||
# - BYPASS (Remote Only) is a true hands-off / service mode:
|
||||
# - ESPHome may still observe the LEDs/status.
|
||||
# - ESPHome will not enforce OFF and will not take over after a local physical start.
|
||||
# - In AUTO or MANUAL, if the controller intends heater power OFF but either heater LED
|
||||
# still shows the heater ON, ESPHome will pulse the heater ON/OFF button to force it
|
||||
# fully OFF.
|
||||
# - On every ESPHome restart (including yaml upload or power loss), the controller first
|
||||
# forces the heater OFF, then returns to BYPASS (Remote Only).
|
||||
# forces the heater OFF, then returns to OFF mode.
|
||||
# - On power ON in AUTO, the controller:
|
||||
# - Applies a warmup lockout
|
||||
# - Calibrates to level 1 using 6x DOWN
|
||||
@@ -120,7 +131,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Sonoff Technologies.Sonoff DEV"
|
||||
project_version: "v1.11"
|
||||
project_version: "v1.12"
|
||||
|
||||
# Passwords & Secrets
|
||||
api_key: !secret esp-api_key
|
||||
@@ -421,21 +432,33 @@ binary_sensor:
|
||||
if (!id(test_mode_enabled).state && id(mode_select).state == "AUTO") {
|
||||
id(warmup_until_ms) = millis() + (${warmup_seconds} * 1000UL);
|
||||
}
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state != "BYPASS (Remote Only)" && !id(power_intended_on) && !id(self_power_on);'
|
||||
then:
|
||||
- script.execute: enforce_heater_off
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(test_mode_enabled).state && (id(mode_select).state == "AUTO") && id(power_intended_on) && (!id(self_power_on));'
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_until_ms) = millis() + (${fast_ramp_seconds} * 1000UL);
|
||||
- delay: ${warmup_seconds}s
|
||||
- script.execute: calibrate_to_min
|
||||
- script.execute: auto_control_tick
|
||||
- script.execute: adjust_to_desired
|
||||
- choose:
|
||||
# OFF mode watches for local physical heater starts.
|
||||
# If someone presses the real heater ON button, the LEDs come on, this
|
||||
# powered_state turns ON, and ESPHome changes to AUTO and takes over.
|
||||
- conditions:
|
||||
- lambda: 'return !id(test_mode_enabled).state && id(mode_select).state == "OFF" && !id(self_power_on);'
|
||||
sequence:
|
||||
- script.execute: local_on_takeover
|
||||
|
||||
# In AUTO/MANUAL, if the controller intended the heater OFF but LEDs say ON,
|
||||
# force the heater OFF. OFF is excluded because OFF performs local-start takeover.
|
||||
- conditions:
|
||||
- lambda: 'return !id(test_mode_enabled).state && id(mode_select).state != "BYPASS (Remote Only)" && id(mode_select).state != "OFF" && !id(power_intended_on) && !id(self_power_on);'
|
||||
sequence:
|
||||
- script.execute: enforce_heater_off
|
||||
|
||||
# If the heater turns on externally while AUTO already intended it ON,
|
||||
# re-baseline after warmup and continue normal automatic control.
|
||||
- conditions:
|
||||
- lambda: 'return !id(test_mode_enabled).state && id(mode_select).state == "AUTO" && id(power_intended_on) && !id(self_power_on);'
|
||||
sequence:
|
||||
- lambda: |-
|
||||
id(ramp_until_ms) = millis() + (${fast_ramp_seconds} * 1000UL);
|
||||
- delay: ${warmup_seconds}s
|
||||
- script.execute: calibrate_to_min
|
||||
- script.execute: auto_control_tick
|
||||
- script.execute: adjust_to_desired
|
||||
|
||||
on_release:
|
||||
- lambda: |-
|
||||
@@ -493,7 +516,8 @@ switch:
|
||||
|
||||
# High-level thermostat enable helper.
|
||||
# ON = thermostat logic active in AUTO
|
||||
# OFF = force BYPASS and turn heater power OFF
|
||||
# OFF = managed OFF mode. Heater is forced OFF, but local physical starts are monitored
|
||||
# and will cause ESPHome to switch back to AUTO and take over.
|
||||
- platform: template
|
||||
id: heater_operation_switch
|
||||
name: "Heater Operation"
|
||||
@@ -514,10 +538,9 @@ switch:
|
||||
turn_off_action:
|
||||
- lambda: |-
|
||||
id(power_intended_on) = false;
|
||||
- switch.turn_off: power_switch
|
||||
- select.set:
|
||||
id: mode_select
|
||||
option: "BYPASS (Remote Only)"
|
||||
option: "OFF"
|
||||
|
||||
- platform: template
|
||||
id: power_switch
|
||||
@@ -650,31 +673,42 @@ text_sensor:
|
||||
}
|
||||
|
||||
if (use_test_temp) {
|
||||
if (mode == "OFF") {
|
||||
return std::string("Off (Test)");
|
||||
}
|
||||
|
||||
if (mode == "BYPASS (Remote Only)") {
|
||||
return std::string("Bypass (Remote Only) (Test)");
|
||||
}
|
||||
|
||||
if (mode == "MANUAL") {
|
||||
if (id(powered_state).state) {
|
||||
char buf[48];
|
||||
snprintf(buf, sizeof(buf), "Manual (Level %d) (Test)", id(current_level));
|
||||
return std::string(buf);
|
||||
}
|
||||
if (id(led_standby_red).state) return std::string("Manual (Standby) (Test)");
|
||||
return std::string("Manual (Off) (Test)");
|
||||
}
|
||||
|
||||
if (id(powered_state).state) {
|
||||
char buf[40];
|
||||
snprintf(buf, sizeof(buf), "Operating (Level %d) (Test)", id(current_level));
|
||||
snprintf(buf, sizeof(buf), "Auto (Level %d) (Test)", id(current_level));
|
||||
return std::string(buf);
|
||||
}
|
||||
if (mode == "AUTO" && !isnan(effective_t) && effective_t >= at_temp_threshold) {
|
||||
return std::string("At Temperature (Test)");
|
||||
return std::string("Auto (At Temperature) (Test)");
|
||||
}
|
||||
return std::string("Off (Test)");
|
||||
return std::string("Auto (Standby) (Test)");
|
||||
}
|
||||
|
||||
if (mode == "OFF") {
|
||||
return std::string("Off");
|
||||
}
|
||||
|
||||
if (mode == "BYPASS (Remote Only)") {
|
||||
if (id(led_operating_blue).state) return std::string("Remote Only (Operating)");
|
||||
if (id(led_standby_red).state) return std::string("Remote Only (Standby)");
|
||||
return std::string("Remote Only (Off)");
|
||||
if (id(led_operating_blue).state) return std::string("Bypass (Operating)");
|
||||
if (id(led_standby_red).state) return std::string("Bypass (Standby)");
|
||||
return std::string("Bypass (Remote Only)");
|
||||
}
|
||||
|
||||
if (mode == "MANUAL") {
|
||||
@@ -689,14 +723,14 @@ text_sensor:
|
||||
|
||||
if (id(led_operating_blue).state) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "Operating (Level %d)", id(current_level));
|
||||
snprintf(buf, sizeof(buf), "Auto (Level %d)", id(current_level));
|
||||
return std::string(buf);
|
||||
}
|
||||
if (!isnan(effective_t) && effective_t >= at_temp_threshold) {
|
||||
return std::string("At Temperature");
|
||||
return std::string("Auto (At Temperature)");
|
||||
}
|
||||
if (id(led_standby_red).state) return std::string("Standby");
|
||||
return std::string("Standby");
|
||||
if (id(led_standby_red).state) return std::string("Auto (Standby)");
|
||||
return std::string("Auto (Standby)");
|
||||
on_value:
|
||||
- script.execute: publish_status
|
||||
|
||||
@@ -728,18 +762,24 @@ select:
|
||||
- platform: template
|
||||
id: mode_select
|
||||
name: "Mode"
|
||||
options: ["AUTO","MANUAL","BYPASS (Remote Only)"]
|
||||
options: ["OFF","AUTO","MANUAL","BYPASS (Remote Only)"]
|
||||
optimistic: true
|
||||
initial_option: "BYPASS (Remote Only)"
|
||||
initial_option: "OFF"
|
||||
restore_value: false
|
||||
set_action:
|
||||
- lambda: |-
|
||||
if (x == "BYPASS (Remote Only)") {
|
||||
if (x == "OFF" || x == "BYPASS (Remote Only)") {
|
||||
id(power_intended_on) = false;
|
||||
}
|
||||
if (x != "AUTO") {
|
||||
id(room_temp_lost_fault) = false;
|
||||
}
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return x == "OFF" && !id(test_mode_enabled).state && id(powered_state).state;'
|
||||
then:
|
||||
- logger.log: "Mode OFF selected: heater appears ON, pulsing power to force OFF."
|
||||
- script.execute: press_power
|
||||
|
||||
- platform: template
|
||||
id: flame_level_select
|
||||
@@ -932,13 +972,15 @@ interval:
|
||||
then:
|
||||
- script.execute: auto_control_tick
|
||||
|
||||
# Outside BYPASS, if the controller intends power OFF but LEDs still show ON,
|
||||
# Outside OFF/BYPASS, if the controller intends power OFF but LEDs still show ON,
|
||||
# keep checking and force the heater OFF.
|
||||
# OFF is excluded because it deliberately watches for local physical starts and
|
||||
# switches to AUTO instead of forcing the heater back off.
|
||||
- interval: ${power_off_check_interval_s}s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state != "BYPASS (Remote Only)" && !id(power_intended_on) && id(powered_state).state && !id(self_power_on);'
|
||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state != "BYPASS (Remote Only)" && id(mode_select).state != "OFF" && !id(power_intended_on) && id(powered_state).state && !id(self_power_on);'
|
||||
then:
|
||||
- script.execute: enforce_heater_off
|
||||
|
||||
@@ -984,7 +1026,29 @@ script:
|
||||
|
||||
id(powered_state).publish_state(new_state);
|
||||
|
||||
# Force heater OFF after an ESPHome reboot before returning to BYPASS.
|
||||
# OFF-mode local physical-start takeover.
|
||||
# When Mode is OFF and the heater is started from its own front panel, the LED
|
||||
# inputs make powered_state turn ON. This script changes to AUTO, warms up,
|
||||
# re-baselines the flame level, and resumes thermostat control.
|
||||
- id: local_on_takeover
|
||||
mode: restart
|
||||
then:
|
||||
- logger.log: "OFF mode: local physical heater start detected. Switching to AUTO and taking over."
|
||||
- lambda: |-
|
||||
id(power_intended_on) = true;
|
||||
id(warmup_until_ms) = millis() + (${warmup_seconds} * 1000UL);
|
||||
id(ramp_until_ms) = millis() + (${fast_ramp_seconds} * 1000UL);
|
||||
- select.set:
|
||||
id: mode_select
|
||||
option: "AUTO"
|
||||
- delay: ${warmup_seconds}s
|
||||
- script.execute: calibrate_to_min
|
||||
- script.execute: auto_control_tick
|
||||
- script.execute: adjust_to_desired
|
||||
- lambda: |-
|
||||
id(self_power_on) = false;
|
||||
|
||||
# Force heater OFF after an ESPHome reboot before returning to OFF.
|
||||
- id: boot_force_heater_off
|
||||
mode: restart
|
||||
then:
|
||||
@@ -1003,12 +1067,13 @@ script:
|
||||
- script.execute: recompute_powered_state
|
||||
- select.set:
|
||||
id: mode_select
|
||||
option: "BYPASS (Remote Only)"
|
||||
option: "OFF"
|
||||
- lambda: |-
|
||||
id(power_intended_on) = false;
|
||||
|
||||
# In non-BYPASS modes, if the controller intends power OFF but LEDs still show ON,
|
||||
# In AUTO/MANUAL, if the controller intends power OFF but LEDs still show ON,
|
||||
# pulse the heater ON/OFF button to force the heater fully OFF.
|
||||
# OFF mode is excluded because a physical start in OFF should trigger AUTO takeover.
|
||||
- id: enforce_heater_off
|
||||
mode: restart
|
||||
then:
|
||||
@@ -1016,7 +1081,7 @@ script:
|
||||
- script.execute: recompute_powered_state
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state != "BYPASS (Remote Only)" && !id(power_intended_on) && id(powered_state).state && !id(self_power_on);'
|
||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state != "BYPASS (Remote Only)" && id(mode_select).state != "OFF" && !id(power_intended_on) && id(powered_state).state && !id(self_power_on);'
|
||||
then:
|
||||
- logger.log: "Heater should be OFF but LEDs still show ON. Pulsing power to force OFF."
|
||||
- script.execute: press_power
|
||||
@@ -1125,7 +1190,7 @@ script:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mode_select).state != "BYPASS (Remote Only)" && id(powered_state).state;'
|
||||
lambda: 'return id(mode_select).state != "BYPASS (Remote Only)" && id(mode_select).state != "OFF" && id(powered_state).state;'
|
||||
then:
|
||||
- lambda: |-
|
||||
id(calibrating) = true;
|
||||
@@ -1245,6 +1310,11 @@ script:
|
||||
mode: queued
|
||||
then:
|
||||
- lambda: |-
|
||||
if (id(mode_select).state != "AUTO") {
|
||||
ESP_LOGD("gas", "AUTO: skipping control tick because Mode is not AUTO");
|
||||
return;
|
||||
}
|
||||
|
||||
const bool use_test_temp = id(test_mode_enabled).state;
|
||||
const float t = use_test_temp ? id(test_temp_c) : id(room_temp).state;
|
||||
const float sp = id(setpoint_c);
|
||||
|
||||
Reference in New Issue
Block a user