changes to esphome devices and packages to start removing node red dependencies
This commit is contained in:
+130
-33
@@ -6,6 +6,11 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-gasheater.yaml
|
||||
#:########################################################################################:#
|
||||
# VERSIONS:
|
||||
# V1.11 2026-04-21 Reboot now enters temporary FULL OFF hold before returning to BYPASS;
|
||||
# FULL OFF is remembered across reboots if it was already active
|
||||
# V1.10 2026-04-21 Added FULL OFF mode and reboot-safe shutdown to force heater OFF
|
||||
# after ESPHome restart; fixed stray setpoint typo
|
||||
# V1.8 2026-04-21 Physical testing, swapped GPIO12 and 14 (had the buttons/relays inverted)
|
||||
# V1.7 2026-03-26 Added Heater Operation switch; MQTT status now mirrors the Status Text
|
||||
# entity; preserved Test Mode AUTO test temperature and visible 6-step calibration
|
||||
# V1.6 2026-03-22 Added richer status text: At Temperature, Operating (Level X),
|
||||
@@ -27,9 +32,9 @@
|
||||
# - 3-relay daughterboard
|
||||
# - Rinnai Neo gas heater
|
||||
# - Relay outputs:
|
||||
# - GPIO12 = ON/OFF button press
|
||||
# - GPIO14 = ON/OFF button press
|
||||
# - GPIO13 = Flame UP button press
|
||||
# - GPIO14 = Flame DOWN button press
|
||||
# - GPIO12 = Flame DOWN button press
|
||||
# - LED inputs:
|
||||
# - GPIO4 = Red standby LED
|
||||
# - GPIO5 = Blue operating LED
|
||||
@@ -59,7 +64,12 @@
|
||||
# - Heater Operation is a high-level thermostat enable helper:
|
||||
# - ON = turn Test Mode OFF (if needed) and set Mode to AUTO
|
||||
# - OFF = turn heater Power OFF and set Mode to BYPASS (Remote Only)
|
||||
# - On normal ESPHome startup, Mode defaults to BYPASS (Remote Only).
|
||||
# - FULL OFF mode is a stronger safety state than BYPASS:
|
||||
# - If either heater LED shows the heater is still on, ESPHome will pulse the power
|
||||
# button after a short delay to force the heater OFF
|
||||
# - On normal ESPHome startup, the controller first enters FULL OFF for a short hold time
|
||||
# to verify the heater is really off, then returns to BYPASS (Remote Only).
|
||||
# - If FULL OFF was already active before reboot, FULL OFF is restored and remembered.
|
||||
# - On power ON in AUTO, the controller:
|
||||
# - Applies a warmup lockout
|
||||
# - Calibrates to level 1 using 6x DOWN
|
||||
@@ -109,7 +119,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Sonoff Technologies.Sonoff DEV"
|
||||
project_version: "v1.7"
|
||||
project_version: "v1.11"
|
||||
|
||||
# Passwords & Secrets
|
||||
api_key: !secret esp-api_key
|
||||
@@ -135,6 +145,9 @@ substitutions:
|
||||
temp_stale_timeout_min: "20"
|
||||
fast_ramp_seconds: "240"
|
||||
fast_ramp_step_s: "4"
|
||||
safe_power_off_delay_s: "2"
|
||||
full_off_check_interval_s: "5"
|
||||
boot_full_off_hold_s: "10"
|
||||
|
||||
# Optional anti short-cycle (set to "0" to disable either one)
|
||||
min_on_minutes: "1"
|
||||
@@ -213,20 +226,33 @@ esphome:
|
||||
|
||||
id(last_temp_ms) = millis();
|
||||
id(warmup_until_ms) = millis() + (${warmup_seconds} * 1000UL);
|
||||
- delay: ${warmup_seconds}s
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(powered_state).state;'
|
||||
lambda: 'return id(full_off_persist);'
|
||||
then:
|
||||
- logger.log: "Boot: FULL OFF was active before reboot; restoring FULL OFF."
|
||||
- select.set:
|
||||
id: mode_select
|
||||
option: "FULL OFF"
|
||||
- script.execute: enforce_full_off
|
||||
else:
|
||||
- logger.log: "Boot: entering temporary FULL OFF hold before BYPASS."
|
||||
- select.set:
|
||||
id: mode_select
|
||||
option: "FULL OFF"
|
||||
- script.execute: enforce_full_off
|
||||
- delay: ${boot_full_off_hold_s}s
|
||||
- script.execute: recompute_powered_state
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mode_select).state != "BYPASS (Remote Only)";'
|
||||
lambda: 'return !id(powered_state).state;'
|
||||
then:
|
||||
- logger.log: "Boot: heater already powered. Calibrating to level 1."
|
||||
- script.execute: calibrate_to_min
|
||||
- logger.log: "Boot: heater appears OFF after FULL OFF hold; switching to BYPASS."
|
||||
- select.set:
|
||||
id: mode_select
|
||||
option: "BYPASS (Remote Only)"
|
||||
else:
|
||||
- logger.log: "Boot: heater already powered. BYPASS (Remote Only) -> skipping calibration."
|
||||
|
||||
- logger.log: "Boot: heater still appears ON after FULL OFF hold; staying in FULL OFF."
|
||||
##########################################################################################
|
||||
# ESP PLATFORM
|
||||
##########################################################################################
|
||||
@@ -338,6 +364,13 @@ globals:
|
||||
restore_value: no
|
||||
initial_value: "false"
|
||||
|
||||
# Persist whether FULL OFF was the active mode before reboot.
|
||||
# If true, reboot returns to FULL OFF instead of temporary FULL OFF then BYPASS.
|
||||
- id: full_off_persist
|
||||
type: bool
|
||||
restore_value: yes
|
||||
initial_value: "false"
|
||||
|
||||
##########################################################################################
|
||||
# ESPHOME LOGGING ENABLE
|
||||
##########################################################################################
|
||||
@@ -363,6 +396,11 @@ binary_sensor:
|
||||
entity_category: diagnostic
|
||||
on_state:
|
||||
- script.execute: recompute_powered_state
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state == "FULL OFF" && (id(led_operating_blue).state || id(led_standby_red).state);'
|
||||
then:
|
||||
- script.execute: enforce_full_off
|
||||
|
||||
- platform: gpio
|
||||
id: led_operating_blue
|
||||
@@ -378,6 +416,11 @@ binary_sensor:
|
||||
entity_category: diagnostic
|
||||
on_state:
|
||||
- script.execute: recompute_powered_state
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state == "FULL OFF" && (id(led_operating_blue).state || id(led_standby_red).state);'
|
||||
then:
|
||||
- script.execute: enforce_full_off
|
||||
|
||||
- platform: template
|
||||
id: powered_state
|
||||
@@ -413,7 +456,7 @@ switch:
|
||||
internal: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
pin:
|
||||
number: GPIO12
|
||||
number: GPIO14
|
||||
inverted: ${relay_inverted}
|
||||
|
||||
- platform: gpio
|
||||
@@ -429,7 +472,7 @@ switch:
|
||||
internal: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
pin:
|
||||
number: GPIO14
|
||||
number: GPIO12
|
||||
inverted: ${relay_inverted}
|
||||
|
||||
- platform: template
|
||||
@@ -463,6 +506,11 @@ switch:
|
||||
return id(mode_select).state == "AUTO";
|
||||
|
||||
turn_on_action:
|
||||
- if:
|
||||
condition:
|
||||
switch.is_on: test_mode_enabled
|
||||
then:
|
||||
- switch.turn_off: test_mode_enabled
|
||||
- select.set:
|
||||
id: mode_select
|
||||
option: "AUTO"
|
||||
@@ -482,9 +530,9 @@ switch:
|
||||
turn_on_action:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mode_select).state == "BYPASS (Remote Only)" && !id(test_mode_enabled).state;'
|
||||
lambda: 'return (id(mode_select).state == "BYPASS (Remote Only)" || id(mode_select).state == "FULL OFF") && !id(test_mode_enabled).state;'
|
||||
then:
|
||||
- logger.log: "BYPASS (Remote Only): ignoring Power ON."
|
||||
- logger.log: "BYPASS/FULL OFF: ignoring Power ON."
|
||||
else:
|
||||
- if:
|
||||
condition:
|
||||
@@ -556,7 +604,8 @@ sensor:
|
||||
if (v > vmax) v = vmax;
|
||||
|
||||
id(sp_suppress_mqtt) = true;
|
||||
id(setpoint_c) = v;
|
||||
const float step = ${setpoint_step_c};
|
||||
id(setpoint_c) = roundf(v / step) * step;
|
||||
- number.set:
|
||||
id: setpoint_number
|
||||
value: !lambda 'return id(setpoint_c);'
|
||||
@@ -577,10 +626,6 @@ text_sensor:
|
||||
const float at_temp_threshold = id(setpoint_c) + ${off_above_c};
|
||||
const std::string mode = id(mode_select).state;
|
||||
|
||||
if (!use_test_temp && id(room_temp_lost_fault)) {
|
||||
return std::string("Fault (Room Temperature Lost)");
|
||||
}
|
||||
|
||||
if (id(calibrating)) {
|
||||
if (use_test_temp) return std::string("Calibrating (Test)");
|
||||
return std::string("Calibrating");
|
||||
@@ -608,12 +653,22 @@ text_sensor:
|
||||
return std::string("Off (Test)");
|
||||
}
|
||||
|
||||
if (mode == "FULL OFF") {
|
||||
if (id(led_operating_blue).state) return std::string("Full Off (Operating)");
|
||||
if (id(led_standby_red).state) return std::string("Full Off (Standby)");
|
||||
return std::string("Full Off (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 (!use_test_temp && id(room_temp_lost_fault)) {
|
||||
return std::string("Fault (Room Temperature Lost)");
|
||||
}
|
||||
|
||||
if (mode == "MANUAL") {
|
||||
if (id(led_operating_blue).state) {
|
||||
char buf[32];
|
||||
@@ -660,14 +715,28 @@ text_sensor:
|
||||
##########################################################################################
|
||||
# SELECT COMPONENT
|
||||
##########################################################################################
|
||||
# Mode meanings:
|
||||
# - AUTO = thermostat active
|
||||
# - MANUAL = manual flame control allowed
|
||||
# - BYPASS (Remote Only) = ESPHome control bypassed, heater may still be used externally
|
||||
# - FULL OFF = ESPHome actively forces the heater OFF if it detects either LED on
|
||||
select:
|
||||
- platform: template
|
||||
id: mode_select
|
||||
name: "Mode"
|
||||
options: ["AUTO","MANUAL","BYPASS (Remote Only)"]
|
||||
options: ["AUTO","MANUAL","BYPASS (Remote Only)","FULL OFF"]
|
||||
optimistic: true
|
||||
initial_option: "BYPASS (Remote Only)"
|
||||
restore_value: false
|
||||
set_action:
|
||||
- lambda: |-
|
||||
id(full_off_persist) = (x == "FULL OFF");
|
||||
if (x != "AUTO") id(room_temp_lost_fault) = false;
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return x == "FULL OFF";'
|
||||
then:
|
||||
- script.execute: enforce_full_off
|
||||
|
||||
- platform: template
|
||||
id: flame_level_select
|
||||
@@ -686,7 +755,7 @@ select:
|
||||
else:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mode_select).state != "BYPASS (Remote Only)";'
|
||||
lambda: 'return id(mode_select).state == "AUTO" || id(mode_select).state == "MANUAL";'
|
||||
then:
|
||||
- select.set:
|
||||
id: mode_select
|
||||
@@ -724,7 +793,7 @@ select:
|
||||
else:
|
||||
- script.execute: adjust_to_desired
|
||||
else:
|
||||
- logger.log: "BYPASS (Remote Only): ignoring Flame Level selection."
|
||||
- logger.log: "BYPASS/FULL OFF: ignoring Flame Level selection."
|
||||
|
||||
##########################################################################################
|
||||
# BUTTON COMPONENT
|
||||
@@ -736,14 +805,14 @@ button:
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mode_select).state != "BYPASS (Remote Only)";'
|
||||
lambda: 'return id(mode_select).state == "AUTO" || id(mode_select).state == "MANUAL";'
|
||||
then:
|
||||
- select.set:
|
||||
id: mode_select
|
||||
option: "MANUAL"
|
||||
- script.execute: step_up_once
|
||||
else:
|
||||
- logger.log: "BYPASS (Remote Only): ignoring Flame Up (step)."
|
||||
- logger.log: "BYPASS/FULL OFF: ignoring Flame Up (step)."
|
||||
|
||||
- platform: template
|
||||
id: btn_flame_down
|
||||
@@ -751,14 +820,14 @@ button:
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mode_select).state != "BYPASS (Remote Only)";'
|
||||
lambda: 'return id(mode_select).state == "AUTO" || id(mode_select).state == "MANUAL";'
|
||||
then:
|
||||
- select.set:
|
||||
id: mode_select
|
||||
option: "MANUAL"
|
||||
- script.execute: step_down_once
|
||||
else:
|
||||
- logger.log: "BYPASS (Remote Only): ignoring Flame Down (step)."
|
||||
- logger.log: "BYPASS/FULL OFF: ignoring Flame Down (step)."
|
||||
|
||||
- platform: template
|
||||
id: btn_calibrate_min
|
||||
@@ -777,6 +846,8 @@ button:
|
||||
float v = id(setpoint_c) + step;
|
||||
const float vmax = ${setpoint_max_c};
|
||||
if (v > vmax) v = vmax;
|
||||
// Snap to the nearest configured step to avoid float drift
|
||||
v = roundf(v / step) * step;
|
||||
id(setpoint_c) = v;
|
||||
- number.set:
|
||||
id: setpoint_number
|
||||
@@ -792,6 +863,8 @@ button:
|
||||
float v = id(setpoint_c) - step;
|
||||
const float vmin = ${setpoint_min_c};
|
||||
if (v < vmin) v = vmin;
|
||||
// Snap to the nearest configured step to avoid float drift
|
||||
v = roundf(v / step) * step;
|
||||
id(setpoint_c) = v;
|
||||
- number.set:
|
||||
id: setpoint_number
|
||||
@@ -814,7 +887,8 @@ number:
|
||||
return id(setpoint_c);
|
||||
set_action:
|
||||
- lambda: |-
|
||||
id(setpoint_c) = x;
|
||||
const float step = ${setpoint_step_c};
|
||||
id(setpoint_c) = roundf(x / step) * step;
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
@@ -856,6 +930,15 @@ interval:
|
||||
then:
|
||||
- script.execute: auto_control_tick
|
||||
|
||||
# In FULL OFF, keep checking that the heater is really off.
|
||||
- interval: ${full_off_check_interval_s}s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state == "FULL OFF" && id(powered_state).state;'
|
||||
then:
|
||||
- script.execute: enforce_full_off
|
||||
|
||||
##########################################################################################
|
||||
# SCRIPT COMPONENT
|
||||
##########################################################################################
|
||||
@@ -883,6 +966,20 @@ script:
|
||||
id(powered_state).publish_state(new_state);
|
||||
- script.execute: publish_status
|
||||
|
||||
# FULL OFF enforcement helper.
|
||||
# After a short delay, if the heater still looks on, pulse the power button once.
|
||||
- id: enforce_full_off
|
||||
mode: restart
|
||||
then:
|
||||
- delay: ${safe_power_off_delay_s}s
|
||||
- script.execute: recompute_powered_state
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(test_mode_enabled).state && id(mode_select).state == "FULL OFF" && id(powered_state).state;'
|
||||
then:
|
||||
- logger.log: "FULL OFF: heater still appears ON, pulsing power to force OFF."
|
||||
- script.execute: press_power
|
||||
|
||||
# Low-level relay click helper.
|
||||
# Handles the button press pulse and enforces a minimum gap between clicks.
|
||||
- id: press_with_gap
|
||||
@@ -923,25 +1020,25 @@ script:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mode_select).state != "BYPASS (Remote Only)";'
|
||||
lambda: 'return id(mode_select).state == "AUTO" || id(mode_select).state == "MANUAL";'
|
||||
then:
|
||||
- script.execute:
|
||||
id: press_with_gap
|
||||
which: "up"
|
||||
else:
|
||||
- logger.log: "BYPASS (Remote Only): ignoring UP press."
|
||||
- logger.log: "BYPASS/FULL OFF: ignoring UP press."
|
||||
|
||||
- id: press_down
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mode_select).state != "BYPASS (Remote Only)";'
|
||||
lambda: 'return id(mode_select).state == "AUTO" || id(mode_select).state == "MANUAL";'
|
||||
then:
|
||||
- script.execute:
|
||||
id: press_with_gap
|
||||
which: "down"
|
||||
else:
|
||||
- logger.log: "BYPASS (Remote Only): ignoring DOWN press."
|
||||
- logger.log: "BYPASS/FULL OFF: ignoring DOWN press."
|
||||
|
||||
# Step one flame level UP and only update the visible level after the relay click finishes.
|
||||
- id: step_up_once
|
||||
@@ -1058,7 +1155,7 @@ script:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mode_select).state != "BYPASS (Remote Only)";'
|
||||
lambda: 'return id(mode_select).state == "AUTO" || id(mode_select).state == "MANUAL";'
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
|
||||
Reference in New Issue
Block a user