Files
zorruno-homeassistant/esphome/esp-poollightpower.yaml
2025-08-18 17:07:49 +12:00

706 lines
27 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##########################################################################################
##########################################################################################
# POOL LIGHT POWER AND TIMER
# Controlled by a Athom Smart Plug V1
#
# dashboard_import:
# package_import_url: github://athom-tech/esp32-configs/athom-smart-plug.yaml
#
# V2.4 2025-06-15 Changed back to an Athom V1 (esp8266)
# V2.3 2025-06-15 Changed to an Athom V3 (esp32)
# V2.2 2025-06-14 Fixes to offline time when sntp/network unavailable
# V2.1 2025-06-12 Added select and button to chose modes, added countdown & startup to boost
# V2.0 2025-06-05 YAML Tidyups
#
# INSTRUCTIONS
# - It allows the device to work in a standalone timer style operation
# - The timer has a morning and evening time (but no weekday/weekend settings)
# - Default values are set, but changed values are remembered in flash
# - It uses SNTP for time setting (but obviously only if wifi & networking are working)
# - It will default to an internal timer if no wifi. To reset internal timer, reboot the device at 12pm (noon)
# - If on a network and there is a MQTT server, you can set the on/off times via MQTT (See below commands)
# - You can set 4 modes ON/OFF/TIMER/BOOST via MQTT. Setting BOOST gives you a oneshot operation
# - Any new timer times set via MQTT will be remembered though a reboot
# - On startup, or a reboot, the device will always turn on for the BOOST Duration (BOOST mode, default 2 hours)
# - TIMER mode will always be switched on after BOOST mode is complete
# - Home Assistant entities are set so that BOOST mode can be pressed with a button and other modes selectable with a dropdown
# - If you need it ON continuously with no MQTT, toggle power ON/OFF 4 times within 30 seconds (with ~2 secs in between to allow it to boot)
#
# MQTT Commands
# Values will be set in place on the update_interval time, not immediately
# Use 00:00 in 24hr format for time setting. (Note there is no weekday/weekend setting)
# mqtt_timer_topic/morning-on/06:00 : Time device will go on
# mqtt_timer_topic/morning-off/08:00 : Time device will go off
# mqtt_timer_topic/evening-on/09:00 : Time device will go on
# mqtt_timer_topic/evening-off/00:00 : Time device will go off
# mqtt_timer_topic/boost-time/0000 : Time in minutes device will temporarily go on for (1-1439)
# mqtt_timer_topic/operation/ON : Device permanently on
# mqtt_timer_topic/operation/OFF : Device permanently off
# mqtt_timer_topic/operation/TIMER : Device will obey timer settings
# mqtt_timer_topic/operation/BOOST : Turn on for (boost_duration) minutes then BOOST (also on startup)
#
# operation_mode:
# 0 = OFF
# 1 = ON
# 2 = TIMER
# 3 = BOOST
#
##########################################################################################
##########################################################################################
##########################################################################################
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
##########################################################################################
substitutions:
# Device Naming
device_name: "esp-poollightpower"
friendly_name: "Pool Light Power"
description_comment: "Pool Light Power :: Athom Smart Plug Power V1"
device_area: "Outside" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
# Project Naming
project_name: "Athom Technology.Smart Plug V1" # Project Details
project_version: "v2.4" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
# Passwords
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
static_ip_address: !secret esp-poollightpower_ip
# Device Settings
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
update_interval: "10s" # update time for for general sensors etc
# Device Settings
relay_icon: "mdi:power-socket-au"
current_limit : "10" # Current Limit in Amps. AU Plug = 10. IL, BR, EU, UK, US Plug = 16.
mqtt_timer_topic: "viewroad-commands/poollight-timer" # Topics you will use to change stuff
boost_duration_default: "180" # Minutes to stay ON in BOOST mode before reverting to TIMER
morning_on_default: "450" # Default in minutes from midnight. Default 07:30 => 450
morning_off_default: "450" # Default in minutes from midnight. Default 07:30 => 450 (same as ON as no need for morning schedule)
evening_on_default: "1140" # Default in minutes from midnight. Default 19:00 => 1140
evening_off_default: "1350" # Default in minutes from midnight. Default 22:30 => 1350 => 1440 is midnight
##########################################################################################
# PACKAGES: Included Common Packages
# https://esphome.io/components/packages.html
##########################################################################################
packages:
common_wifi: !include
file: common/network_common.yaml
vars:
local_device_name: "${device_name}"
local_static_ip_address: "${static_ip_address}"
local_ota_pass: "${ota_pass}"
common_api: !include
file: common/api_common.yaml
vars:
local_api_key: "${api_key}"
#common_webportal: !include
# file: common/webportal_common.yaml
common_mqtt: !include
file: common/mqtt_common.yaml
vars:
local_device_name: "${device_name}"
common_sntp: !include
file: common/sntp_common.yaml
common_general_sensors: !include
file: common/sensors_common.yaml
vars:
local_friendly_name: "${friendly_name}"
local_update_interval: "${update_interval}"
# Device Specific included packages
common_athompowermonV1: !include
file: common/athompowermonv1_common.yaml
vars:
local_friendly_name: "${friendly_name}"
local_current_limit: "${current_limit}"
##########################################################################################
# ESPHome
# https://esphome.io/components/esphome.html
##########################################################################################
esphome:
name: "${device_name}"
friendly_name: "${friendly_name}"
comment: "${description_comment}" #Appears on the esphome page in HA
area: "${device_area}"
name_add_mac_suffix: False
min_version: 2024.6.0
project:
name: "${project_name}"
version: "${project_version}"
#platformio_options:
# build_unflags:
# - -std=gnu++20
# - -std=gnu++2a
# build_flags:
# - -std=gnu++11
# - -Os
# - -Wl,--gc-sections
# - -fno-exceptions
# - -fno-rtti
##########################################################################################
# ESP Platform and Framework
# https://esphome.io/components/esp32.html
##########################################################################################
esp8266:
board: esp8285
restore_from_flash: true # mainly for calculating cumulative energy, but not that important here
#framework:
# version: 2.7.4
preferences:
flash_write_interval: 10min
mdns:
disabled: True # binary size saving
##########################################################################################
# ESPHome Logging Enable
# https://esphome.io/components/logger.html
##########################################################################################
logger:
level: "${log_level}" #INFO Level suggested, or DEBUG for testing
baud_rate: 0 #set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM)
#esp8266_store_log_strings_in_flash: false
#tx_buffer_size: 64
##########################################################################################
# Global Variables for use in automations etc
# https://esphome.io/guides/automations.html?highlight=globals#global-variables
##########################################################################################
globals:
# Tracks the time (in seconds from midnight) at the previous boot
- id: last_boot_time_s
type: int
restore_value: true
initial_value: "0"
# Counts how many consecutive boots have occurred (within X seconds)
- id: boot_count
type: int
restore_value: true
initial_value: "0"
# Morning On time (minutes from midnight),
- id: morning_on
type: int
restore_value: true
initial_value: "${morning_on_default}"
# Morning Off time (minutes from midnight),
- id: morning_off
type: int
restore_value: true
initial_value: "${morning_off_default}"
# Evening On time (minutes from midnight),
- id: evening_on
type: int
restore_value: true
initial_value: "${evening_on_default}"
# Evening Off time (minutes from midnight),
- id: evening_off
type: int
restore_value: true
initial_value: "${evening_off_default}"
# Boost Duration (minutes),
- id: boost_duration
type: int
restore_value: true
initial_value: "${boost_duration_default}"
####################################################
# operation_mode:
# 0 = OFF
# 1 = ON
# 2 = TIMER
# 3 = BOOST
####################################################
- id: operation_mode
type: int
restore_value: true
initial_value: "2"
####################################################
# current_mins is set if SNTP is invalid.
# We assume user powers on the device at 12:00 noon
# => 12 * 60 = 720 minutes from midnight.
####################################################
- id: current_mins
type: int
restore_value: true
initial_value: "720" # 720 is 12:00 Noon
####################################################
# boost_timer: counts minutes in BOOST mode
# After 'boost_duration' minutes, revert to TIMER.
####################################################
- id: boost_timer
type: int
restore_value: true
initial_value: "0"
##########################################################################################
# Text Sensors
# https://esphome.io/components/text_sensor/index.html
##########################################################################################
text_sensor:
############################
# MQTT Subscriptions
############################
####################################################
# Subscribe to the Morning On time, format "HH:MM"
# We check x.size() == 5 and x[2] == ':',
# then parse x.substr(0,2) and x.substr(3,2)
# std::string uses 'substr', not 'substring'.
####################################################
- platform: mqtt_subscribe
name: "Morning On Time Setting"
id: morning_on_topic
topic: "${mqtt_timer_topic}/morning-on" # Stored in the format HH:MM
internal: True
on_value:
then:
- lambda: |-
// Expect "HH:MM" => total length = 5, with ':'
if (x.size() == 5 && x[2] == ':') {
int hour = atoi(x.substr(0, 2).c_str()); // "HH"
int minute = atoi(x.substr(3, 2).c_str()); // "MM"
id(morning_on) = hour * 60 + minute;
ESP_LOGI("timer","Received new Morning On: %02d:%02d", hour, minute);
} else {
ESP_LOGW("timer","Invalid Morning On format: %s", x.c_str());
}
####################################################
# Morning Off time => "HH:MM"
####################################################
- platform: mqtt_subscribe
name: "Morning Off Time Setting"
id: morning_off_topic
topic: "${mqtt_timer_topic}/morning-off" # Stored in the format HH:MM
internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value
on_value:
then:
- lambda: |-
if (x.size() == 5 && x[2] == ':') {
int hour = atoi(x.substr(0, 2).c_str());
int minute = atoi(x.substr(3, 2).c_str());
id(morning_off) = hour * 60 + minute;
ESP_LOGI("timer","Received new Morning Off: %02d:%02d", hour, minute);
} else {
ESP_LOGW("timer","Invalid Morning Off format: %s", x.c_str());
}
####################################################
# Evening On time => "HH:MM"
####################################################
- platform: mqtt_subscribe
name: "Evening On Time Setting"
id: evening_on_topic
topic: "${mqtt_timer_topic}/evening-on" # Stored in the format HH:MM
internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value
on_value:
then:
- lambda: |-
if (x.size() == 5 && x[2] == ':') {
int hour = atoi(x.substr(0, 2).c_str());
int minute = atoi(x.substr(3, 2).c_str());
id(evening_on) = hour * 60 + minute;
ESP_LOGI("timer","Received new Evening On: %02d:%02d", hour, minute);
} else {
ESP_LOGW("timer","Invalid Evening On format: %s", x.c_str());
}
####################################################
# Evening Off time => "HH:MM"
####################################################
- platform: mqtt_subscribe
name: "Evening Off Time Setting"
id: evening_off_topic
topic: "${mqtt_timer_topic}/evening-off" # Stored in the format HH:MM
internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value
on_value:
then:
- lambda: |-
if (x.size() == 5 && x[2] == ':') {
int hour = atoi(x.substr(0, 2).c_str());
int minute = atoi(x.substr(3, 2).c_str());
id(evening_off) = hour * 60 + minute;
ESP_LOGI("timer","Received new Evening Off: %02d:%02d", hour, minute);
} else {
ESP_LOGW("timer","Invalid Evening Off format: %s", x.c_str());
}
####################################################
# Boost duration => 1 - 1439
####################################################
- platform: mqtt_subscribe
name: "Boost Duration"
id: boost_time_topic
topic: "${mqtt_timer_topic}/boost-time" # Stored as an integer from 1-1439
internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value
on_value:
then:
- lambda: |-
// parse as integer
char *endptr;
long v = strtol(x.c_str(), &endptr, 10);
// invalid if nothing parsed, trailing chars, or out of 01439
if (endptr == x.c_str() || *endptr != '\0' || v < 0 || v > 1439) {
ESP_LOGE("boost_time", "Invalid boost_time '%s'", x.c_str());
} else {
id(boost_duration) = static_cast<int>(v);
}
####################################################
# Subscribe to operation mode:
# OFF, ON, TIMER, BOOST
# We do case-insensitive compare using strcasecmp
# (Requires <strings.h> typically included in ESPHome)
####################################################
# MQTT subscription: set mode, then immediately re-evaluate relay
- platform: mqtt_subscribe
id: timer_operation_mode_topic
topic: "${mqtt_timer_topic}/operation"
internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value
on_value:
then:
- lambda: |-
if (strcasecmp(x.c_str(), "TIMER") == 0) {
id(operation_mode) = 2;
ESP_LOGI("timer","Operation mode set to TIMER");
} else if (strcasecmp(x.c_str(), "ON") == 0) {
id(operation_mode) = 1;
ESP_LOGI("timer","Operation mode set to ON");
} else if (strcasecmp(x.c_str(), "OFF") == 0) {
id(operation_mode) = 0;
ESP_LOGI("timer","Operation mode set to OFF");
} else if (strcasecmp(x.c_str(), "BOOST") == 0) {
id(operation_mode) = 3;
id(boost_timer) = 0;
ESP_LOGI("timer","Operation mode set to BOOST");
} else {
ESP_LOGW("timer","Invalid operation mode: %s", x.c_str());
}
- script.execute: evaluate_relay_state
######################################################
# Expose the current operation mode (OFF, ON, TIMER, BOOST)
######################################################
- platform: template
name: "Operation Mode State"
lambda: |-
// 0=OFF, 1=ON, 2=TIMER, 3=BOOST
switch (id(operation_mode)) {
case 0: return {"OFF"};
case 1: return {"ON"};
case 2: return {"TIMER"};
case 3: return {"BOOST"};
default: return {"UNKNOWN"};
}
update_interval: 5s
######################################################
# Expose the "Morning On" time as a text (HH:MM)
######################################################
- platform: template
name: "Timeclock: Morning On Time"
lambda: |-
int hour = id(morning_on) / 60;
int minute = id(morning_on) % 60;
// Increase to 16 for safety
char buff[16];
snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute);
return { std::string(buff) };
update_interval: "${update_interval}"
######################################################
# Expose the "Morning Off" time as a text (HH:MM)
######################################################
- platform: template
name: "Timeclock: Morning Off Time"
lambda: |-
int hour = id(morning_off) / 60;
int minute = id(morning_off) % 60;
// Increase buffer size to 8 just to be safe
// Increase to 16 for safety
char buff[16];
snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute);
return { std::string(buff) };
update_interval: "${update_interval}"
######################################################
# Expose the "Evening On" time as a text (HH:MM)
######################################################
- platform: template
name: "Timeclock: Evening On Time"
lambda: |-
int hour = id(evening_on) / 60;
int minute = id(evening_on) % 60;
// Increase buffer size to 8 just to be safe
// Increase to 16 for safety
char buff[16];
snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute);
return { std::string(buff) };
update_interval: "${update_interval}"
######################################################
# Expose the "Evening Off" time as a text (HH:MM)
######################################################
- platform: template
name: "Timeclock: Evening Off Time"
lambda: |-
int hour = id(evening_off) / 60;
int minute = id(evening_off) % 60;
// Increase buffer size to 8 just to be safe
// Increase to 16 for safety
char buff[16];
snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute);
return { std::string(buff) };
update_interval: "${update_interval}"
##########################################################################################
# BINARY SENSORS
# https://esphome.io/components/binary_sensor/
##########################################################################################
binary_sensor:
- platform: gpio
pin:
number: GPIO03
mode: INPUT_PULLUP
inverted: true
name: "Power Button"
id: power_button
filters:
- delayed_on: 20ms
on_click:
- min_length: 20ms
max_length: 500ms
then:
- lambda: |-
if (id(relay).state) {
// Relay is ON: turn it OFF and set mode to 0 (TIMER)
id(relay).turn_off();
id(operation_mode) = 2;
} else {
// Relay is OFF: turn it ON and set mode to 3 (BOOST)
id(relay).turn_on();
id(operation_mode) = 3;
}
- platform: template
name: "Relay Status"
lambda: |-
return id(relay).state;
##########################################################################################
# Sensors
# https://esphome.io/components/text_sensor/index.html
##########################################################################################
sensor:
- platform: template
name: "Timeclock: Boost Duration"
id: boost_duration_time
unit_of_measurement: "mins"
accuracy_decimals: "0"
update_interval: "${update_interval}"
lambda: |-
return id(boost_duration);
- platform: template
name: "Mins from Midnight"
id: mins_from_midnight
unit_of_measurement: "mins"
accuracy_decimals: "0"
update_interval: "${update_interval}"
internal: True # No need to show this in Home Assistant
lambda: |-
return id(current_mins);
# A value in mins if a timer is running showing how many mins left
- platform: template
name: "Timer Minutes Remaining"
id: timer_minutes_remaining
unit_of_measurement: "Mins"
update_interval: 5s
accuracy_decimals: "0"
lambda: |-
// always zero if relay is off
if (!id(relay).state) {
return 0;
}
int rem = 0;
// only calculate for mode 2 (scheduled) or mode 3 (BOOST)
if (id(operation_mode) == 2) {
int a = id(morning_off) - id(current_mins);
int b = id(evening_off) - id(current_mins);
// if a is negative, use b; otherwise pick the smaller of a or b
rem = (a < 0) ? b : (a < b ? a : b);
}
else if (id(operation_mode) == 3) {
rem = id(boost_duration) - id(boost_timer);
}
// never return negative
return rem > 0 ? rem : 0;
#################################################################################################
# SWITCH COMPONENT
# https://esphome.io/components/switch/
#################################################################################################
switch:
- platform: gpio
name: "Power Output"
pin: GPIO14
id: relay
restore_mode: RESTORE_DEFAULT_OFF # Ensures the relay is restored (or off) at boot
#internal: true # Hides the switch from Home Assistant
icon: "${relay_icon}"
#################################################################################################
# BUTTON COMPONENT
# https://esphome.io/components/button/index.html
#################################################################################################
button:
- platform: template
name: "Boost now"
id: boost_button
icon: "mdi:play-circle-outline"
on_press:
# 1) reset BOOST timer and set mode
- lambda: |-
id(boost_timer) = 0;
id(operation_mode) = 3;
# 2) immediately re-evaluate relay state
- script.execute: evaluate_relay_state
- platform: template
name: "Default timer settings"
id: default_timer_settings_button
icon: "mdi:restore"
on_press:
- lambda: |-
// Restore all timing globals to their YAML defaults
id(morning_on) = ${morning_on_default};
id(morning_off) = ${morning_off_default};
id(evening_on) = ${evening_on_default};
id(evening_off) = ${evening_off_default};
id(boost_duration)= ${boost_duration_default};
// Reset mode to TIMER and clear any running boost
id(operation_mode)= 2;
id(boost_timer) = 0;
ESP_LOGI("timer","Default timer settings applied");
- script.execute: evaluate_relay_state
#################################################################################################
# SELECT COMPONENT
# https://esphome.io/components/select/index.html
#################################################################################################
select:
- platform: template
name: "Operation Mode"
id: operation_mode_select
update_interval: 5s
options:
- "OFF"
- "ON"
- "TIMER"
- "BOOST"
# show the current mode
lambda: |-
switch (id(operation_mode)) {
case 1: return std::string("ON");
case 2: return std::string("TIMER");
case 3: return std::string("BOOST");
default: return std::string("OFF");
}
# when changed in HA, set mode & re-evaluate
set_action:
- lambda: |-
if (x == "OFF") { id(operation_mode) = 0; }
else if (x == "ON") { id(operation_mode) = 1; }
else if (x == "TIMER") { id(operation_mode) = 2; }
else { // BOOST
id(boost_timer) = 0;
id(operation_mode) = 3;
}
- script.execute: evaluate_relay_state
#################################################################################################
# SCRIPT COMPONENT
# https://esphome.io/components/script.html
#################################################################################################
# Script: evaluate and drive the relay
script:
- id: evaluate_relay_state
then:
- lambda: |-
int mode = id(operation_mode);
// BOOST just forces the relay on
if (mode == 3) {
id(relay).turn_on();
return;
}
// OFF → always off
if (mode == 0) {
id(relay).turn_off();
return;
}
// ON → always on
if (mode == 1) {
id(relay).turn_on();
return;
}
// TIMER → follow schedule windows
{
bool should_on = false;
if (id(current_mins) >= id(morning_on) && id(current_mins) < id(morning_off))
should_on = true;
if (id(current_mins) >= id(evening_on) && id(current_mins) < id(evening_off))
should_on = true;
if (should_on) id(relay).turn_on();
else id(relay).turn_off();
}
#################################################################################################
# INTERVAL COMPONENT
# https://esphome.io/components/interval.html
#################################################################################################
# Interval: bumps time (even if no SNTP), then calls the script to evaluate relay state
interval:
- interval: "1min"
then:
- lambda: |-
// — update current_mins via SNTP or fallback
if (!id(sntp_time).now().is_valid()) {
id(current_mins)++;
if (id(current_mins) >= 1440) id(current_mins) = 0;
} else {
auto now = id(sntp_time).now();
id(current_mins) = now.hour * 60 + now.minute;
}
// — if in BOOST, advance boost_timer and expire when done
if (id(operation_mode) == 3) {
id(boost_timer)++;
if (id(boost_timer) >= id(boost_duration)) {
id(operation_mode) = 2;
//id(mqtt_client).publish("${mqtt_timer_topic}/operation", "TIMER");
}
}
- script.execute: evaluate_relay_state