Edit esp-gasheater.yaml
This commit is contained in:
+108
-38
@@ -6,6 +6,10 @@
|
|||||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-gasheater.yaml
|
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-gasheater.yaml
|
||||||
#:########################################################################################:#
|
#:########################################################################################:#
|
||||||
# VERSIONS:
|
# 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
|
# V1.11 2026-04-26 Restored Heater Operation after regression; restored fast optimistic
|
||||||
# master setpoint number with debounced MQTT publish; restored proportional
|
# master setpoint number with debounced MQTT publish; restored proportional
|
||||||
# flame tuning; kept current GPIO12/13/14 relay mapping
|
# flame tuning; kept current GPIO12/13/14 relay mapping
|
||||||
@@ -65,12 +69,19 @@
|
|||||||
# - AUTO may still be selected manually later for testing
|
# - AUTO may still be selected manually later for testing
|
||||||
# - Heater Operation is a high-level thermostat enable helper:
|
# - Heater Operation is a high-level thermostat enable helper:
|
||||||
# - ON = turn Test Mode OFF (if needed) and set Mode to AUTO
|
# - ON = turn Test Mode OFF (if needed) and set Mode to AUTO
|
||||||
# - OFF = force heater Power OFF and set Mode to BYPASS (Remote Only)
|
# - OFF = force heater Power OFF and set Mode to OFF
|
||||||
# - In any mode except BYPASS (Remote Only), if the controller intends heater power OFF
|
# - OFF mode is the normal thermostat-off / managed-standby mode:
|
||||||
# but either heater LED still shows the heater ON, ESPHome will pulse the heater ON/OFF
|
# - The controller will not call for heat by itself.
|
||||||
# button to force it fully OFF.
|
# - 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
|
# - 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:
|
# - On power ON in AUTO, the controller:
|
||||||
# - Applies a warmup lockout
|
# - Applies a warmup lockout
|
||||||
# - Calibrates to level 1 using 6x DOWN
|
# - Calibrates to level 1 using 6x DOWN
|
||||||
@@ -120,7 +131,7 @@ substitutions:
|
|||||||
|
|
||||||
# Project Naming
|
# Project Naming
|
||||||
project_name: "Sonoff Technologies.Sonoff DEV"
|
project_name: "Sonoff Technologies.Sonoff DEV"
|
||||||
project_version: "v1.11"
|
project_version: "v1.12"
|
||||||
|
|
||||||
# Passwords & Secrets
|
# Passwords & Secrets
|
||||||
api_key: !secret esp-api_key
|
api_key: !secret esp-api_key
|
||||||
@@ -421,15 +432,27 @@ binary_sensor:
|
|||||||
if (!id(test_mode_enabled).state && id(mode_select).state == "AUTO") {
|
if (!id(test_mode_enabled).state && id(mode_select).state == "AUTO") {
|
||||||
id(warmup_until_ms) = millis() + (${warmup_seconds} * 1000UL);
|
id(warmup_until_ms) = millis() + (${warmup_seconds} * 1000UL);
|
||||||
}
|
}
|
||||||
- if:
|
- choose:
|
||||||
condition:
|
# OFF mode watches for local physical heater starts.
|
||||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state != "BYPASS (Remote Only)" && !id(power_intended_on) && !id(self_power_on);'
|
# If someone presses the real heater ON button, the LEDs come on, this
|
||||||
then:
|
# 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
|
- script.execute: enforce_heater_off
|
||||||
- if:
|
|
||||||
condition:
|
# If the heater turns on externally while AUTO already intended it ON,
|
||||||
lambda: 'return !id(test_mode_enabled).state && (id(mode_select).state == "AUTO") && id(power_intended_on) && (!id(self_power_on));'
|
# re-baseline after warmup and continue normal automatic control.
|
||||||
then:
|
- conditions:
|
||||||
|
- lambda: 'return !id(test_mode_enabled).state && id(mode_select).state == "AUTO" && id(power_intended_on) && !id(self_power_on);'
|
||||||
|
sequence:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(ramp_until_ms) = millis() + (${fast_ramp_seconds} * 1000UL);
|
id(ramp_until_ms) = millis() + (${fast_ramp_seconds} * 1000UL);
|
||||||
- delay: ${warmup_seconds}s
|
- delay: ${warmup_seconds}s
|
||||||
@@ -493,7 +516,8 @@ switch:
|
|||||||
|
|
||||||
# High-level thermostat enable helper.
|
# High-level thermostat enable helper.
|
||||||
# ON = thermostat logic active in AUTO
|
# 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
|
- platform: template
|
||||||
id: heater_operation_switch
|
id: heater_operation_switch
|
||||||
name: "Heater Operation"
|
name: "Heater Operation"
|
||||||
@@ -514,10 +538,9 @@ switch:
|
|||||||
turn_off_action:
|
turn_off_action:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(power_intended_on) = false;
|
id(power_intended_on) = false;
|
||||||
- switch.turn_off: power_switch
|
|
||||||
- select.set:
|
- select.set:
|
||||||
id: mode_select
|
id: mode_select
|
||||||
option: "BYPASS (Remote Only)"
|
option: "OFF"
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
id: power_switch
|
id: power_switch
|
||||||
@@ -650,31 +673,42 @@ text_sensor:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (use_test_temp) {
|
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 (mode == "MANUAL") {
|
||||||
if (id(powered_state).state) {
|
if (id(powered_state).state) {
|
||||||
char buf[48];
|
char buf[48];
|
||||||
snprintf(buf, sizeof(buf), "Manual (Level %d) (Test)", id(current_level));
|
snprintf(buf, sizeof(buf), "Manual (Level %d) (Test)", id(current_level));
|
||||||
return std::string(buf);
|
return std::string(buf);
|
||||||
}
|
}
|
||||||
if (id(led_standby_red).state) return std::string("Manual (Standby) (Test)");
|
|
||||||
return std::string("Manual (Off) (Test)");
|
return std::string("Manual (Off) (Test)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id(powered_state).state) {
|
if (id(powered_state).state) {
|
||||||
char buf[40];
|
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);
|
return std::string(buf);
|
||||||
}
|
}
|
||||||
if (mode == "AUTO" && !isnan(effective_t) && effective_t >= at_temp_threshold) {
|
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 (mode == "BYPASS (Remote Only)") {
|
||||||
if (id(led_operating_blue).state) return std::string("Remote Only (Operating)");
|
if (id(led_operating_blue).state) return std::string("Bypass (Operating)");
|
||||||
if (id(led_standby_red).state) return std::string("Remote Only (Standby)");
|
if (id(led_standby_red).state) return std::string("Bypass (Standby)");
|
||||||
return std::string("Remote Only (Off)");
|
return std::string("Bypass (Remote Only)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == "MANUAL") {
|
if (mode == "MANUAL") {
|
||||||
@@ -689,14 +723,14 @@ text_sensor:
|
|||||||
|
|
||||||
if (id(led_operating_blue).state) {
|
if (id(led_operating_blue).state) {
|
||||||
char buf[32];
|
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);
|
return std::string(buf);
|
||||||
}
|
}
|
||||||
if (!isnan(effective_t) && effective_t >= at_temp_threshold) {
|
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");
|
if (id(led_standby_red).state) return std::string("Auto (Standby)");
|
||||||
return std::string("Standby");
|
return std::string("Auto (Standby)");
|
||||||
on_value:
|
on_value:
|
||||||
- script.execute: publish_status
|
- script.execute: publish_status
|
||||||
|
|
||||||
@@ -728,18 +762,24 @@ select:
|
|||||||
- platform: template
|
- platform: template
|
||||||
id: mode_select
|
id: mode_select
|
||||||
name: "Mode"
|
name: "Mode"
|
||||||
options: ["AUTO","MANUAL","BYPASS (Remote Only)"]
|
options: ["OFF","AUTO","MANUAL","BYPASS (Remote Only)"]
|
||||||
optimistic: true
|
optimistic: true
|
||||||
initial_option: "BYPASS (Remote Only)"
|
initial_option: "OFF"
|
||||||
restore_value: false
|
restore_value: false
|
||||||
set_action:
|
set_action:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
if (x == "BYPASS (Remote Only)") {
|
if (x == "OFF" || x == "BYPASS (Remote Only)") {
|
||||||
id(power_intended_on) = false;
|
id(power_intended_on) = false;
|
||||||
}
|
}
|
||||||
if (x != "AUTO") {
|
if (x != "AUTO") {
|
||||||
id(room_temp_lost_fault) = false;
|
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
|
- platform: template
|
||||||
id: flame_level_select
|
id: flame_level_select
|
||||||
@@ -932,13 +972,15 @@ interval:
|
|||||||
then:
|
then:
|
||||||
- script.execute: auto_control_tick
|
- 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.
|
# 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
|
- interval: ${power_off_check_interval_s}s
|
||||||
then:
|
then:
|
||||||
- if:
|
- if:
|
||||||
condition:
|
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:
|
then:
|
||||||
- script.execute: enforce_heater_off
|
- script.execute: enforce_heater_off
|
||||||
|
|
||||||
@@ -984,7 +1026,29 @@ script:
|
|||||||
|
|
||||||
id(powered_state).publish_state(new_state);
|
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
|
- id: boot_force_heater_off
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
@@ -1003,12 +1067,13 @@ script:
|
|||||||
- script.execute: recompute_powered_state
|
- script.execute: recompute_powered_state
|
||||||
- select.set:
|
- select.set:
|
||||||
id: mode_select
|
id: mode_select
|
||||||
option: "BYPASS (Remote Only)"
|
option: "OFF"
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(power_intended_on) = false;
|
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.
|
# 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
|
- id: enforce_heater_off
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
@@ -1016,7 +1081,7 @@ script:
|
|||||||
- script.execute: recompute_powered_state
|
- script.execute: recompute_powered_state
|
||||||
- if:
|
- if:
|
||||||
condition:
|
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:
|
then:
|
||||||
- logger.log: "Heater should be OFF but LEDs still show ON. Pulsing power to force OFF."
|
- logger.log: "Heater should be OFF but LEDs still show ON. Pulsing power to force OFF."
|
||||||
- script.execute: press_power
|
- script.execute: press_power
|
||||||
@@ -1125,7 +1190,7 @@ script:
|
|||||||
then:
|
then:
|
||||||
- if:
|
- if:
|
||||||
condition:
|
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:
|
then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(calibrating) = true;
|
id(calibrating) = true;
|
||||||
@@ -1245,6 +1310,11 @@ script:
|
|||||||
mode: queued
|
mode: queued
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- 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 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 t = use_test_temp ? id(test_temp_c) : id(room_temp).state;
|
||||||
const float sp = id(setpoint_c);
|
const float sp = id(setpoint_c);
|
||||||
|
|||||||
Reference in New Issue
Block a user