diff --git a/automations.yaml b/automations.yaml index ef51dee..082d3e0 100644 --- a/automations.yaml +++ b/automations.yaml @@ -4,7 +4,7 @@ triggers: - type: turned_on device_id: 1a7e2bfdfaa08e2b78bcbd5d5d12d2a7 - entity_id: afb10b1efeb20fdf7215636d438882a2 + entity_id: ef02e93de5fdda30bea18dac1316927a domain: switch trigger: device conditions: [] @@ -22,7 +22,7 @@ triggers: - type: turned_off device_id: 1a7e2bfdfaa08e2b78bcbd5d5d12d2a7 - entity_id: afb10b1efeb20fdf7215636d438882a2 + entity_id: ef02e93de5fdda30bea18dac1316927a domain: switch trigger: device conditions: [] diff --git a/esphome/archive/esp-downstbathtowelrail copy.yaml.old b/esphome/archive/esp-downstbathtowelrail copy.yaml.old new file mode 100644 index 0000000..7c0e928 --- /dev/null +++ b/esphome/archive/esp-downstbathtowelrail copy.yaml.old @@ -0,0 +1,656 @@ +########################################################################################## +########################################################################################## +# DOWNSTAIRS BATHROOM HEATED TOWEL RAIL +# Controlled by a Sonoff Basic +# +# V2.0 2025-06-11 Added select and button to chose modes, added countdown & startup to boost +# V2.0 2025-06-05 YAML Tidyups +# V1.1 2025-04-12 Fixes to timers and offline modes +# V1.0 2025-02-14 Initial Version +# +# INSTRUCTIONS +# - It allows a heated towel rail device to work in a standalone operation +# - On startup, it will turn on for (startup_duration) hours then go into timer mode (this allows you to just turn it on to get some heat immediately) +# - The timer has a morning and evening time (but no weekday/weekend setting) +# - Default values are 5am-7am and 9pm-Midnight (as this suits our use case) +# - 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 4 on/off times via MQTT (See below commands) +# - You can set 4 modes ON/OFF/TIMER/STARTUP via MQTT. That way, you can set to STARTUP for a short boost +# - Any new timer times set via MQTT will be remembered though a reboot +# - On a reboot, the device will always turn on for the Startup Duration (STARTUP mode, default 2 hours) +# - TIMER mode will always be switched on after startup mode is complete +# - 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 +# 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 (startup_duration) hours then BOOST (also on startup) +# +########################################################################################## +########################################################################################## + +########################################################################################## +# 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-downstbathtowelrail" + friendly_name: "Downstairs Bathroom Towelrail" + description_comment: "Sonoff Basic controlling ON/OFF/Timer for the Heated Towel Rail in the Downstairs Bathroom" + device_area: "Downstairs Bathroom" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Project Naming + project_name: "Sonoff Technologies.Sonoff Basic V1" # Project Details + project_version: "v2.0" # 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-downstbathtowelrail_ip + + # Device Settings + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + + # Timer Settings + mqtt_timer_topic: "viewroad-commands/downstbath-towelrail" # Topics you will use to change stuff + startup_duration: "120" # Minutes to stay ON in STARTUP mode before reverting to TIMER + morning_on_default: "420" # Default in minutes from midnight. Default 07:00 => 420 + morning_off_default: "450" # Default in minutes from midnight. Default 07:30 => 450 + evening_on_default: "1260" # Default in minutes from midnight. Default 21:00 => 1260 + evening_off_default: "1320" # Default in minutes from midnight. Default 22:00 => 1320 => 1440 is midnight + +############################################# +# Included Common Packages +# https://esphome.io/components/esphome.html +############################################# +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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 + 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}" + +############################################# +# 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}" + on_boot: + priority: 900 # High priority to run after globals are initialized + then: + - lambda: |- + // 1) Figure out the current time in "seconds from midnight" + // using SNTP if available, otherwise current_mins * 60. + bool have_sntp = id(sntp_time).now().is_valid(); + int current_time_s = 0; + + if (have_sntp) { + auto now = id(sntp_time).now(); + current_time_s = now.hour * 3600 + now.minute * 60 + now.second; + } else { + // current_mins is in minutes; convert to seconds + current_time_s = id(current_mins) * 60; + } + + // 2) Compare with the last boot time + int diff = current_time_s - id(last_boot_time_s); + + // If within 30 seconds, increment boot_count; otherwise reset to 1 + if (diff >= 0 && diff <= 30) { + id(boot_count)++; + } else { + id(boot_count) = 1; + } + + // Update stored last boot time + id(last_boot_time_s) = current_time_s; + + // 3) If we've booted 4+ times in 20s => force ON mode + if (id(boot_count) >= 4) { + id(operation_mode) = 1; // ON + ESP_LOGI("power_cycle", "Detected 4 power cycles in 20s => Forcing ON mode"); + } else { + // Otherwise do your normal startup logic: + id(operation_mode) = 3; // on_boot -> sets operation_mode = 3 (STARTUP) + id(startup_timer) = 0; // and reset startup_timer = 0 (for time sync if no sntp) + ESP_LOGI("power_cycle", "Boot count=%d => STARTUP mode", id(boot_count)); + } + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m # The original sonoff basic + restore_from_flash: true # restore some values on reboot + +preferences: + flash_write_interval: 5min + +mdns: + disabled: false + +############################################# +# 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: "${morning_off_default}" + + # Evening Off time (minutes from midnight), + - id: evening_off + type: int + restore_value: true + initial_value: "${morning_off_default}" + + #################################################### + # operation_mode: + # 0 = OFF + # 1 = ON + # 2 = TIMER + # 3 = STARTUP + #################################################### + - id: operation_mode + type: int + restore_value: false + initial_value: "3" + + #################################################### + # 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. + # Not restored, so it resets each boot. + #################################################### + - id: current_mins + type: int + restore_value: false + initial_value: "720" # 720 is 12:00 Noon + + #################################################### + # startup_timer: counts minutes in STARTUP mode + # After 'startup_duration' minutes, revert to TIMER. + # Not restored, so each boot starts fresh at 0. + #################################################### + - id: startup_timer + type: int + restore_value: false + 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()); + } + #################################################### + # Subscribe to operation mode: + # OFF, ON, TIMER, STARTUP + # We do case-insensitive compare using strcasecmp + # (Requires typically included in ESPHome) + #################################################### + - platform: mqtt_subscribe + name: "Operation Mode Setting" + id: timer_operation_mode_topic + topic: "${mqtt_timer_topic}/operation" # STARTUP,ON,OFF,TIMER + internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value + on_value: + then: + - lambda: |- + /* + * In standard C++ (ESPHome), no 'equalsIgnoreCase()'. + * We use 'strcasecmp' for case-insensitive compare. + * Returns 0 if they match ignoring case. + */ + 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(), "STARTUP") == 0) { + id(operation_mode) = 3; + id(startup_timer) = 0; // Reset the startup timer to zero + ESP_LOGI("timer","Operation mode set to STARTUP"); + } else { + ESP_LOGW("timer","Invalid operation mode: %s", x.c_str()); + } + + ###################################################### + # Expose the current operation mode (OFF, ON, TIMER, STARTUP) + ###################################################### + - platform: template + name: "Operation Mode State" + lambda: |- + // 0=OFF, 1=ON, 2=TIMER, 3=STARTUP + switch (id(operation_mode)) { + case 0: return {"OFF"}; + case 1: return {"ON"}; + case 2: return {"TIMER"}; + case 3: return {"STARTUP"}; + 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}" + +############################################# +# Sensors +# https://esphome.io/components/text_sensor/index.html +############################################# +sensor: + - platform: template + name: "Mins from Midnight" + unit_of_measurement: "mins" + accuracy_decimals: 0 + update_interval: "${update_interval}" + internal: True + 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 (startup) + 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 = ${startup_duration} - id(startup_timer); + } + // never return negative + return rem > 0 ? rem : 0; + +#################################################### +# Relay Switch (Sonoff Basic Relay on GPIO12) +#################################################### +switch: + - platform: gpio + name: "Towel Rail Power" + pin: GPIO12 + id: relay + restore_mode: RESTORE_DEFAULT_OFF + +################################################################################################# +# BUTTON COMPONENT +# https://esphome.io/components/button/index.html +################################################################################################# +button: + - platform: template + name: "Startup: ${startup_duration} Minutes" + id: startup_button + icon: "mdi:play-circle-outline" # optional, pick any MaterialDesign icon you like + on_press: + then: + # 1) set the mode to STARTUP (3) + - lambda: |- + id(startup_timer) = 0; // Reset the startup timer to zero + id(operation_mode) = 3; // Set to STARTUP + ESP_LOGD("main", "operation_mode set to %d via STARTUP button", id(operation_mode)); + # 2) turn on the relay switch + - switch.turn_on: + id: relay + +################################################################################################# +# SELECT COMPONENT +# https://esphome.io/components/select/index.html +################################################################################################# +select: + - platform: template + name: "Operation Mode" + id: operation_mode_select + update_interval: 5s # poll every 5 s for external changes + options: + - "OFF" + - "ON" + - "TIMER" + - "STARTUP" + + # Getter: maps your integer into one of the four strings + lambda: |- + switch (id(operation_mode)) { + case 1: return std::string("ON"); + case 2: return std::string("TIMER"); + case 3: return std::string("STARTUP"); + default: return std::string("OFF"); + } + + # set_action: called when you pick an option in HA + 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 { + id(startup_timer) = 0; // Reset the startup timer to zero + id(operation_mode) = 3; /* STARTUP */ + } + ESP_LOGD("main", "operation_mode set to %d", id(operation_mode)); + +#################################################### +# Check every minute to decide relay state +#################################################### +interval: + - interval: "1min" # Must be 1min as this is used to calculate times + then: + - lambda: |- + + // Do we have correct time from SNTP? If not... + if (!id(time_sync).has_state()) { + // Set minutes since midnight + id(current_mins) = id(current_mins) +1 ; + + // wrap around at 1440 => next day + if (id(current_mins) >= 1440) { + id(current_mins) = 0; + } + + // If we do have proper SNMP time... + } else { + // Use real time from SNTP + auto now = id(sntp_time).now(); + id(current_mins) = now.hour * 60 + now.minute; + } + + // operation_mode: + // 0 = OFF + // 1 = ON + // 2 = TIMER + // 3 = STARTUP + int mode = id(operation_mode); + + ////////////////////////////////////////////////// + // STARTUP MODE: Relay ON for 'startup_duration' + // minutes, then automatically revert to TIMER. + ////////////////////////////////////////////////// + if (mode == 3) { + id(startup_timer) = id(startup_timer) + 1 ; // works as long as update_interval in seconds + // Compare with the substitution startup_duration + if (id(startup_timer) < (int) ${startup_duration}) { + // Still within the STARTUP period => turn relay on + id(relay).turn_on(); + } else { + // After 'startup_duration' minutes => switch to TIMER + id(operation_mode) = 2; + id(mqtt_client).publish("${mqtt_timer_topic}/operation", "TIMER"); + } + // Skip the rest of the logic + ESP_LOGI("startup_timer", "startup_timer=%d", id(startup_timer)); + return; + } + + ////////////////////////////////////////////////// + // OFF MODE => always off + ////////////////////////////////////////////////// + if (mode == 0) { + id(relay).turn_off(); + return; + } + + ////////////////////////////////////////////////// + // ON MODE => always on + ////////////////////////////////////////////////// + if (mode == 1) { + id(relay).turn_on(); + return; + } + + ////////////////////////////////////////////////// + // TIMER MODE => follow morning/evening schedule + // using SNTP if valid, else current_mins + ////////////////////////////////////////////////// + if (mode == 2) + { + + bool should_on = false; + + // Check morning window + // Example: morning_on=360 => 06:00, morning_off=480 => 08:00 + // If current_mins in [360..480), should_on = true + if (id(current_mins) >= id(morning_on) && id(current_mins) < id(morning_off) ) + { + should_on = true; + } + + // Check evening window + // Example: evening_on=1260 => 21:00, evening_off=1440 => midnight + if (id(current_mins) >= id(evening_on) && id(current_mins) < id(evening_off) ) + { + should_on = true; + } + + // Final relay state based on schedule + if (should_on) { + id(relay).turn_on(); + } else { + id(relay).turn_off(); + } + } + + + + diff --git a/esphome/common/esp-generic-startfile.yaml.trmp b/esphome/archive/esp-generic-startfile.yaml.trmp similarity index 100% rename from esphome/common/esp-generic-startfile.yaml.trmp rename to esphome/archive/esp-generic-startfile.yaml.trmp diff --git a/esphome/archive/esp-laundrylights copy.yaml.old b/esphome/archive/esp-laundrylights copy.yaml.old new file mode 100644 index 0000000..2eb374c --- /dev/null +++ b/esphome/archive/esp-laundrylights copy.yaml.old @@ -0,0 +1,154 @@ +############################################# +############################################# +# LAUNDRY MAIN LIGHTSWITCH +# V1.0 2025-05-30 Initial Version +############################################# +# Zemismart KS-811 Triple push button +# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/ +# +# NOTES +# - +# +############################################# +############################################# + +############################################# +# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +############################################# +substitutions: + device_name: "esp-laundrylights" + project_name: "Zemismart Technologies.KS-811 Triple" # Project Details + project_version: "v1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + entity_prefix: "Laundry" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current. + friendly_name: "Laundry Main Lightswitch (3)" + description_comment: "Laundry Main Lightswitch using a Zemismart KS-811 Triple Push Button. Laundry Lights (1), Spare (2), Laundry Power Signal (3)" + device_area: "Laundry" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + api_key: !secret esp-laundrylights_api_key # unfortunately you can't use substitutions inside secrets names + ota_pass: !secret esp-laundrylights_ota_pass # unfortunately you can't use substitutions inside secrets names + static_ip_address: !secret esp-laundrylights_ip + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + +############################################# +# Included Common Packages +# https://esphome.io/components/esphome.html +############################################# +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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 + 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} + +############################################# +# 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} + project: + name: "${project_name}" + version: "${project_version}" + on_boot: + priority: 200 + then: + - switch.turn_on: Relay_3 + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m + early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True. + board_flash_mode: dout # Default is dout + +############################################# +# 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 + +############################################# +# STATUS LED +# https://esphome.io/components/status_led.html +############################################# +status_led: + pin: + number: GPIO2 + inverted: yes + +############################################# +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +############################################# +binary_sensor: + - platform: gpio + pin: + number: GPIO16 + mode: INPUT + inverted: True + name: "Button 1: Light Switch (Laundry)" + on_press: + - switch.toggle: Relay_1 + + - platform: gpio + pin: + number: GPIO05 + mode: INPUT + inverted: True + name: "Button 2: Spare" + on_press: + - switch.toggle: Relay_2 + + - platform: gpio + pin: + number: GPIO4 + mode: INPUT + inverted: True + name: "Button 3: Laundry Power Enable" + on_press: + - switch.toggle: Relay_3 + +############################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +############################################# +switch: + - platform: gpio + name: "Relay 1: Laundry Lights" + pin: GPIO13 + id: Relay_1 + - platform: gpio + name: "Relay 2: Spare" + pin: GPIO12 + id: Relay_2 + - platform: gpio + name: "Relay 3: Laundry Power Enable Signal" + pin: GPIO14 + id: Relay_3 + + diff --git a/esphome/archive/esp-laundrylights copy.yaml.old2 b/esphome/archive/esp-laundrylights copy.yaml.old2 new file mode 100644 index 0000000..7202292 --- /dev/null +++ b/esphome/archive/esp-laundrylights copy.yaml.old2 @@ -0,0 +1,154 @@ +############################################# +############################################# +# LAUNDRY MAIN LIGHTSWITCH +# V1.0 2025-05-30 Initial Version +############################################# +# Zemismart KS-811 Triple push button +# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/ +# +# NOTES +# - +# +############################################# +############################################# + +############################################# +# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +############################################# +substitutions: + device_name: "esp-laundrylights" + project_name: "Zemismart Technologies.KS-811 Triple" # Project Details + project_version: "v1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + entity_prefix: "Laundry" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current. + friendly_name: "Main Bathroom Lightswitch (3)" + description_comment: "Main Bathroom Lightswitch using a Zemismart KS-811 Triple Push Button. Main Lights (1), Shower Lights (2), Cabinet Lights (3)" + device_area: "Laundry" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + api_key: !secret esp-mainbathlights_api_key # unfortunately you can't use substitutions inside secrets names + ota_pass: !secret esp-mainbathlights_ota_pass # unfortunately you can't use substitutions inside secrets names + static_ip_address: !secret esp-mainbathlights_ip + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + +############################################# +# Included Common Packages +# https://esphome.io/components/esphome.html +############################################# +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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 + 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} + +############################################# +# 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} + project: + name: "${project_name}" + version: "${project_version}" +# on_boot: +# priority: 200 +# then: +# - switch.turn_on: Relay_3 + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m + early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True. + board_flash_mode: dout # Default is dout + +############################################# +# 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 + +############################################# +# STATUS LED +# https://esphome.io/components/status_led.html +############################################# +status_led: + pin: + number: GPIO2 + inverted: yes + +############################################# +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +############################################# +binary_sensor: + - platform: gpio + pin: + number: GPIO16 + mode: INPUT + inverted: True + name: "Button 1: Main Lights" + on_press: + - switch.toggle: Relay_1 + + - platform: gpio + pin: + number: GPIO05 + mode: INPUT + inverted: True + name: "Button 2: Shower Lights" + on_press: + - switch.toggle: Relay_2 + + - platform: gpio + pin: + number: GPIO4 + mode: INPUT + inverted: True + name: "Button 3: Cabinet Lights" + on_press: + - switch.toggle: Relay_3 + +############################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +############################################# +switch: + - platform: gpio + name: "Relay 1: Main Lights" + pin: GPIO13 + id: Relay_1 + - platform: gpio + name: "Relay 2: Shower Lights" + pin: GPIO12 + id: Relay_2 + - platform: gpio + name: "Relay 3: Cabinet Lights" + pin: GPIO14 + id: Relay_3 + + diff --git a/esphome/archive/esp-laundrylights2 copy.yaml.old3 b/esphome/archive/esp-laundrylights2 copy.yaml.old3 new file mode 100644 index 0000000..cfb8581 --- /dev/null +++ b/esphome/archive/esp-laundrylights2 copy.yaml.old3 @@ -0,0 +1,157 @@ +############################################# +############################################# +# LAUNDRY MAIN LIGHTSWITCH +# V2.0 2025-06-01 Was initially a 3 button switch, now 2 +# V1.0 2025-05-30 Initial Version +############################################# +# Zemismart KS-811 Triple push button +# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/ +# +# NOTES +# - +# +############################################# +############################################# + +############################################# +# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +############################################# +substitutions: + device_name: "esp-laundrylights2" + project_name: "Zemismart Technologies.KS-811 Double" # Project Details + project_version: "v2" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + entity_prefix: "Laundry" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current. + friendly_name: "Laundry Main Lightswitch (2)" + description_comment: "Laundry Main Lightswitch using a Zemismart KS-811 Double Push Button. Laundry Lights (1), Laundry Power Signal (2)" + device_area: "Laundry" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + api_key: !secret esp-laundrylights_api_key # unfortunately you can't use substitutions inside secrets names + ota_pass: !secret esp-laundrylights_ota_pass # unfortunately you can't use substitutions inside secrets names + static_ip_address: !secret esp-laundrylights2_ip + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + +############################################# +# Included Common Packages +# https://esphome.io/components/esphome.html +############################################# +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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 + 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} + +############################################# +# 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} + project: + name: "${project_name}" + version: "${project_version}" + on_boot: + priority: 200 + then: + - switch.turn_on: Relay_2 + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m + early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True. + board_flash_mode: dout # Default is dout + +############################################# +# 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 + +############################################# +# STATUS LED +# https://esphome.io/components/status_led.html +############################################# +status_led: + pin: + number: GPIO2 + inverted: yes + +############################################# +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +############################################# +binary_sensor: + - platform: gpio + pin: + number: GPIO16 + mode: INPUT + inverted: True + name: "Button 1: Light Switch (Laundry)" + on_press: + - switch.toggle: Relay_1 + + - platform: gpio + pin: + number: GPIO05 + mode: INPUT + inverted: True + name: "Button 2: Laundry Power Enable" + on_press: + - switch.toggle: Relay_2 + +# ONLY ON A KS-811-3 +# - platform: gpio +# pin: +# number: GPIO4 +# mode: INPUT +# inverted: True +# name: "Button 3: " +# on_press: +# - switch.toggle: Relay_3 + +############################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +############################################# +switch: + - platform: gpio + name: "Relay 1: Laundry Lights" + pin: GPIO13 + id: Relay_1 + - platform: gpio + name: "Relay 2: Laundry Power Enable Signal" + pin: GPIO12 + id: Relay_2 +# ONLY ON A KS-811-3 +# - platform: gpio +# name: "Relay 3: " +# pin: GPIO14 +# id: Relay_3 + + diff --git a/esphome/archive/esp-mainbathlights copy.yaml b/esphome/archive/esp-mainbathlights copy.yaml new file mode 100644 index 0000000..1c26c9f --- /dev/null +++ b/esphome/archive/esp-mainbathlights copy.yaml @@ -0,0 +1,157 @@ +############################################# +############################################# +# LAUNDRY MAIN LIGHTSWITCH +# V1.0 2025-05-30 Initial Version +############################################# +# Zemismart KS-811 Triple push button +# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/ +# +# NOTES +# - +# +############################################# +############################################# + +############################################# +# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +############################################# +substitutions: + device_name: "esp-mainbathlights" + project_name: "Zemismart Technologies.KS-811 Triple" # Project Details + project_version: "v1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + entity_prefix: "Laundry" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current. + friendly_name: "Main Bathroom Lightswitch (3)" + description_comment: "Main Bathroom Lightswitch using a Zemismart KS-811 Triple Push Button. Main Lights (1), Shower Lights (2), Cabinet Lights (3)" + device_area: "Laundry" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + api_key: !secret esp-mainbathlights_api_key # unfortunately you can't use substitutions inside secrets names + ota_pass: !secret esp-mainbathlights_ota_pass # unfortunately you can't use substitutions inside secrets names + static_ip_address: !secret esp-mainbathlights_ip + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + +############################################# +# Included Common Packages +# https://esphome.io/components/esphome.html +############################################# +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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: + 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} + +############################################# +# 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} + project: + name: "${project_name}" + version: "${project_version}" +# on_boot: +# priority: 200 +# then: +# - switch.turn_on: Relay_3 + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m + early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True. + board_flash_mode: dout # Default is dout + +############################################# +# 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 + +############################################# +# STATUS LED +# https://esphome.io/components/status_led.html +############################################# +status_led: + pin: + number: GPIO2 + inverted: yes + +############################################# +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +############################################# +binary_sensor: + - platform: gpio + pin: + number: GPIO16 + mode: INPUT + inverted: True + name: "Button 1: Main Lights" + on_press: + - switch.toggle: Relay_1 + + - platform: gpio + pin: + number: GPIO05 + mode: INPUT + inverted: True + name: "Button 2: Shower Lights" + on_press: + - switch.toggle: Relay_2 + + - platform: gpio + pin: + number: GPIO4 + mode: INPUT + inverted: True + name: "Button 3: Cabinet Lights" + on_press: + - switch.toggle: Relay_3 + +############################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +############################################# +switch: + - platform: gpio + name: "Relay 1: Main Lights" + pin: GPIO13 + id: Relay_1 + - platform: gpio + name: "Relay 2: Shower Lights" + pin: GPIO12 + id: Relay_2 + - platform: gpio + name: "Relay 3: Cabinet Lights" + pin: GPIO14 + id: Relay_3 + + diff --git a/esphome/common/mqtt_common.yaml b/esphome/common/mqtt_common.yaml index b1ad3cb..097a941 100644 --- a/esphome/common/mqtt_common.yaml +++ b/esphome/common/mqtt_common.yaml @@ -14,7 +14,7 @@ substitutions: ############################################# mqtt: broker: ${mqtt_server} - topic_prefix: ${mqtt_topic}/${devicename} + topic_prefix: ${mqtt_topic}/${device_name} username: ${mqtt_username} password: ${mqtt_password} discovery: False # enable entity discovery (true is default) diff --git a/esphome/common/network_common.yaml b/esphome/common/network_common.yaml index acd75c4..f718f2e 100644 --- a/esphome/common/network_common.yaml +++ b/esphome/common/network_common.yaml @@ -44,7 +44,7 @@ wifi: subnet: ${static_ip_subnet} dns1: ${static_ip_dns1} ap: # Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode - ssid: ${devicename} AP + ssid: ${device_name} AP password: ${fallback_ap_password} ap_timeout: 10min # Time until it brings up fallback AP. default is 1min # Allow rapid re-connection to previously connect WiFi SSID, skipping scan of all SSID @@ -61,6 +61,7 @@ captive_portal: # extra fallback mechanism for when connecting if the configured ota: - platform: esphome password: ${local_ota_pass} + version: 2 ############################################# # Safe Mode diff --git a/esphome/esp-downstbathswitch.yaml b/esphome/esp-downstbathswitch.yaml index 55c71d6..e07b175 100644 --- a/esphome/esp-downstbathswitch.yaml +++ b/esphome/esp-downstbathswitch.yaml @@ -1,6 +1,7 @@ ############################################# ############################################# # DOWNSTAIRS BATHROOM MAIN LIGHTSWITCH +# V2.0 2025-06-05 YAML Tidyups # V1.0 2025-02-14 Initial Version ############################################# # Zemismart KS-811 Triple push button @@ -17,15 +18,29 @@ # If NOT using a secrets file, just replace these with the passwords etc (in quotes) ############################################# substitutions: + # Device Naming devicename: "esp-downstbathswitch" friendly_name: "Downstairs Bath Lightswitch (3)" description_comment: "Downstairs Bathroom Main Lightswitch using a Zemismart KS-811 Triple Push Button. Main Light (1), Cabinet Light (2), Extract Fan (3)" + device_area: "Downstairs Bathroom" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Project Naming + project_name: "Zemismart Technologies.KS-811 Triple" # Project Details + project_version: "v2.0" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + + # Passwords api_key: !secret esp-downstbathswitch_api_key # unfortunately you can't use substitutions inside secrets names ota_pass: !secret esp-downstbathswitch_ota_pass # unfortunately you can't use substitutions inside secrets names static_ip_address: !secret esp-downstbathswitch_ip + + # Device Settings log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE update_interval: "60s" # update time for for general sensors etc - room: "Downstairs Bathroom" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Switch Naming + switch_1_name: "Main Lights" + switch_2_name: "Cabinet Light" + switch_3_name: "Extract Fan" # This is virtual only, no power connected to 3rd relay ############################################# # Included Common Packages @@ -35,49 +50,32 @@ packages: common_wifi: !include file: common/network_common.yaml vars: - local_static_ip_address: ${static_ip_address} - local_ota_pass: ${ota_pass} + 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} + local_api_key: "${api_key}" # common_webportal: !include # file: common/webportal_common.yaml - common_mqtt: !include - file: common/mqtt_common.yaml +# common_mqtt: !include +# file: common/mqtt_common.yaml 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} + local_friendly_name: "${friendly_name}" + local_update_interval: "${update_interval}" -# common_multiclick_pushbutton_Switch_2: !include -# file: common/multiclick_pushbutton_common.yaml -# vars: -# local_name: "Button: Light Switch 2 (Cabinet)" -# local_gpio: GPIO05 # Switch 2 for KS-811 Triple is GPIO5 -# local_relay_id: Relay_2 # ID of relay to turn on -# local_singleclick_duration: 7200000 # 2 hrs in ms -# common_multiclick_pushbutton_Switch_3: !include -# file: common/multiclick_pushbutton_common.yaml -# vars: -# local_name: "Button: Extract Fan" -# local_gpio: GPIO04 # Switch 3 for KS-811 Triple is GPIO4 -# local_relay_id: Relay_3 # ID of relay to turn on -# local_singleclick_duration: 300000 # 5 minutes in ms -# local_doubleclick_duration: 1800000 # 30 minutes in ms -# local_tripleclick_duration: 7200000 # 2 hours in ms - ############################################# # ESPHome # https://esphome.io/components/esphome.html ############################################# esphome: - name: ${devicename} - friendly_name: ${friendly_name} - comment: ${description_comment} #Appears on the esphome page in HA + name: "${devicename}" + friendly_name: "${friendly_name}" + comment: "${description_comment}" #Appears on the esphome page in HA ############################################# # ESP Platform and Framework @@ -91,7 +89,7 @@ esp8266: # https://esphome.io/components/logger.html ############################################# logger: - level: ${log_level} #INFO Level suggested, or DEBUG for testing + 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 @@ -103,7 +101,7 @@ logger: status_led: pin: number: GPIO2 - inverted: yes + inverted: True ############################################# # BINARY SENSORS @@ -115,7 +113,7 @@ binary_sensor: number: GPIO16 mode: INPUT inverted: True - name: "Button 1: Light Switch 1 (Main)" + name: "Button 1: ${switch_1_name}" on_press: - switch.toggle: Relay_1 @@ -124,7 +122,7 @@ binary_sensor: number: GPIO05 mode: INPUT inverted: True - name: "Button 2: Light Switch 2 (Cabinet)" + name: "Button 2: ${switch_2_name}" on_press: - switch.toggle: Relay_2 @@ -133,7 +131,7 @@ binary_sensor: number: GPIO4 mode: INPUT inverted: True - name: "Button 3: Light Switch 3 (Fan)" + name: "Button 3: ${switch_3_name}" on_multi_click: # Single click: 1 press → relay on for 5 minutes - timing: @@ -204,15 +202,15 @@ binary_sensor: ############################################# switch: - platform: gpio - name: "Relay 1: Main Light" + name: "Relay 1: ${switch_1_name}" pin: GPIO13 id: Relay_1 - platform: gpio - name: "Relay 2: Cabinet Light" + name: "Relay 2: ${switch_2_name}" pin: GPIO12 id: Relay_2 - platform: gpio - name: "Relay 3: Extract Fan" + name: "Relay 3: ${switch_3_name}" pin: GPIO14 id: Relay_3 diff --git a/esphome/esp-downstbathtowelrail.yaml b/esphome/esp-downstbathtowelrail.yaml index 8899869..63774b1 100644 --- a/esphome/esp-downstbathtowelrail.yaml +++ b/esphome/esp-downstbathtowelrail.yaml @@ -1,60 +1,74 @@ -############################################# -############################################# +########################################################################################## +########################################################################################## # DOWNSTAIRS BATHROOM HEATED TOWEL RAIL # Controlled by a Sonoff Basic # +# V2.1 2025-06-12 Added select and button to chose modes, added countdown & startup to boost +# V2.0 2025-06-05 YAML Tidyups # V1.1 2025-04-12 Fixes to timers and offline modes # V1.0 2025-02-14 Initial Version # # INSTRUCTIONS -# - It allows a heated towel rail device to work in a standalone operation -# - On startup, it will turn on for (startup_duration) hours then go into timer mode (this allows you to just turn it on to get some heat immediately) -# - The timer has a morning and evening time (but no weekday/weekend setting) -# - Default values are 5am-7am and 9pm-Midnight (as this suits our use case) +# - 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 4 on/off times via MQTT (See below commands) -# - You can set 4 modes ON/OFF/TIMER/STARTUP via MQTT. That way, you can set to STARTUP for a short boost +# - 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 a reboot, the device will always turn on for the Startup Duration (STARTUP mode, default 2 hours) -# - TIMER mode will always be switched on after startup mode is complete +# - 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 towel rail will go on -# mqtt_timer_topic/morning-off/08:00 : Time towel rail will go off -# mqtt_timer_topic/evening-on/09:00 : Time towel rail will go on -# mqtt_timer_topic/evening-off/00:00 : Time towel rail will go off -# mqtt_timer_topic/operation/ON : Towel rail permanently on -# mqtt_timer_topic/operation/OFF : Towel rail permanently off -# mqtt_timer_topic/operation/TIMER : Towel rail will obey timer settings -# mqtt_timer_topic/operation/STARTUP : Turn on for (startup_duration) hours then TIMER (also on startup) +# 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) # -############################################# -############################################# +########################################################################################## +########################################################################################## -############################################# +########################################################################################## # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS # If NOT using a secrets file, just replace these with the passwords etc (in quotes) -############################################# +########################################################################################## substitutions: - devicename: "esp-downstbathtowelrail" + # Device Naming + device_name: "esp-downstbathtowelrail" friendly_name: "Downstairs Bathroom Towelrail" description_comment: "Sonoff Basic controlling ON/OFF/Timer for the Heated Towel Rail in the Downstairs Bathroom" - api_key: !secret esp-downstbathtowelrail_api_key # unfortunately you can't use substitutions inside secrets names - ota_pass: !secret esp-downstbathtowelrail_ota_pass # unfortunately you can't use substitutions inside secrets names - static_ip_address: !secret esp-downstbathtowelrail_ip - log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE - update_interval: "60s" # update time for for general sensors etc - room: "Downstairs Bathroom" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + device_area: "Downstairs Bathroom" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. - ############################################# - # SPECIFIC PROJECT VARIABLE SUBSTITUTIONS - ############################################# + # Project Naming + project_name: "Sonoff Technologies.Sonoff Basic V1" # Project Details + project_version: "v2.1" # 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-downstbathtowelrail_ip + + # Device Settings + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "20s" # update time for for general sensors etc + + # Timer Settings mqtt_timer_topic: "viewroad-commands/downstbath-towelrail" # Topics you will use to change stuff - startup_duration: "120" # Minutes to stay ON in STARTUP mode before reverting to TIMER + boost_duration_default: "120" # Minutes to stay ON in BOOST mode before reverting to TIMER + morning_on_default: "420" # Default in minutes from midnight. Default 07:00 => 420 + morning_off_default: "450" # Default in minutes from midnight. Default 07:30 => 450 + evening_on_default: "1260" # Default in minutes from midnight. Default 21:00 => 1260 + evening_off_default: "1320" # Default in minutes from midnight. Default 22:00 => 1320 => 1440 is midnight ############################################# # Included Common Packages @@ -64,12 +78,12 @@ packages: common_wifi: !include file: common/network_common.yaml vars: - local_static_ip_address: ${static_ip_address} - local_ota_pass: ${ota_pass} + 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} + local_api_key: "${api_key}" # common_webportal: !include # file: common/webportal_common.yaml common_mqtt: !include @@ -79,18 +93,18 @@ packages: common_general_sensors: !include file: common/sensors_common.yaml vars: - local_friendly_name: ${friendly_name} - local_update_interval: ${update_interval} + local_friendly_name: "${friendly_name}" + local_update_interval: "${update_interval}" ############################################# # ESPHome # https://esphome.io/components/esphome.html ############################################# esphome: - name: ${devicename} - friendly_name: ${friendly_name} - comment: ${description_comment} # Appears on the esphome page in HA - area: "${room}" + name: "${device_name}" + friendly_name: "${friendly_name}" + comment: "${description_comment}" # Appears on the esphome page in HA + area: "${device_area}" on_boot: priority: 900 # High priority to run after globals are initialized then: @@ -127,9 +141,9 @@ esphome: ESP_LOGI("power_cycle", "Detected 4 power cycles in 20s => Forcing ON mode"); } else { // Otherwise do your normal startup logic: - id(operation_mode) = 3; // on_boot -> sets operation_mode = 3 (STARTUP) - id(startup_timer) = 0; // and reset startup_timer = 0 (for time sync if no sntp) - ESP_LOGI("power_cycle", "Boot count=%d => STARTUP mode", id(boot_count)); + id(operation_mode) = 3; // on_boot -> sets operation_mode = 3 (BOOST) + id(boost_timer) = 0; // and reset boost_timer = 0 (for time sync if no sntp) + ESP_LOGI("power_cycle", "Boot count=%d => BOOST mode", id(boot_count)); } ############################################# @@ -138,13 +152,20 @@ esphome: ############################################# esp8266: board: esp01_1m # The original sonoff basic + restore_from_flash: True # restore some values on reboot + +preferences: + flash_write_interval: 5min + +mdns: + disabled: false ############################################# # ESPHome Logging Enable # https://esphome.io/components/logger.html ############################################# logger: - level: ${log_level} #INFO Level suggested, or DEBUG for testing + 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 @@ -168,39 +189,41 @@ globals: initial_value: "0" # Morning On time (minutes from midnight), - # default 07:00 => 420 - id: morning_on type: int restore_value: true - initial_value: "420" + initial_value: "${morning_on_default}" # Morning Off time (minutes from midnight), - # default 07:30 => 450 - id: morning_off type: int restore_value: true - initial_value: "450" + initial_value: "${morning_off_default}" # Evening On time (minutes from midnight), - # default 21:00 => 1260 - id: evening_on type: int restore_value: true - initial_value: "1260" + initial_value: "${morning_off_default}" # Evening Off time (minutes from midnight), - # default 22:00 => 1320 => treat as midnight - id: evening_off type: int restore_value: true - initial_value: "1320" + initial_value: "${morning_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 = STARTUP + # 3 = BOOST #################################################### - id: operation_mode type: int @@ -219,11 +242,11 @@ globals: initial_value: "720" # 720 is 12:00 Noon #################################################### - # startup_timer: counts minutes in STARTUP mode - # After 'startup_duration' minutes, revert to TIMER. + # boost_timer: counts minutes in BOOST mode + # After 'boost_duration' minutes, revert to TIMER. # Not restored, so each boot starts fresh at 0. #################################################### - - id: startup_timer + - id: boost_timer type: int restore_value: false initial_value: "0" @@ -318,15 +341,37 @@ text_sensor: 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 0–1439 + 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(v); + } + + #################################################### # Subscribe to operation mode: - # OFF, ON, TIMER, STARTUP + # OFF, ON, TIMER, BOOST # We do case-insensitive compare using strcasecmp # (Requires typically included in ESPHome) #################################################### - platform: mqtt_subscribe name: "Operation Mode Setting" id: timer_operation_mode_topic - topic: "${mqtt_timer_topic}/operation" # STARTUP,ON,OFF,TIMER + topic: "${mqtt_timer_topic}/operation" # BOOST,ON,OFF,TIMER internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value on_value: then: @@ -345,29 +390,29 @@ text_sensor: } 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(), "STARTUP") == 0) { + } else if (strcasecmp(x.c_str(), "BOOST") == 0) { id(operation_mode) = 3; - id(startup_timer) = 0; // Reset the startup timer to zero - ESP_LOGI("timer","Operation mode set to STARTUP"); + id(boost_timer) = 0; // Reset the BOOST timer to zero + ESP_LOGI("timer","Operation mode set to BOOST"); } else { ESP_LOGW("timer","Invalid operation mode: %s", x.c_str()); } ###################################################### - # Expose the current operation mode (OFF, ON, TIMER, STARTUP) + # Expose the current operation mode (OFF, ON, TIMER, BOOST) ###################################################### - platform: template name: "Operation Mode State" lambda: |- - // 0=OFF, 1=ON, 2=TIMER, 3=STARTUP + // 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 {"STARTUP"}; + case 3: return {"BOOST"}; default: return {"UNKNOWN"}; } - update_interval: ${update_interval} + update_interval: 5s ###################################################### # Expose the "Morning On" time as a text (HH:MM) @@ -381,7 +426,7 @@ text_sensor: char buff[16]; snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute); return { std::string(buff) }; - update_interval: ${update_interval} + update_interval: "${update_interval}" ###################################################### # Expose the "Morning Off" time as a text (HH:MM) @@ -396,7 +441,7 @@ text_sensor: char buff[16]; snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute); return { std::string(buff) }; - update_interval: ${update_interval} + update_interval: "${update_interval}" ###################################################### # Expose the "Evening On" time as a text (HH:MM) @@ -411,7 +456,7 @@ text_sensor: char buff[16]; snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute); return { std::string(buff) }; - update_interval: ${update_interval} + update_interval: "${update_interval}" ###################################################### # Expose the "Evening Off" time as a text (HH:MM) @@ -426,7 +471,17 @@ text_sensor: char buff[16]; snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute); return { std::string(buff) }; - update_interval: ${update_interval} + update_interval: "${update_interval}" + + ###################################################### + # Expose the "Boost time" time as text (HH:MM) + ###################################################### + - platform: template + name: "Timeclock: Boost Time" + lambda: |- + // e.g. "120 Mins" + return std::to_string(id(boost_duration)) + " Mins"; + update_interval: "${update_interval}" ############################################# # Sensors @@ -437,11 +492,37 @@ sensor: name: "Mins from Midnight" unit_of_measurement: "mins" accuracy_decimals: 0 - update_interval: ${update_interval} + update_interval: "${update_interval}" internal: True 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; + #################################################### # Relay Switch (Sonoff Basic Relay on GPIO12) #################################################### @@ -452,6 +533,62 @@ switch: id: relay restore_mode: RESTORE_DEFAULT_OFF +################################################################################################# +# BUTTON COMPONENT +# https://esphome.io/components/button/index.html +################################################################################################# +button: + - platform: template + name: "Boost now" + id: boost_button + icon: "mdi:play-circle-outline" # optional, pick any MaterialDesign icon you like + on_press: + then: + # 1) set the mode to BOOST (3) + - lambda: |- + id(boost_timer) = 0; // Reset the BOOST timer to zero + id(operation_mode) = 3; // Set to BOOST + ESP_LOGD("main", "operation_mode set to %d via BOOST button", id(operation_mode)); + # 2) turn on the relay switch + - switch.turn_on: + id: relay + +################################################################################################# +# SELECT COMPONENT +# https://esphome.io/components/select/index.html +################################################################################################# +select: + - platform: template + name: "Operation Mode" + id: operation_mode_select + update_interval: 5s # poll every 5 s for external changes + options: + - "OFF" + - "ON" + - "TIMER" + - "BOOST" + + # Getter: maps your integer into one of the four strings + 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"); + } + + # set_action: called when you pick an option in HA + 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 { + id(boost_timer) = 0; // Reset the BOOST timer to zero + id(operation_mode) = 3; /* BOOST */ + } + ESP_LOGD("main", "operation_mode set to %d", id(operation_mode)); + #################################################### # Check every minute to decide relay state #################################################### @@ -481,26 +618,26 @@ interval: // 0 = OFF // 1 = ON // 2 = TIMER - // 3 = STARTUP + // 3 = BOOST int mode = id(operation_mode); ////////////////////////////////////////////////// - // STARTUP MODE: Relay ON for 'startup_duration' + // BOOST MODE: Relay ON for 'boost_duration' // minutes, then automatically revert to TIMER. ////////////////////////////////////////////////// if (mode == 3) { - id(startup_timer) = id(startup_timer) + 1 ; // works as long as update_interval in seconds - // Compare with the substitution startup_duration - if (id(startup_timer) < (int) ${startup_duration}) { - // Still within the STARTUP period => turn relay on + id(boost_timer) = id(boost_timer) + 1 ; // works as long as update_interval in seconds + // Compare with the substitution boost_duration + if (id(boost_timer) < id(boost_duration)) { + // Still within the BOOST period => turn relay on id(relay).turn_on(); } else { - // After 'startup_duration' minutes => switch to TIMER + // After 'boost_duration' minutes => switch to TIMER id(operation_mode) = 2; id(mqtt_client).publish("${mqtt_timer_topic}/operation", "TIMER"); } // Skip the rest of the logic - ESP_LOGI("startup_timer", "startup_timer=%d", id(startup_timer)); + ESP_LOGI("boost_timer", "boost_timer=%d", id(boost_timer)); return; } diff --git a/esphome/esp-downstkitchlights.yaml b/esphome/esp-downstkitchlights.yaml index 6730f62..e101b79 100644 --- a/esphome/esp-downstkitchlights.yaml +++ b/esphome/esp-downstkitchlights.yaml @@ -1,6 +1,7 @@ ############################################# ############################################# # DOWNSTAIRS KITCHEN MAIN LIGHTSWITCH +# V2.0 2025-06-05 YAML tidyups # V1.0 2025-03-28 Initial Version ############################################# # Zemismart KS-811 Triple push button @@ -17,15 +18,25 @@ # If NOT using a secrets file, just replace these with the passwords etc (in quotes) ############################################# substitutions: - devicename: "esp-downstkitchlights" + # Device Naming + device_name: "esp-downstkitchlights" friendly_name: "Downstairs Kitchen Lightswitch (3)" description_comment: "Downstairs Kitch Main Lightswitch using a Zemismart KS-811 Triple Push Button. Dining Light (1), Kitchen Light (2), Extract Fan (3)" - api_key: !secret esp-downstkitchlights_api_key # unfortunately you can't use substitutions inside secrets names - ota_pass: !secret esp-downstkitchlights_ota_pass # unfortunately you can't use substitutions inside secrets names + device_area: "Downstairs Kitchen" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # 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-downstkitchlights_ip + + # Device Settings log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE update_interval: "60s" # update time for for general sensors etc - room: "Downstairs Kitchen" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Switch Naming + switch_1_name: "Dining Light" + switch_2_name: "Kitchen Light" + switch_3_name: "Extract Fan" # This is virtual only, no power connected to 3rd relay ############################################# # Included Common Packages @@ -35,50 +46,33 @@ packages: common_wifi: !include file: common/network_common.yaml vars: - local_static_ip_address: ${static_ip_address} - local_ota_pass: ${ota_pass} + 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} + local_api_key: "${api_key}" # common_webportal: !include # file: common/webportal_common.yaml - common_mqtt: !include - file: common/mqtt_common.yaml +# common_mqtt: !include +# file: common/mqtt_common.yaml 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} + local_friendly_name: "${friendly_name}" + local_update_interval: "${update_interval}" -# common_multiclick_pushbutton_Switch_2: !include -# file: common/multiclick_pushbutton_common.yaml -# vars: -# local_name: "Button: Light Switch 2 (Cabinet)" -# local_gpio: GPIO05 # Switch 2 for KS-811 Triple is GPIO5 -# local_relay_id: Relay_2 # ID of relay to turn on -# local_singleclick_duration: 7200000 # 2 hrs in ms -# common_multiclick_pushbutton_Switch_3: !include -# file: common/multiclick_pushbutton_common.yaml -# vars: -# local_name: "Button: Extract Fan" -# local_gpio: GPIO04 # Switch 3 for KS-811 Triple is GPIO4 -# local_relay_id: Relay_3 # ID of relay to turn on -# local_singleclick_duration: 300000 # 5 minutes in ms -# local_doubleclick_duration: 1800000 # 30 minutes in ms -# local_tripleclick_duration: 7200000 # 2 hours in ms - ############################################# # ESPHome # https://esphome.io/components/esphome.html ############################################# esphome: - name: ${devicename} + name: ${device_name} friendly_name: ${friendly_name} - comment: ${description_comment} #Appears on the esphome page in HA - area: ${room} + comment: ${description_comment} # Appears on the esphome page in HA + area: ${device_area} ############################################# # ESP Platform and Framework @@ -116,7 +110,7 @@ binary_sensor: number: GPIO16 mode: INPUT inverted: True - name: "Button 1: Light Switch 1 (Dining)" + name: "Button 1: ${switch_1_name}" on_press: - switch.toggle: Relay_1 @@ -125,7 +119,7 @@ binary_sensor: number: GPIO05 mode: INPUT inverted: True - name: "Button 2: Light Switch 2 (Kitchen)" + name: "Button 2: ${switch_2_name}" on_press: - switch.toggle: Relay_2 @@ -134,7 +128,7 @@ binary_sensor: number: GPIO4 mode: INPUT inverted: True - name: "Button 3: Switch 3 (Extract)" + name: "Button 3: ${switch_3_name}" on_press: - switch.toggle: Relay_3 @@ -144,15 +138,15 @@ binary_sensor: ############################################# switch: - platform: gpio - name: "Relay 1: Dining Light" + name: "Relay 1: ${switch_1_name}" pin: GPIO13 id: Relay_1 - platform: gpio - name: "Relay 2: Kitchen Light" + name: "Relay 2: ${switch_2_name}" pin: GPIO12 id: Relay_2 - platform: gpio - name: "Relay 3: Extract Fan" + name: "Relay 3: ${switch_3_name}" pin: GPIO14 id: Relay_3 diff --git a/esphome/esp-laundrylights.yaml b/esphome/esp-laundrylights.yaml index 2eb374c..bea18ff 100644 --- a/esphome/esp-laundrylights.yaml +++ b/esphome/esp-laundrylights.yaml @@ -1,6 +1,8 @@ ############################################# ############################################# # LAUNDRY MAIN LIGHTSWITCH +# V3.0 2025-06-05 YAML tidyups +# V2.0 2025-06-01 Was initially a 3 button switch, now 2 # V1.0 2025-05-30 Initial Version ############################################# # Zemismart KS-811 Triple push button @@ -17,19 +19,30 @@ # If NOT using a secrets file, just replace these with the passwords etc (in quotes) ############################################# substitutions: + # Device Naming device_name: "esp-laundrylights" - project_name: "Zemismart Technologies.KS-811 Triple" # Project Details - project_version: "v1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version - entity_prefix: "Laundry" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current. - friendly_name: "Laundry Main Lightswitch (3)" - description_comment: "Laundry Main Lightswitch using a Zemismart KS-811 Triple Push Button. Laundry Lights (1), Spare (2), Laundry Power Signal (3)" + + friendly_name: "Laundry Main Lightswitch (2)" + description_comment: "Laundry Main Lightswitch using a Zemismart KS-811 Double Push Button. Laundry Lights (1), Laundry Power Signal (2)" device_area: "Laundry" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. - api_key: !secret esp-laundrylights_api_key # unfortunately you can't use substitutions inside secrets names - ota_pass: !secret esp-laundrylights_ota_pass # unfortunately you can't use substitutions inside secrets names + + # Project Naming + project_name: "Zemismart Technologies.KS-811 Double" # Project Details + project_version: "v3.0" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + + 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-laundrylights_ip + + # Device Settings log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE update_interval: "60s" # update time for for general sensors etc + # Switch Naming + switch_1_name: "Laundry Lights" + switch_2_name: "Laundry Power Enable" # This is virtual only, no power connected to 3rd relay + #switch_3_name: "Spare" + ############################################# # Included Common Packages # https://esphome.io/components/esphome.html @@ -38,40 +51,40 @@ packages: common_wifi: !include file: common/network_common.yaml vars: - local_static_ip_address: ${static_ip_address} - local_ota_pass: ${ota_pass} + 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} + local_api_key: "${api_key}" # common_webportal: !include # file: common/webportal_common.yaml - common_mqtt: !include - file: common/mqtt_common.yaml +# common_mqtt: !include +# file: common/mqtt_common.yaml 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} + local_friendly_name: "${friendly_name}" + local_update_interval: "${update_interval}" ############################################# # 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: "${device_name}" + friendly_name: "${friendly_name}" + comment: "${description_comment}" # Appears on the esphome page in HA + area: "${device_area}" project: name: "${project_name}" version: "${project_version}" on_boot: priority: 200 then: - - switch.turn_on: Relay_3 + - switch.turn_on: Relay_2 ############################################# # ESP Platform and Framework @@ -87,7 +100,7 @@ esp8266: # https://esphome.io/components/logger.html ############################################# logger: - level: ${log_level} #INFO Level suggested, or DEBUG for testing + 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 @@ -111,7 +124,7 @@ binary_sensor: number: GPIO16 mode: INPUT inverted: True - name: "Button 1: Light Switch (Laundry)" + name: "Button 1: ${switch_1_name}" on_press: - switch.toggle: Relay_1 @@ -120,18 +133,19 @@ binary_sensor: number: GPIO05 mode: INPUT inverted: True - name: "Button 2: Spare" + name: "Button 2: ${switch_2_name}" on_press: - switch.toggle: Relay_2 - - platform: gpio - pin: - number: GPIO4 - mode: INPUT - inverted: True - name: "Button 3: Laundry Power Enable" - on_press: - - switch.toggle: Relay_3 +# KS-811-2 is a double only +# - platform: gpio +# pin: +# number: GPIO4 +# mode: INPUT +# inverted: True +# name: "Button 3: ${switch_3_name}" +# on_press: +# - switch.toggle: Relay_3 ############################################# # SWITCH COMPONENT @@ -139,16 +153,19 @@ binary_sensor: ############################################# switch: - platform: gpio - name: "Relay 1: Laundry Lights" + name: "Relay 1: ${switch_1_name}" pin: GPIO13 id: Relay_1 + - platform: gpio - name: "Relay 2: Spare" + name: "Relay 2: ${switch_2_name}" pin: GPIO12 id: Relay_2 - - platform: gpio - name: "Relay 3: Laundry Power Enable Signal" - pin: GPIO14 - id: Relay_3 + +# KS-811-2 is a double only +# - platform: gpio +# name: "Relay 3: ${switch_3_name}" +# pin: GPIO14 +# id: Relay_3 diff --git a/esphome/esp-mainbathfancombo.yaml b/esphome/esp-mainbathfancombo.yaml new file mode 100644 index 0000000..074facf --- /dev/null +++ b/esphome/esp-mainbathfancombo.yaml @@ -0,0 +1,146 @@ +############################################# +############################################# +# MAIN BATHROOM FAN/HEAT COMBO SWITCH +# V1.0 2025-06-01 Initial Version +############################################# +# Zemismart KS-811 Triple push button +# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/ +# +# NOTES +# - +# +############################################# +############################################# + +############################################# +# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +############################################# +substitutions: + device_name: "esp-mainbathfancombo" + project_name: "Zemismart Technologies.KS-811-2 (Double)" # Project Details + project_version: "v1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + entity_prefix: "Main Bathroom" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current. + friendly_name: "Main Bathroom Fan/Heat Combo Switch (2)" + description_comment: "Main Bathroom Fan/Heat Switch using a Zemismart KS-811 Double Push Button. Extract Fan (1), IR heater (2)" + device_area: "Laundry" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + 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-mainbathfancombo_ip + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + +############################################# +# Included Common Packages +# https://esphome.io/components/esphome.html +############################################# +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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: + 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} + +############################################# +# 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} + project: + name: "${project_name}" + version: "${project_version}" +# on_boot: +# priority: 200 +# then: +# - switch.turn_on: Relay_3 + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m + early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True. + board_flash_mode: dout # Default is dout + +############################################# +# 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 + +############################################# +# STATUS LED +# https://esphome.io/components/status_led.html +############################################# +status_led: + pin: + number: GPIO2 + inverted: yes + +############################################# +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +############################################# +binary_sensor: + - platform: gpio + pin: + number: GPIO16 + mode: INPUT + inverted: True + name: "Button 1: Extract Fan" + on_press: + - switch.toggle: Relay_1 + + - platform: gpio + pin: + number: GPIO05 + mode: INPUT + inverted: True + name: "Button 2: IR Heater" + on_press: + - switch.toggle: Relay_2 + +############################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +############################################# +switch: + - platform: gpio + name: "Relay 1: Extract Fan" + pin: GPIO13 + id: Relay_1 + + - platform: gpio + name: "Relay 2: IR Heater" + pin: GPIO12 + id: Relay_2 + on_turn_on: + switch.turn_on: Relay_1 + diff --git a/esphome/esp-mainbathlights.yaml b/esphome/esp-mainbathlights.yaml new file mode 100644 index 0000000..3fe4061 --- /dev/null +++ b/esphome/esp-mainbathlights.yaml @@ -0,0 +1,164 @@ +############################################# +############################################# +# MAIN BATHROOM LIGHTSWITCH +# V1.0 2025-06-01 Initial Version +############################################# +# Zemismart KS-811 Triple push button +# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/ +# +# NOTES +# - +# +############################################# +############################################# + +############################################# +# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +############################################# +substitutions: + device_name: "esp-mainbathlights" + project_name: "Zemismart Technologies.KS-811-3 (Triple)" # Project Details + project_version: "v1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + entity_prefix: "Laundry" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current. + friendly_name: "Main Bathroom Lightswitch (3)" + description_comment: "Main Bathroom Lightswitch using a Zemismart KS-811 Triple Push Button. Main Lights (1), Shower Lights (2), Cabinet Lights (3)" + device_area: "Laundry" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + 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-mainbathlights_ip + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + +############################################# +# Included Common Packages +# https://esphome.io/components/esphome.html +############################################# +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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: + 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} + +############################################# +# 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} + project: + name: "${project_name}" + version: "${project_version}" +# on_boot: +# priority: 200 +# then: +# - switch.turn_on: Relay_3 + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m + early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True. + board_flash_mode: dout # Default is dout + +############################################# +# 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 + +############################################# +# STATUS LED +# https://esphome.io/components/status_led.html +############################################# +status_led: + pin: + number: GPIO2 + inverted: yes + +############################################# +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +############################################# +binary_sensor: + - platform: gpio + pin: + number: GPIO16 + mode: INPUT + inverted: True + name: "Button 1: Main Lights" + on_press: + - switch.toggle: Relay_1 + + - platform: gpio + pin: + number: GPIO05 + mode: INPUT + inverted: True + name: "Button 2: Shower Lights" + on_press: + - switch.toggle: Relay_2 + + - platform: gpio + pin: + number: GPIO4 + mode: INPUT + inverted: True + name: "Button 3: Cabinet Lights" + on_press: + - switch.turn_off: Relay_1 + - switch.turn_off: Relay_2 + - switch.turn_off: Relay_3 + +############################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +############################################# +switch: + - platform: gpio + name: "Relay 1: Main Lights" + pin: GPIO13 + id: Relay_1 + on_turn_on: + - switch.turn_on: Relay_2 + - switch.turn_on: Relay_3 + + - platform: gpio + name: "Relay 2: Shower Lights" + pin: GPIO12 + id: Relay_2 + + - platform: gpio + name: "Relay 3: Cabinet Lights" + pin: GPIO14 + id: Relay_3 + + diff --git a/esphome/esp-maindishwasherpower.yaml b/esphome/esp-maindishwasherpower.yaml new file mode 100644 index 0000000..1ad8e17 --- /dev/null +++ b/esphome/esp-maindishwasherpower.yaml @@ -0,0 +1,302 @@ +########################################################################################## +########################################################################################## +# MAIN DISHWASHER POWER +# Controlled by a Athom Smart Plug V1 +# package_import_url: github://athom-tech/athom-configs/athom-smart-plug.yaml +# +# V1.0 2025-06-10 YAML Tidyups +# +########################################################################################## +########################################################################################## + +########################################################################################## +# 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-maindishwasherpower" + friendly_name: "Main Dishwasher Power" + description_comment: "Main Dishwasher Power, Athom Smart Plug V1 Power Monitor" + device_area: "Kitchen" # 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: "v1.0" # 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-maindishwasherpower_ip + + # Device Settings + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + + # Device Settings + relay_icon: "mdi:dishwasher" + current_limit : "10" # Current Limit in Amps. AU Plug = 10. IL, BR, EU, UK, US Plug = 16. + +########################################################################################## +# PACKAGES +# https://esphome.io/components/esphome.html +########################################################################################## +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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 + 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}" + +########################################################################################## +# 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}" + on_boot: + priority: 200 + then: + - switch.turn_on: "relay" + +########################################################################################## +# 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 + +preferences: + flash_write_interval: 5min + +mdns: + disabled: false + +#dashboard_import: +# package_import_url: github://athom-tech/esp32-configs/athom-smart-plug.yaml + +########################################################################################## +# ESPHome LOGGING +# 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 +# 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" + + #################################################### + # total_energy: cumulative power + # Restored, so keeps counting up. + #################################################### + - id: total_energy + type: float + restore_value: yes + initial_value: "0.0" + +########################################################################################## +# STATUS LED +# https://esphome.io/components/status_led.html +########################################################################################## +status_led: + pin: + number: GPIO13 + inverted: True + +########################################################################################## +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +########################################################################################## +binary_sensor: + - platform: gpio + pin: + number: 3 + mode: INPUT_PULLUP + inverted: true + name: "Power Button" + id: power_button + filters: + - delayed_on: 20ms + on_click: + - min_length: 20ms + max_length: 500ms + then: + - switch.toggle: + id: relay + + - platform: template + name: "Relay Status" + lambda: |- + return id(relay).state; + +########################################################################################## +# Sensors +# https://esphome.io/components/text_sensor/index.html +########################################################################################## +sensor: + - platform: template + name: "Total Energy" + id: total_energy_sensor + unit_of_measurement: kWh + device_class: "energy" + state_class: "total_increasing" + icon: mdi:lightning-bolt + accuracy_decimals: 3 + lambda: |- + return id(total_energy); + update_interval: "${update_interval}" + + ############################################# + # CSE7766 POWER SENSOR + # https://esphome.io/components/sensor/cse7766.html + ############################################# + - platform: hlw8012 + id: athom_hlw8012 + sel_pin: + number: GPIO12 + inverted: True + cf_pin: GPIO4 + cf1_pin: GPIO5 + voltage_divider: 780 + + current: + name: "Current" + id: current + unit_of_measurement: A + accuracy_decimals: 2 + icon: mdi:current-ac + filters: + - calibrate_linear: + - 0.0000 -> 0.0110 # Relay off no load + - 0.0097 -> 0.0260 # Relay on no load + - 0.9270 -> 0.7570 + - 2.0133 -> 1.6330 + - 2.9307 -> 2.3750 + - 5.4848 -> 4.4210 + - 8.4308 -> 6.8330 + - 9.9171 -> 7.9830 + # Normalize for plug load + - lambda: if (x < 0.0260) return 0; else return (x - 0.0260); + on_value_range: + - above: ${current_limit} + then: + - switch.turn_off: relay + + voltage: + name: "Voltage" + id: voltage + unit_of_measurement: V + accuracy_decimals: 1 + icon: mdi:sine-wave + filters: + - skip_initial: 2 + + power: + name: "Power" + id: power_sensor + unit_of_measurement: W + accuracy_decimals: 1 + icon: mdi:power + filters: + - calibrate_linear: + - 0.0000 -> 0.5900 # Relay off no load + - 0.0000 -> 1.5600 # Relay on no load + - 198.5129 -> 87.8300 + - 434.2469 -> 189.5000 + - 628.6241 -> 273.9000 + - 1067.0067 -> 460.1000 + - 1619.8098 -> 699.2000 + - 2043.0282 -> 885.0000 + # Normalize for plug load + - lambda: if (x < 1.5600) return 0; else return (x - 1.5600); + change_mode_every: 1 + update_interval: 5s + + # Shows the Energy kWh since the device was last started + energy: + name: "Energy (Since Restart)" + id: energy + icon: mdi:lightning-bolt + unit_of_measurement: kWh + accuracy_decimals: 3 + filters: + # Multiplication factor from W to kW is 0.001 + - multiply: 0.001 + on_value: + then: + - lambda: |- + static float previous_energy_value = 0.0; + float current_energy_value = id(energy).state; + id(total_energy) += current_energy_value - previous_energy_value; + previous_energy_value = current_energy_value; + id(total_energy_sensor).update(); + # internal: ${hide_energy_sensor} + + ############################################# + # Total Daily Energy + # https://esphome.io/components/sensor/total_daily_energy.html + ############################################# + - platform: total_daily_energy + name: "Total Daily Energy" + restore: true + power_id: power_sensor + unit_of_measurement: kWh + icon: mdi:hours-24 + accuracy_decimals: 3 + filters: + - multiply: 0.001 + +################################################################################################# +# 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}" + diff --git a/esphome/esp-masterbathtowelrail.yaml b/esphome/esp-masterbathtowelrail.yaml index ed8e7ce..f7c828c 100644 --- a/esphome/esp-masterbathtowelrail.yaml +++ b/esphome/esp-masterbathtowelrail.yaml @@ -3,58 +3,72 @@ # MASTER BATHROOM HEATED TOWEL RAIL # Controlled by a Sonoff Basic # +# V2.1 2025-06-12 Added select and button to chose modes, added countdown & startup to boost +# V2.0 2025-06-05 YAML Tidyups # V1.1 2025-04-12 Fixes to timers and offline modes # V1.0 2025-02-14 Initial Version # # INSTRUCTIONS -# - It allows a heated towel rail device to work in a standalone operation -# - On startup, it will turn on for (startup_duration) hours then go into timer mode (this allows you to just turn it on to get some heat immediately) -# - The timer has a morning and evening time (but no weekday/weekend setting) -# - Default values are 5am-7am and 9pm-Midnight (as this suits our use case) +# - 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 4 on/off times via MQTT (See below commands) -# - You can set 4 modes ON/OFF/TIMER/STARTUP via MQTT. That way, you can set to STARTUP for a short boost +# - 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 a reboot, the device will always turn on for the Startup Duration (STARTUP mode, default 2 hours) -# - TIMER mode will always be switched on after startup mode is complete +# - 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 towel rail will go on -# mqtt_timer_topic/morning-off/08:00 : Time towel rail will go off -# mqtt_timer_topic/evening-on/09:00 : Time towel rail will go on -# mqtt_timer_topic/evening-off/00:00 : Time towel rail will go off -# mqtt_timer_topic/operation/ON : Towel rail permanently on -# mqtt_timer_topic/operation/OFF : Towel rail permanently off -# mqtt_timer_topic/operation/TIMER : Towel rail will obey timer settings -# mqtt_timer_topic/operation/STARTUP : Turn on for (startup_duration) hours then TIMER (also on startup) +# 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) # -############################################# -############################################# +########################################################################################## +########################################################################################## -############################################# +########################################################################################## # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS # If NOT using a secrets file, just replace these with the passwords etc (in quotes) -############################################# +########################################################################################## substitutions: - devicename: "esp-masterbathtowelrail" + # Device Naming + device_name: "esp-masterbathtowelrail" friendly_name: "Master Bathroom Towelrail" description_comment: "Sonoff Basic controlling ON/OFF/Timer for the Heated Towel Rail in the Master Bathroom" + device_area: "Main Bathroom" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Project Naming + project_name: "Sonoff Technologies.Sonoff Basic V1" # Project Details + project_version: "v2.1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + + # Passwords api_key: !secret esp-masterbathtowelrail_api_key # unfortunately you can't use substitutions inside secrets names ota_pass: !secret esp-masterbathtowelrail_ota_pass # unfortunately you can't use substitutions inside secrets names static_ip_address: !secret esp-masterbathtowelrail_ip - log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE - update_interval: "60s" # update time for for general sensors etc - room: "Main Bathroom" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. - ############################################# - # SPECIFIC PROJECT VARIABLE SUBSTITUTIONS - ############################################# + # Device Settings + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "10s" # update time for for general sensors etc + + # Timer Settings mqtt_timer_topic: "viewroad-commands/masterbath-towelrail" # Topics you will use to change stuff - startup_duration: "120" # Minutes to stay ON in STARTUP mode before reverting to TIMER + boost_duration_default: "120" # Minutes to stay ON in BOOST mode before reverting to TIMER + morning_on_default: "300" # Default in minutes from midnight. Default 05:00 => 300 + morning_off_default: "420" # Default in minutes from midnight. Default 07:00 => 420 + evening_on_default: "1260" # Default in minutes from midnight. Default 21:00 => 1260 + evening_off_default: "1440" # Default in minutes from midnight. Default 24:00 => 1440 => 1440 is midnight ############################################# # Included Common Packages @@ -64,12 +78,12 @@ packages: common_wifi: !include file: common/network_common.yaml vars: - local_static_ip_address: ${static_ip_address} - local_ota_pass: ${ota_pass} + 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} + local_api_key: "${api_key}" # common_webportal: !include # file: common/webportal_common.yaml common_mqtt: !include @@ -79,18 +93,18 @@ packages: common_general_sensors: !include file: common/sensors_common.yaml vars: - local_friendly_name: ${friendly_name} - local_update_interval: ${update_interval} + local_friendly_name: "${friendly_name}" + local_update_interval: "${update_interval}" ############################################# # ESPHome # https://esphome.io/components/esphome.html ############################################# esphome: - name: ${devicename} - friendly_name: ${friendly_name} - comment: ${description_comment} # Appears on the esphome page in HA - area: "${room}" + name: "${device_name}" + friendly_name: "${friendly_name}" + comment: "${description_comment}" # Appears on the esphome page in HA + area: "${device_area}" on_boot: priority: 900 # High priority to run after globals are initialized then: @@ -127,9 +141,9 @@ esphome: ESP_LOGI("power_cycle", "Detected 4 power cycles in 20s => Forcing ON mode"); } else { // Otherwise do your normal startup logic: - id(operation_mode) = 3; // on_boot -> sets operation_mode = 3 (STARTUP) - id(startup_timer) = 0; // and reset startup_timer = 0 (for time sync if no sntp) - ESP_LOGI("power_cycle", "Boot count=%d => STARTUP mode", id(boot_count)); + id(operation_mode) = 3; // on_boot -> sets operation_mode = 3 (BOOST) + id(boost_timer) = 0; // and reset boost_timer = 0 (for time sync if no sntp) + ESP_LOGI("power_cycle", "Boot count=%d => BOOST mode", id(boot_count)); } ############################################# @@ -138,13 +152,20 @@ esphome: ############################################# esp8266: board: esp01_1m # The original sonoff basic + restore_from_flash: true # restore some values on reboot + +preferences: + flash_write_interval: 5min + +mdns: + disabled: false ############################################# # ESPHome Logging Enable # https://esphome.io/components/logger.html ############################################# logger: - level: ${log_level} #INFO Level suggested, or DEBUG for testing + 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 @@ -168,39 +189,41 @@ globals: initial_value: "0" # Morning On time (minutes from midnight), - # default 05:00 => 300 - id: morning_on type: int restore_value: true - initial_value: "300" + initial_value: "${morning_on_default}" # Morning Off time (minutes from midnight), - # default 07:00 => 420 - id: morning_off type: int restore_value: true - initial_value: "420" + initial_value: "${morning_off_default}" # Evening On time (minutes from midnight), - # default 21:00 => 1260 - id: evening_on type: int restore_value: true - initial_value: "1260" + initial_value: "${morning_off_default}" # Evening Off time (minutes from midnight), - # default 24:00 => 1440 => treat as midnight - id: evening_off type: int restore_value: true - initial_value: "1440" + initial_value: "${morning_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 = STARTUP + # 3 = BOOST #################################################### - id: operation_mode type: int @@ -219,16 +242,15 @@ globals: initial_value: "720" # 720 is 12:00 Noon #################################################### - # startup_timer: counts minutes in STARTUP mode - # After 'startup_duration' minutes, revert to TIMER. + # boost_timer: counts minutes in BOOST mode + # After 'boost_duration' minutes, revert to TIMER. # Not restored, so each boot starts fresh at 0. #################################################### - - id: startup_timer + - id: boost_timer type: int restore_value: false initial_value: "0" - ############################################# # Text Sensors # https://esphome.io/components/text_sensor/index.html @@ -319,15 +341,37 @@ text_sensor: 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 0–1439 + 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(v); + } + + #################################################### # Subscribe to operation mode: - # OFF, ON, TIMER, STARTUP + # OFF, ON, TIMER, BOOST # We do case-insensitive compare using strcasecmp # (Requires typically included in ESPHome) #################################################### - platform: mqtt_subscribe name: "Operation Mode Setting" id: timer_operation_mode_topic - topic: "${mqtt_timer_topic}/operation" # STARTUP,ON,OFF,TIMER + topic: "${mqtt_timer_topic}/operation" # BOOST,ON,OFF,TIMER internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value on_value: then: @@ -346,29 +390,29 @@ text_sensor: } 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(), "STARTUP") == 0) { + } else if (strcasecmp(x.c_str(), "BOOST") == 0) { id(operation_mode) = 3; - id(startup_timer) = 0; // Reset the startup timer to zero - ESP_LOGI("timer","Operation mode set to STARTUP"); + id(boost_timer) = 0; // Reset the BOOST timer to zero + ESP_LOGI("timer","Operation mode set to BOOST"); } else { ESP_LOGW("timer","Invalid operation mode: %s", x.c_str()); } ###################################################### - # Expose the current operation mode (OFF, ON, TIMER, STARTUP) + # Expose the current operation mode (OFF, ON, TIMER, BOOST) ###################################################### - platform: template name: "Operation Mode State" lambda: |- - // 0=OFF, 1=ON, 2=TIMER, 3=STARTUP + // 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 {"STARTUP"}; + case 3: return {"BOOST"}; default: return {"UNKNOWN"}; } - update_interval: ${update_interval} + update_interval: 5s ###################################################### # Expose the "Morning On" time as a text (HH:MM) @@ -382,7 +426,7 @@ text_sensor: char buff[16]; snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute); return { std::string(buff) }; - update_interval: ${update_interval} + update_interval: "${update_interval}" ###################################################### # Expose the "Morning Off" time as a text (HH:MM) @@ -397,7 +441,7 @@ text_sensor: char buff[16]; snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute); return { std::string(buff) }; - update_interval: ${update_interval} + update_interval: "${update_interval}" ###################################################### # Expose the "Evening On" time as a text (HH:MM) @@ -412,7 +456,7 @@ text_sensor: char buff[16]; snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute); return { std::string(buff) }; - update_interval: ${update_interval} + update_interval: "${update_interval}" ###################################################### # Expose the "Evening Off" time as a text (HH:MM) @@ -427,7 +471,17 @@ text_sensor: char buff[16]; snprintf(buff, sizeof(buff), "%02d:%02d", hour, minute); return { std::string(buff) }; - update_interval: ${update_interval} + update_interval: "${update_interval}" + + ###################################################### + # Expose the "Boost time" time as text (HH:MM) + ###################################################### + - platform: template + name: "Timeclock: Boost Time" + lambda: |- + // e.g. "120 Mins" + return std::to_string(id(boost_duration)) + " Mins"; + update_interval: "${update_interval}" ############################################# # Sensors @@ -438,11 +492,37 @@ sensor: name: "Mins from Midnight" unit_of_measurement: "mins" accuracy_decimals: 0 - update_interval: ${update_interval} + update_interval: "${update_interval}" internal: True 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; + #################################################### # Relay Switch (Sonoff Basic Relay on GPIO12) #################################################### @@ -453,6 +533,62 @@ switch: id: relay restore_mode: RESTORE_DEFAULT_OFF +################################################################################################# +# BUTTON COMPONENT +# https://esphome.io/components/button/index.html +################################################################################################# +button: + - platform: template + name: "Boost now" + id: boost_button + icon: "mdi:play-circle-outline" # optional, pick any MaterialDesign icon you like + on_press: + then: + # 1) set the mode to BOOST (3) + - lambda: |- + id(boost_timer) = 0; // Reset the BOOST timer to zero + id(operation_mode) = 3; // Set to BOOST + ESP_LOGD("main", "operation_mode set to %d via BOOST button", id(operation_mode)); + # 2) turn on the relay switch + - switch.turn_on: + id: relay + +################################################################################################# +# SELECT COMPONENT +# https://esphome.io/components/select/index.html +################################################################################################# +select: + - platform: template + name: "Operation Mode" + id: operation_mode_select + update_interval: 5s # poll every 5 s for external changes + options: + - "OFF" + - "ON" + - "TIMER" + - "BOOST" + + # Getter: maps your integer into one of the four strings + 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"); + } + + # set_action: called when you pick an option in HA + 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 { + id(boost_timer) = 0; // Reset the BOOST timer to zero + id(operation_mode) = 3; /* BOOST */ + } + ESP_LOGD("main", "operation_mode set to %d", id(operation_mode)); + #################################################### # Check every minute to decide relay state #################################################### @@ -482,26 +618,26 @@ interval: // 0 = OFF // 1 = ON // 2 = TIMER - // 3 = STARTUP + // 3 = BOOST int mode = id(operation_mode); ////////////////////////////////////////////////// - // STARTUP MODE: Relay ON for 'startup_duration' + // BOOST MODE: Relay ON for 'boost_duration' // minutes, then automatically revert to TIMER. ////////////////////////////////////////////////// if (mode == 3) { - id(startup_timer) = id(startup_timer) + 1 ; // works as long as update_interval in seconds - // Compare with the substitution startup_duration - if (id(startup_timer) < (int) ${startup_duration}) { - // Still within the STARTUP period => turn relay on + id(boost_timer) = id(boost_timer) + 1 ; // works as long as update_interval in seconds + // Compare with the substitution boost_duration + if (id(boost_timer) < id(boost_duration)) { + // Still within the BOOST period => turn relay on id(relay).turn_on(); } else { - // After 'startup_duration' minutes => switch to TIMER + // After 'boost_duration' minutes => switch to TIMER id(operation_mode) = 2; id(mqtt_client).publish("${mqtt_timer_topic}/operation", "TIMER"); } // Skip the rest of the logic - ESP_LOGI("startup_timer", "startup_timer=%d", id(startup_timer)); + ESP_LOGI("boost_timer", "boost_timer=%d", id(boost_timer)); return; } diff --git a/esphome/esp-occupancyoffice.yaml b/esphome/esp-occupancyoffice.yaml index 886fe01..112d993 100644 --- a/esphome/esp-occupancyoffice.yaml +++ b/esphome/esp-occupancyoffice.yaml @@ -21,7 +21,7 @@ # If NOT using a secrets file, just replace these with the passwords etc (in quotes) ############################################# substitutions: - devicename: "esp-occupancyoffice" + device_name: "esp-occupancyoffice" friendly_name: "Office Occupancy & Environment" description_comment: "D1 Mini ESP32 with LD1125H mmWave and environment sensors for downstairs office" api_key: !secret esp-occupancyoffice_api_key #unfortunately you can't use substitutions inside secrets names @@ -29,7 +29,7 @@ substitutions: static_ip_address: !secret esp-occupancyoffice_ip log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE update_interval: "60s" # update time for for general sensors etc - room: "Office" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + device_area: "Office" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. ############################################# # SPECIFIC PROJECT VARIABLE SUBSTITUTIONS @@ -68,11 +68,11 @@ packages: # https://esphome.io/components/esphome.html ############################################# esphome: - name: ${devicename} + name: ${device_name} friendly_name: ${friendly_name} comment: ${description_comment} #appears on the esphome page in HA min_version: 2024.6.0 - area: "${room}" + area: "${device_area}" on_boot: #LD1125H Initial Setting, will remember previous values (if set) priority: -200 then: diff --git a/esphome/esp-occupancystair.yaml b/esphome/esp-occupancystair.yaml index 6652409..0a8621a 100644 --- a/esphome/esp-occupancystair.yaml +++ b/esphome/esp-occupancystair.yaml @@ -1,13 +1,16 @@ ############################################# ############################################# # HiLink LD2410 mmWave sensor, with BME280 Temp/Hum/Pres Sensor and PIR on an ESP32 +# VERSION +# V2.0 2025-06-05 YAML Tidyups +# # https://github.com/patrick3399/Hi-Link_mmWave_Radar_ESPHome/tree/main # https://github.com/patrick3399/Hi-Link_mmWave_Radar_ESPHome/blob/main/LD1125H/ESP32-LD1125H-Complete.yaml # # https://esphome.io/components/sensor/ld2410.html # https://www.simplysmart.house/blog/presence-detection-ld2410-home-assistant # -# The B and c versions of this device can use Bluetooth, but we are not using it here. +# The B and C versions of this device can use Bluetooth, but we are not using it here. ############################################# ############################################# @@ -16,16 +19,25 @@ # If NOT using a secrets file, just replace these with the passwords etc (in quotes) ############################################# substitutions: - devicename: "esp-occupancystair" + # Device Naming + device_name: "esp-occupancystair" friendly_name: "Stair Occupancy and Underhouse Environment" description_comment: "D1 Mini ESP32 with LD2410 mmWave for internal stairwell and environment sensors for under house" + device_area: "Outside" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Project Naming + project_name: "Generic.ESP32" # Project Details + project_version: "v1.0" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + + # Passwords api_key: !secret esp-occupancystair_api_key # unfortunately you can't use substitutions inside secrets names ota_pass: !secret esp-occupancystair_ota_pass # unfortunately you can't use substitutions inside secrets names static_ip_address: !secret esp-occupancystair_ip + + # Device Settings log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE update_interval: "60s" # update time for for general sensors etc - room: "Outside" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. - + ############################################# # Included Common Packages # https://esphome.io/components/esphome.html @@ -34,14 +46,14 @@ packages: common_wifi: !include file: common/network_common.yaml vars: - local_static_ip_address: ${static_ip_address} - local_ota_pass: ${ota_pass} + 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 + local_api_key: "${api_key}" +# common_webportal: !include +# file: common/webportal_common.yaml common_mqtt: !include file: common/mqtt_common.yaml common_sntp: !include @@ -49,20 +61,20 @@ packages: common_general_sensors: !include file: common/sensors_common.yaml vars: - local_friendly_name: ${friendly_name} - local_update_interval: ${update_interval} + local_friendly_name: "${friendly_name}" + local_update_interval: "${update_interval}" ############################################# # ESPHome # https://esphome.io/components/esphome.html ############################################# esphome: - name: ${devicename} - friendly_name: ${friendly_name} - comment: ${description_comment} #appears on the esphome page in HA + name: "${device_name}" + friendly_name: "${friendly_name}" + comment: "${description_comment}" # appears on the esphome page in HA min_version: 2024.6.0 - area: "${room}" - #on_boot: #Initial Setting, will remember previous values (if set) + area: "${device_area}" + #on_boot: # Initial Setting, will remember previous values (if set) #priority: -200 #then: @@ -73,9 +85,8 @@ esphome: esp32: board: esp32dev framework: - #type: arduino - type: esp-idf #Suggested Use ESP-IDF Framework, or Plug Out the UART Cable Might Cause ESP32 Hang. - version: recommended #recommended, latest or dev + type: esp-idf # "esp-idf" OR "arduino". Suggested ESP-IDF Framework, or Plug Out the UART Cable Might Cause ESP32 Hang. + version: recommended # recommended, latest or dev ############################################# # i2s bus @@ -92,8 +103,8 @@ i2c: # https://esphome.io/components/logger.html ############################################# logger: - level: INFO #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) + level: INFO # 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 @@ -182,58 +193,58 @@ ld2410: number: - platform: ld2410 timeout: - name: Timeout + name: "Timeout" light_threshold: - name: Light Threshold + name: "Light Threshold" max_move_distance_gate: - name: Max Move Distance Gate + name: "Max Move Distance Gate" max_still_distance_gate: - name: Max Still Distance Gate + name: "Max Still Distance Gate" g0: move_threshold: - name: g0 move threshold + name: "g0 move threshold" still_threshold: - name: g0 still threshold + name: "g0 still threshold" g1: move_threshold: - name: g1 move threshold + name: "g1 move threshold" still_threshold: - name: g1 still threshold + name: "g1 still threshold" g2: move_threshold: - name: g2 move threshold + name: "g2 move threshold" still_threshold: - name: g2 still threshold + name: "g2 still threshold" g3: move_threshold: - name: g3 move threshold + name: "g3 move threshold" still_threshold: - name: g3 still threshold + name: "g3 still threshold" g4: move_threshold: - name: g4 move threshold + name: "g4 move threshold" still_threshold: - name: g4 still threshold + name: "g4 still threshold" g5: move_threshold: - name: g5 move threshold + name: "g5 move threshold" still_threshold: - name: g5 still threshold + name: "g5 still threshold" g6: move_threshold: - name: g6 move threshold + name: "g6 move threshold" still_threshold: - name: g6 still threshold + name: "g6 still threshold" g7: move_threshold: - name: g7 move threshold + name: "g7 move threshold" still_threshold: - name: g7 still threshold + name: "g7 still threshold" g8: move_threshold: - name: g8 move threshold + name: "g8 move threshold" still_threshold: - name: g8 still threshold + name: "g8 still threshold" #The ld2410 select allows you to control your LD2410 Sensor. #distance_resolution (Optional): Control the gates distance resolution. Can be 0.75m or 0.2m. Defaults to 0.75m. All options from Select. @@ -244,13 +255,13 @@ number: select: - platform: ld2410 distance_resolution: - name: ${friendly_name} LD2140 Distance Resolution + name: "${friendly_name} LD2140 Distance Resolution" baud_rate: - name: ${friendly_name} LD2140 Baud Rate + name: "${friendly_name} LD2140 Baud Rate" light_function: - name: ${friendly_name} LD2140 Light Function + name: "${friendly_name} LD2140 Light Function" out_pin_level: - name: ${friendly_name} LD2140 Out Pin Level + name: "${friendly_name} LD2140 Out Pin Level" ############################################# # General Sensors @@ -270,7 +281,7 @@ sensor: accuracy_decimals: 1 oversampling: 2x address: 0x76 - update_interval: ${update_interval} + update_interval: "${update_interval}" #The ld2410 sensor values - platform: ld2410 @@ -337,9 +348,9 @@ sensor: switch: - platform: ld2410 engineering_mode: - name: ${friendly_name} LD2140 Engineering Mode + name: "${friendly_name} LD2140 Engineering Mode" #bluetooth: - #name: ${friendly_name} LD2140 Control Bluetooth + #name: "${friendly_name} LD2140 Control Bluetooth" #The ld2410 binary sensors to get presence notification binary_sensor: @@ -369,19 +380,19 @@ binary_sensor: button: - platform: ld2410 factory_reset: - name: ${friendly_name} LD2140 Factory reset" + name: "${friendly_name} LD2140 Factory reset" restart: - name: ${friendly_name} LD2140 Restart + name: "${friendly_name} LD2140 Restart" query_params: - name: Query Parameters + name: "Query Parameters" #The ld2410 text sensor allows you to get information about your LD2410 Sensor. #Bluetooth sensor is only useful of you have a B or C model text_sensor: - platform: ld2410 version: - name: ${friendly_name} LD2140 Firmware Version + name: "${friendly_name} LD2140 Firmware Version" #mac_address: - #name: ${friendly_name} LD2140 BT MAC Address + #name: "${friendly_name} LD2140 BT MAC Address" diff --git a/esphome/esp-officelights.yaml b/esphome/esp-officelights.yaml new file mode 100644 index 0000000..2f001e4 --- /dev/null +++ b/esphome/esp-officelights.yaml @@ -0,0 +1,165 @@ +############################################# +############################################# +# OFFICE MAIN LIGHTSWITCH +# V2.0 2025-06-05 YAML Tidyups +# V1.0 2025-05-31 Initial Version +############################################# +# Zemismart KS-811 Triple push button +# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/ +# +# NOTES +# - +# +############################################# +############################################# + +############################################# +# 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-officelights" + friendly_name: "Office Main Lightswitch (3)" + description_comment: "Office Main Lightswitch using a Zemismart KS-811 Triple Push Button. Bunker Light A (1), Spare A (2), Spare B (3)" + device_area: "Office" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Project Naming + project_name: "Zemismart Technologies.KS-811 Triple" # Project Details + project_version: "v2.0" # 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-officelights_ip + + # Device Settings + log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + + # Switch Naming + switch_1_name: "Nighttime Lights" # Only one light (Bunker Light) actually connected to this relay + switch_2_name: "Daytime Lights" # This is virtual only, no power connected to 2nd relay + switch_3_name: "Spare" # This is virtual only, no power connected to 3rd relay + +############################################# +# Included Common Packages +# https://esphome.io/components/esphome.html +############################################# +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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 + 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}" + +############################################# +# 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}" + project: + name: "${project_name}" + version: "${project_version}" +# on_boot: +# priority: 200 +# then: +# - switch.turn_on: Relay_3 + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m + early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True. + board_flash_mode: dout # Default is dout + +############################################# +# 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 + id: logger_id + +############################################# +# STATUS LED +# https://esphome.io/components/status_led.html +############################################# +status_led: + pin: + number: GPIO2 + inverted: yes + +############################################# +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +############################################# +binary_sensor: + - platform: gpio + pin: + number: GPIO16 + mode: INPUT + inverted: True + name: "Button 1: ${switch_1_name}" + on_press: + - switch.toggle: Relay_1 + + - platform: gpio + pin: + number: GPIO05 + mode: INPUT + inverted: True + name: "Button 2: ${switch_2_name}" + on_press: + - switch.toggle: Relay_2 + + - platform: gpio + pin: + number: GPIO4 + mode: INPUT + inverted: True + name: "Button 3: ${switch_3_name}" + on_press: + - switch.toggle: Relay_3 + +############################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +############################################# +switch: + - platform: gpio + name: "Relay 1: ${switch_1_name}" + pin: GPIO13 + id: Relay_1 + - platform: gpio + name: "Relay 2: ${switch_2_name}" + pin: GPIO12 + id: Relay_2 + - platform: gpio + name: "Relay 3: ${switch_3_name}" + pin: GPIO14 + id: Relay_3 \ No newline at end of file diff --git a/esphome/esp-poollightpower.yaml b/esphome/esp-poollightpower.yaml new file mode 100644 index 0000000..ac10728 --- /dev/null +++ b/esphome/esp-poollightpower.yaml @@ -0,0 +1,805 @@ +########################################################################################## +########################################################################################## +# POOL LIGHT POWER AND TIMER +# Controlled by a Athom Smart Plug V1 +# package_import_url: github://athom-tech/athom-configs/athom-smart-plug.yaml +# +# V2.0 2025-06-05 YAML Tidyups +# +# INSTRUCTIONS +# - It allows a device to work in a standalone operation +# - On startup, it will turn on for (startup_duration) hours then go into timer mode +# - The timer has a morning and evening time (but no weekday/weekend setting) +# - 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 4 on/off times via MQTT (See below commands) +# - You can set 4 modes ON/OFF/TIMER/STARTUP via MQTT. That way, you can set to STARTUP for a short boost +# - Any new timer times set via MQTT will be remembered though a reboot +# - On a reboot, the device will always turn on for the Startup Duration (STARTUP mode, default 2 hours) +# - TIMER mode will always be switched on after startup mode is complete +# - 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/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/STARTUP : Turn on device for (startup_duration) hours then TIMER (also on startup) +# +# operation_mode: +# 0 = OFF +# 1 = ON +# 2 = TIMER +# 3 = STARTUP +# +########################################################################################## +########################################################################################## + + + +########################################################################################## +# 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 Monitor" + device_area: "Downstairs Kitchen" # 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.0" # 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: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + + # Timer Settings + relay_icon: "mdi:light-flood-up" + 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 + startup_duration: "180" # Minutes to stay ON in STARTUP 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 +# https://esphome.io/components/esphome.html +########################################################################################## +packages: + common_wifi: !include + file: common/network_common.yaml + vars: + 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 + 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}" + +########################################################################################## +# 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}" + +########################################################################################## +# 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 + +preferences: + flash_write_interval: 5min + +mdns: + disabled: false + +#dashboard_import: +# package_import_url: github://athom-tech/esp32-configs/athom-smart-plug.yaml + +########################################################################################## +# ESPHome LOGGING +# 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 +# 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}" + + #################################################### + # operation_mode: + # 0 = OFF + # 1 = ON + # 2 = TIMER + # 3 = STARTUP + #################################################### + - id: operation_mode + type: int + restore_value: false + 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. + # Not restored, so it resets each boot. + #################################################### + - id: current_mins + type: int + restore_value: false + initial_value: "720" # 720 is 12:00 Noon + + #################################################### + # startup_timer: counts minutes in STARTUP mode + # After 'startup_duration' minutes, revert to TIMER. + # Not restored, so each boot starts fresh at 0. + #################################################### + - id: startup_timer + type: int + restore_value: false + initial_value: "0" + + #################################################### + # total_energy: cumulative power + # Restored, so keeps counting up. + #################################################### + - id: total_energy + type: float + restore_value: yes + initial_value: "0.0" + +########################################################################################## +# UART Bus +# https://esphome.io/components/uart.html +########################################################################################## +#uart: +# rx_pin: RX +# baud_rate: 4800 +# parity: EVEN + +########################################################################################## +# STATUS LED +# https://esphome.io/components/status_led.html +########################################################################################## +status_led: + pin: + number: GPIO13 + inverted: True + +########################################################################################## +# 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()); + } + #################################################### + # Subscribe to operation mode: + # OFF, ON, TIMER, STARTUP + # We do case-insensitive compare using strcasecmp + # (Requires typically included in ESPHome) + #################################################### + - platform: mqtt_subscribe + name: "Operation Mode Setting" + id: timer_operation_mode_topic + topic: "${mqtt_timer_topic}/operation" # STARTUP,ON,OFF,TIMER + internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value + on_value: + then: + - lambda: |- + /* + * In standard C++ (ESPHome), no 'equalsIgnoreCase()'. + * We use 'strcasecmp' for case-insensitive compare. + * Returns 0 if they match ignoring case. + */ + 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(), "STARTUP") == 0) { + id(operation_mode) = 3; + id(startup_timer) = 0; // Reset the startup timer to zero + ESP_LOGI("timer","Operation mode set to STARTUP"); + } else { + ESP_LOGW("timer","Invalid operation mode: %s", x.c_str()); + } + + ###################################################### + # Expose the current operation mode (OFF, ON, TIMER, STARTUP) + ###################################################### + - platform: template + name: "Operation Mode State" + lambda: |- + // 0=OFF, 1=ON, 2=TIMER, 3=STARTUP + switch (id(operation_mode)) { + case 0: return {"OFF"}; + case 1: return {"ON"}; + case 2: return {"TIMER"}; + case 3: return {"STARTUP"}; + default: return {"UNKNOWN"}; + } + #update_interval: "${update_interval}" + 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: 3 + 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 (Startup) + 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: "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 calculation for total energy used, ever (so long as wuth esp8266 restore_from_flash: true) + - platform: template + name: "Total Energy" + id: total_energy_sensor + unit_of_measurement: "kWh" + device_class: "energy" + state_class: "total_increasing" + icon: mdi:lightning-bolt + accuracy_decimals: "3" + lambda: |- + return id(total_energy); + update_interval: "${update_interval}" + + # 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: 10s + 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 (startup) + 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 = ${startup_duration} - id(startup_timer); + } + // never return negative + return rem > 0 ? rem : 0; + + ############################################# + # CSE7766 POWER SENSOR + # https://esphome.io/components/sensor/cse7766.html + ############################################# + - platform: hlw8012 + id: athom_hlw8012 + sel_pin: + number: GPIO12 + inverted: True + cf_pin: GPIO4 + cf1_pin: GPIO5 + voltage_divider: 780 + + current: + name: "Current" + id: current + unit_of_measurement: A + accuracy_decimals: 2 + icon: mdi:current-ac + filters: + - calibrate_linear: + - 0.0000 -> 0.0110 # Relay off no load + - 0.0097 -> 0.0260 # Relay on no load + - 0.9270 -> 0.7570 + - 2.0133 -> 1.6330 + - 2.9307 -> 2.3750 + - 5.4848 -> 4.4210 + - 8.4308 -> 6.8330 + - 9.9171 -> 7.9830 + # Normalize for plug load + - lambda: if (x < 0.0260) return 0; else return (x - 0.0260); + on_value_range: + - above: ${current_limit} + then: + - switch.turn_off: relay + + voltage: + name: "Voltage" + id: voltage + unit_of_measurement: V + accuracy_decimals: 1 + icon: mdi:sine-wave + filters: + - skip_initial: 2 + + power: + name: "Power" + id: power_sensor + unit_of_measurement: W + accuracy_decimals: 1 + icon: mdi:power + filters: + - calibrate_linear: + - 0.0000 -> 0.5900 # Relay off no load + - 0.0000 -> 1.5600 # Relay on no load + - 198.5129 -> 87.8300 + - 434.2469 -> 189.5000 + - 628.6241 -> 273.9000 + - 1067.0067 -> 460.1000 + - 1619.8098 -> 699.2000 + - 2043.0282 -> 885.0000 + # Normalize for plug load + - lambda: if (x < 1.5600) return 0; else return (x - 1.5600); + change_mode_every: 1 + update_interval: 5s + + # Shows the Energy kWh since the device was last started + energy: + name: "Energy (Since Restart)" + id: energy + icon: mdi:lightning-bolt + unit_of_measurement: kWh + accuracy_decimals: 3 + filters: + # Multiplication factor from W to kW is 0.001 + - multiply: 0.001 + on_value: + then: + - lambda: |- + static float previous_energy_value = 0.0; + float current_energy_value = id(energy).state; + id(total_energy) += current_energy_value - previous_energy_value; + previous_energy_value = current_energy_value; + id(total_energy_sensor).update(); + # internal: ${hide_energy_sensor} + + ############################################# + # Total Daily Energy + # https://esphome.io/components/sensor/total_daily_energy.html + ############################################# + - platform: total_daily_energy + name: "Total Daily Energy" + restore: true + power_id: power_sensor + unit_of_measurement: kWh + icon: mdi:hours-24 + accuracy_decimals: 3 + filters: + - multiply: 0.001 + +################################################################################################# +# 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: "Startup: ${startup_duration} Minutes" + id: startup_button + icon: "mdi:play-circle-outline" # optional, pick any MaterialDesign icon you like + on_press: + then: + # 1) set the mode to STARTUP (3) + - lambda: |- + id(startup_timer) = 0; // Reset the startup timer to zero + id(operation_mode) = 3; // Set to STARTUP + ESP_LOGD("main", "operation_mode set to %d via STARTUP button", id(operation_mode)); + # 2) turn on the relay switch + - switch.turn_on: + id: relay + +################################################################################################# +# SELECT COMPONENT +# https://esphome.io/components/select/index.html +################################################################################################# +select: + - platform: template + name: "Operation Mode" + id: operation_mode_select + update_interval: 5s # poll every 5 s for external changes + options: + - "OFF" + - "ON" + - "TIMER" + - "STARTUP" + + # Getter: maps your integer into one of the four strings + lambda: |- + switch (id(operation_mode)) { + case 1: return std::string("ON"); + case 2: return std::string("TIMER"); + case 3: return std::string("STARTUP"); + default: return std::string("OFF"); + } + + # set_action: called when you pick an option in HA + 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 { + id(startup_timer) = 0; // Reset the startup timer to zero + id(operation_mode) = 3; /* STARTUP */ + } + ESP_LOGD("main", "operation_mode set to %d", id(operation_mode)); + +################################################################################################# +# INTERVAL COMPONENT +# https://esphome.io/components/interval.html +################################################################################################# +interval: + - interval: "1min" # Must be 1min as this is used to calculate times + then: + - lambda: |- + + // Do we have correct time from SNTP? If not... + if (!id(time_sync).has_state()) { + // Set minutes since midnight + id(current_mins) = id(current_mins) +1 ; + + // wrap around at 1440 => next day + if (id(current_mins) >= 1440) { + id(current_mins) = 0; + } + + // If we do have proper SNMP time... + } else { + // Use real time from SNTP + auto now = id(sntp_time).now(); + id(current_mins) = now.hour * 60 + now.minute; + } + + // operation_mode: + // 0 = OFF + // 1 = ON + // 2 = TIMER + // 3 = STARTUP + int mode = id(operation_mode); + + ////////////////////////////////////////////////// + // STARTUP MODE: Relay ON for 'startup_duration' + // minutes, then automatically revert to TIMER. + ////////////////////////////////////////////////// + if (mode == 3) { + id(startup_timer) = id(startup_timer) + 1 ; // works as long as update_interval in seconds + // Compare with the substitution startup_duration + if (id(startup_timer) < (int) ${startup_duration}) { + // Still within the STARTUP period => turn relay on + id(relay).turn_on(); + } else { + // After 'startup_duration' minutes => switch to TIMER + id(operation_mode) = 2; + id(mqtt_client).publish("${mqtt_timer_topic}/operation", "TIMER"); + } + // Skip the rest of the logic + ESP_LOGI("startup_timer", "startup_timer=%d", id(startup_timer)); + return; + } + + ////////////////////////////////////////////////// + // OFF MODE => always off + ////////////////////////////////////////////////// + if (mode == 0) { + id(relay).turn_off(); + return; + } + + ////////////////////////////////////////////////// + // ON MODE => always on + ////////////////////////////////////////////////// + if (mode == 1) { + id(relay).turn_on(); + return; + } + + ////////////////////////////////////////////////// + // TIMER MODE => follow morning/evening schedule + // using SNTP if valid, else current_mins + ////////////////////////////////////////////////// + if (mode == 2) + { + bool should_on = false; + + // Check morning window + // Example: morning_on=360 => 06:00, morning_off=480 => 08:00 + // If current_mins in [360..480), should_on = true + if (id(current_mins) >= id(morning_on) && id(current_mins) < id(morning_off) ) + { + should_on = true; + } + + // Check evening window + // Example: evening_on=1260 => 21:00, evening_off=1440 => midnight + if (id(current_mins) >= id(evening_on) && id(current_mins) < id(evening_off) ) + { + should_on = true; + } + + // Final relay state based on schedule + if (should_on) { + id(relay).turn_on(); + } else { + id(relay).turn_off(); + } + } + diff --git a/esphome/esp-poolpumppower.yaml b/esphome/esp-poolpumppower.yaml index 9fbc012..66b2142 100644 --- a/esphome/esp-poolpumppower.yaml +++ b/esphome/esp-poolpumppower.yaml @@ -1,51 +1,95 @@ -############################################# -############################################# -# Athom Smart Plug Power Monitor ESP32-C3 +########################################################################################## +########################################################################################## +# POOL PUMP POWER AND tiMEr +# Controlled by a Athom Smart Plug V3 +# +# dashboard_import: +# package_import_url: github://athom-tech/esp32-configs/athom-smart-plug.yaml +# +# V2.0 2025-06-05 YAML Tidyups # -# based on https://github.com/athom-tech/esp32-configs/blob/main/athom-smart-plug.yaml +# INSTRUCTIONS +# - It allows a device to work in a standalone operation +# - On startup, it will turn on for (startup_duration) hours then go into timer mode +# - The timer has a morning and evening time (but no weekday/weekend setting) +# - 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 4 on/off times via MQTT (See below commands) +# - You can set 4 modes ON/OFF/TIMER/STARTUP via MQTT. That way, you can set to STARTUP for a short boost +# - Any new timer times set via MQTT will be remembered though a reboot +# - On a reboot, the device will always turn on for the Startup Duration (STARTUP mode, default 2 hours) +# - TIMER mode will always be switched on after startup mode is complete +# - 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) # -# SUMMARY -# Smart plug with power monitoring. +# 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/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/STARTUP : Turn on device for (startup_duration) hours then TIMER (also on startup) # -############################################# -############################################# +# operation_mode: +# 0 = OFF +# 1 = ON +# 2 = TIMER +# 3 = STARTUP +# +########################################################################################## +########################################################################################## -substitutions: -############################################# +########################################################################################## # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS # If NOT using a secrets file, just replace these with the passwords etc (in quotes) -############################################# - devicename: "esp-poolpumppower" +########################################################################################## +substitutions: + # Device Naming + device_name: "esp-poolpumppower" friendly_name: "Pool Pump Power" description_comment: "Pool Pump Power, Athom Smart Plug Power Monitor" + device_area: "Outside" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Project Naming + project_name: "Athom Technology.Smart Plug V3" # Project Details + project_version: "v1.0.7" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + + # Passwords api_key: !secret esp-poolpumppower_api_key # unfortunately you can't use substitutions inside secrets names ota_pass: !secret esp-poolpumppower_ota_pass # unfortunately you can't use substitutions inside secrets names static_ip_address: !secret esp-poolpumppower_ip + + # Device Settings log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE - update_interval: "10s" # update time for for general sensors etc - room: "Outside" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + update_interval: "60s" # update time for for general sensors etc - ############################################# - # SPECIFIC PROJECT VARIABLE SUBSTITUTIONS - ############################################# - project_name: "Athom Technology.Smart Plug V3" # Project Details - project_version: "v1.0.7" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + # Timer Settings + relay_icon: "mdi:pool" current_limit : "10" # Current Limit in Amps. AU Plug = 10. IL, BR, EU, UK, US Plug = 16. + mqtt_timer_topic: "viewroad-commands/poolpump-timer" # Topics you will use to change stuff + startup_duration: "60" # Minutes to stay ON in STARTUP 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: "1260" # Default in minutes from midnight. Default 21:00 => 1260 + evening_off_default: "1320" # Default in minutes from midnight. Default 22:00 => 1320 => 1440 is midnight -############################################# -# Included Common Packages +########################################################################################## +# PACKAGES # https://esphome.io/components/esphome.html -############################################# +########################################################################################## packages: common_wifi: !include file: common/network_common.yaml vars: - local_static_ip_address: ${static_ip_address} - local_ota_pass: ${ota_pass} + 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} + local_api_key: "${api_key}" # common_webportal: !include # file: common/webportal_common.yaml common_mqtt: !include @@ -55,18 +99,18 @@ packages: common_general_sensors: !include file: common/sensors_common.yaml vars: - local_friendly_name: ${friendly_name} - local_update_interval: ${update_interval} + local_friendly_name: "${friendly_name}" + local_update_interval: "${update_interval}" -############################################# +########################################################################################## # ESPHome # https://esphome.io/components/esphome.html -############################################# +########################################################################################## esphome: - name: ${devicename} - friendly_name: ${friendly_name} - comment: ${description_comment} #Appears on the esphome page in HA - area: "${room}" + 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: @@ -77,10 +121,10 @@ esphome: board_build.variant: esp32c3 board_build.flash_mode: dio -############################################# +########################################################################################## # ESP Platform and Framework # https://esphome.io/components/esp32.html -############################################# +########################################################################################## esp32: board: esp32-c3-devkitm-1 flash_size: 4MB @@ -92,22 +136,107 @@ esp32: preferences: flash_write_interval: 5min -############################################# -# ESPHome Logging Enable +esp32_improv: + authorizer: none + +########################################################################################## +# ESPHome LOGGING # https://esphome.io/components/logger.html -############################################# +########################################################################################## logger: - level: ${log_level} #INFO Level suggested, or DEBUG for testing + 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 -esp32_improv: - authorizer: none +########################################################################################## +# GLOBAL VARIABLES +# https://esphome.io/guides/automations.html?highlight=globals#global-variables +########################################################################################## +globals: -dashboard_import: - package_import_url: github://athom-tech/esp32-configs/athom-smart-plug.yaml + # 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}" + + #################################################### + # operation_mode: + # 0 = OFF + # 1 = ON + # 2 = TIMER + # 3 = STARTUP + #################################################### + - id: operation_mode + type: int + restore_value: false + 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. + # Not restored, so it resets each boot. + #################################################### + - id: current_mins + type: int + restore_value: false + initial_value: "720" # 720 is 12:00 Noon + + #################################################### + # startup_timer: counts minutes in STARTUP mode + # After 'startup_duration' minutes, revert to TIMER. + # Not restored, so each boot starts fresh at 0. + #################################################### + - id: startup_timer + type: int + restore_value: false + initial_value: "0" + + #################################################### + # total_energy: cumulative power + # Not restored, so each boot starts fresh at 0. + #################################################### + - id: total_energy + type: float + restore_value: yes + initial_value: '0.0' + +########################################################################################## +# UART Bus +# https://esphome.io/components/uart.html +########################################################################################## uart: rx_pin: GPIO20 baud_rate: 4800 @@ -115,12 +244,219 @@ uart: stop_bits: 1 parity: EVEN -globals: - - id: total_energy - type: float - restore_value: yes - initial_value: '0.0' +########################################################################################## +# STATUS LED +# https://esphome.io/components/status_led.html +########################################################################################## +status_led: + pin: + number: GPIO06 + inverted: False +########################################################################################## +# 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()); + } + #################################################### + # Subscribe to operation mode: + # OFF, ON, TIMER, STARTUP + # We do case-insensitive compare using strcasecmp + # (Requires typically included in ESPHome) + #################################################### + - platform: mqtt_subscribe + name: "Operation Mode Setting" + id: timer_operation_mode_topic + topic: "${mqtt_timer_topic}/operation" # STARTUP,ON,OFF,TIMER + internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value + on_value: + then: + - lambda: |- + /* + * In standard C++ (ESPHome), no 'equalsIgnoreCase()'. + * We use 'strcasecmp' for case-insensitive compare. + * Returns 0 if they match ignoring case. + */ + 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(), "STARTUP") == 0) { + id(operation_mode) = 3; + id(startup_timer) = 0; // Reset the startup timer to zero + ESP_LOGI("timer","Operation mode set to STARTUP"); + } else { + ESP_LOGW("timer","Invalid operation mode: %s", x.c_str()); + } + + ###################################################### + # Expose the current operation mode (OFF, ON, TIMER, STARTUP) + ###################################################### + - platform: template + name: "Operation Mode State" + lambda: |- + // 0=OFF, 1=ON, 2=TIMER, 3=STARTUP + switch (id(operation_mode)) { + case 0: return {"OFF"}; + case 1: return {"ON"}; + case 2: return {"TIMER"}; + case 3: return {"STARTUP"}; + 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: @@ -132,88 +468,38 @@ binary_sensor: filters: - delayed_on: 20ms on_click: - - switch.toggle: relay + - 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 (Startup) + id(relay).turn_on(); + id(operation_mode) = 3; + } + - platform: template name: "Relay Status" lambda: |- return id(relay).state; -status_led: - pin: GPIO06 - -switch: - - platform: gpio - name: "Power Output" - pin: GPIO5 - 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: "mdi:power-socket-au" - +########################################################################################## +# Sensors +# https://esphome.io/components/text_sensor/index.html +########################################################################################## sensor: - - platform: cse7766 - id: athom_cse7766 - - current: - name: "Current" - icon: mdi:current-ac - filters: - - throttle_average: ${update_interval} - - lambda: if (x < 0.060) return 0.0; else return x; #For the chip will report less than 3w power when no load is connected - on_value_range: - - above: ${current_limit} - then: - - switch.turn_off: relay - - voltage: - name: "Voltage" - icon: mdi:sine-wave - filters: - - throttle_average: ${update_interval} - - power: - name: "Power" - id: power_sensor - icon: mdi:power - filters: - - throttle_average: ${update_interval} - - lambda: if (x < 3.0) return 0.0; else return x; #For the chip will report less than 3w power when no load is connected - - energy: - name: "Energy" - id: energy - icon: mdi:lightning-bolt - unit_of_measurement: kWh - filters: - - throttle: ${update_interval} - # Multiplication factor from W to kW is 0.001 - - multiply: 0.001 - on_value: - then: - - lambda: |- - static float previous_energy_value = 0.0; - float current_energy_value = id(energy).state; - id(total_energy) += current_energy_value - previous_energy_value; - previous_energy_value = current_energy_value; - id(total_energy_sensor).update(); - - apparent_power: - name: "Apparent Power" - icon: mdi:power - filters: - - throttle_average: ${update_interval} - - reactive_power: - name: "Reactive Power" - icon: mdi:flash - filters: - - throttle_average: ${update_interval} - - power_factor: - name: "Power Factor" - icon: mdi:percent-outline - filters: - - throttle_average: ${update_interval} + - platform: template + name: "Mins from Midnight" + unit_of_measurement: "mins" + accuracy_decimals: 0 + update_interval: "${update_interval}" + internal: True # No need to show this in Home Assistanti + lambda: |- + return id(current_mins); - platform: template name: "Total Energy" @@ -225,8 +511,99 @@ sensor: accuracy_decimals: 3 lambda: |- return id(total_energy); - update_interval: ${update_interval} + update_interval: "${update_interval}" + # 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: 10s + 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 (startup) + 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 = ${startup_duration} - id(startup_timer); + } + // never return negative + return rem > 0 ? rem : 0; + + ############################################# + # CSE7766 POWER SENSOR + # https://esphome.io/components/sensor/cse7766.html + ############################################# + - platform: cse7766 + id: athom_cse7766 + current: + name: "Current" + icon: mdi:current-ac + filters: + - throttle_average: "${update_interval}" + - lambda: if (x < 0.060) return 0.0; else return x; #For the chip will report less than 3w power when no load is connected + on_value_range: + - above: "${current_limit}" + then: + - switch.turn_off: relay + voltage: + name: "Voltage" + icon: mdi:sine-wave + filters: + - throttle_average: "${update_interval}" + power: + name: "Power" + id: power_sensor + icon: mdi:power + filters: + - throttle_average: "${update_interval}" + - lambda: if (x < 3.0) return 0.0; else return x; #For the chip will report less than 3w power when no load is connected + energy: + name: "Energy" + id: energy + icon: mdi:lightning-bolt + unit_of_measurement: kWh + filters: + - throttle: "${update_interval}" + # Multiplication factor from W to kW is 0.001 + - multiply: 0.001 + on_value: + then: + - lambda: |- + static float previous_energy_value = 0.0; + float current_energy_value = id(energy).state; + id(total_energy) += current_energy_value - previous_energy_value; + previous_energy_value = current_energy_value; + id(total_energy_sensor).update(); + apparent_power: + name: "Apparent Power" + icon: mdi:power + filters: + - throttle_average: "${update_interval}" + reactive_power: + name: "Reactive Power" + icon: mdi:flash + filters: + - throttle_average: "${update_interval}" + power_factor: + name: "Power Factor" + icon: mdi:percent-outline + filters: + - throttle_average: "${update_interval}" + + ############################################# + # Total Daily Energy + # https://esphome.io/components/sensor/total_daily_energy.html + ############################################# - platform: total_daily_energy name: "Total Daily Energy" restore: true @@ -237,6 +614,173 @@ sensor: filters: - multiply: 0.001 +################################################################################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +################################################################################################# +switch: + - platform: gpio + name: "Power Output" + pin: GPIO5 + 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: "Startup: ${startup_duration} Minutes" + id: startup_button + icon: "mdi:play-circle-outline" # optional, pick any MaterialDesign icon you like + on_press: + then: + # 1) set the mode to STARTUP (3) + - lambda: |- + id(startup_timer) = 0; // Reset the startup timer to zero + id(operation_mode) = 3; // Set to STARTUP + ESP_LOGD("main", "operation_mode set to %d via STARTUP button", id(operation_mode)); + # 2) turn on the relay switch + - switch.turn_on: + id: relay +################################################################################################# +# SELECT COMPONENT +# https://esphome.io/components/select/index.html +################################################################################################# +select: + - platform: template + name: "Operation Mode" + id: operation_mode_select + update_interval: 5s # poll every 5 s for external changes + options: + - "OFF" + - "ON" + - "TIMER" + - "STARTUP" + # Getter: maps your integer into one of the four strings + lambda: |- + switch (id(operation_mode)) { + case 1: return std::string("ON"); + case 2: return std::string("TIMER"); + case 3: return std::string("STARTUP"); + default: return std::string("OFF"); + } + + # set_action: called when you pick an option in HA + 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 { + id(startup_timer) = 0; // Reset the startup timer to zero + id(operation_mode) = 3; /* STARTUP */ + } + ESP_LOGD("main", "operation_mode set to %d", id(operation_mode)); + +################################################################################################# +# INTERVAL COMPONENT +# https://esphome.io/components/interval.html +################################################################################################# +interval: + - interval: "1min" # Must be 1min as this is used to calculate times + then: + - lambda: |- + + // Do we have correct time from SNTP? If not... + if (!id(time_sync).has_state()) { + // Set minutes since midnight + id(current_mins) = id(current_mins) +1 ; + + // wrap around at 1440 => next day + if (id(current_mins) >= 1440) { + id(current_mins) = 0; + } + + // If we do have proper SNMP time... + } else { + // Use real time from SNTP + auto now = id(sntp_time).now(); + id(current_mins) = now.hour * 60 + now.minute; + } + + // operation_mode: + // 0 = OFF + // 1 = ON + // 2 = TIMER + // 3 = STARTUP + int mode = id(operation_mode); + + ////////////////////////////////////////////////// + // STARTUP MODE: Relay ON for 'startup_duration' + // minutes, then automatically revert to TIMER. + ////////////////////////////////////////////////// + if (mode == 3) { + id(startup_timer) = id(startup_timer) + 1 ; // works as long as update_interval in seconds + // Compare with the substitution startup_duration + if (id(startup_timer) < (int) ${startup_duration}) { + // Still within the STARTUP period => turn relay on + id(relay).turn_on(); + } else { + // After 'startup_duration' minutes => switch to TIMER + id(operation_mode) = 2; + id(mqtt_client).publish("${mqtt_timer_topic}/operation", "TIMER"); + } + // Skip the rest of the logic + ESP_LOGI("startup_timer", "startup_timer=%d", id(startup_timer)); + return; + } + + ////////////////////////////////////////////////// + // OFF MODE => always off + ////////////////////////////////////////////////// + if (mode == 0) { + id(relay).turn_off(); + return; + } + + ////////////////////////////////////////////////// + // ON MODE => always on + ////////////////////////////////////////////////// + if (mode == 1) { + id(relay).turn_on(); + return; + } + + ////////////////////////////////////////////////// + // TIMER MODE => follow morning/evening schedule + // using SNTP if valid, else current_mins + ////////////////////////////////////////////////// + if (mode == 2) + { + + bool should_on = false; + + // Check morning window + // Example: morning_on=360 => 06:00, morning_off=480 => 08:00 + // If current_mins in [360..480), should_on = true + if (id(current_mins) >= id(morning_on) && id(current_mins) < id(morning_off) ) + { + should_on = true; + } + + // Check evening window + // Example: evening_on=1260 => 21:00, evening_off=1440 => midnight + if (id(current_mins) >= id(evening_on) && id(current_mins) < id(evening_off) ) + { + should_on = true; + } + + // Final relay state based on schedule + if (should_on) { + id(relay).turn_on(); + } else { + id(relay).turn_off(); + } + } + diff --git a/group/basement_lights.yaml b/group/basement_lights.yaml index 8a9fb79..a6a39ce 100644 --- a/group/basement_lights.yaml +++ b/group/basement_lights.yaml @@ -3,7 +3,9 @@ basement_lights: entities: - switch.tasmo_s4chan_4231_underhouselights_a - switch.tasmo_s4chan_4231_underhouselights_b - - switch.tasmo_ks811t_7846_officelights2_a - - switch.tasmo_ks811t_7846_officelights2_b - switch.tasmo_sdual_7681_officelights1_a - switch.tasmo_sdual_7681_officelights1_b + - switch.esp_downstkitchlights_relay_1_dining_light + - switch.esp_downstkitchlights_relay_2_kitchen_light + - switch.esp_officelights_relay_1_nighttime_lights + - switch.esp_officelights_relay_2_daytime_lights diff --git a/group/foxhole_lights.yaml b/group/foxhole_lights.yaml index ee69ad4..4c195a0 100644 --- a/group/foxhole_lights.yaml +++ b/group/foxhole_lights.yaml @@ -1,5 +1,5 @@ foxhole_lights: - name: Foxhole Lights + name: Downstairs Lights # Doesn't include the outdoor switch lights entities: - switch.tasmo_ks811t_0707_downstloun_2a # Lounge Main Lights @@ -8,8 +8,6 @@ foxhole_lights: - switch.tasmo_ks811t_3642_downstbed1_1a # Main Bedroom, Main Lights - switch.tasmo_ks811t_3642_downstbed1_1b # Main Bedroom, Wardrobe Light - switch.tasmo_ks811t_3642_downstbed1_1c # Main Bedroom, Bedside switch - - switch.tasmo_ks811t_2192_downstkitch_1a # Kitchen Entry Lights - - switch.tasmo_ks811t_2192_downstkitch_1b # Kitchen Main Lights - - switch.tasmo_ks811t_2192_downstkitch_1c # Kitchen (Unused, future strip lights) - - switch.esp_downstbathswitch_relay_main_light # Downstairs Bathroom Main Lights - - switch.esp_downstbathswitch_relay_cabinet_light # Downstairs Bathroom Mirror Lights + - switch.esp_downstkitchlights_relay_1_dining_light + - switch.esp_downstkitchlights_relay_2_kitchen_light + - switch.esp_downstkitchlights_relay_3_extract_fan diff --git a/packages/pool_light_switch_sync.yaml b/packages/pool_light_switch_sync.yaml index 495e5b5..11b0581 100644 --- a/packages/pool_light_switch_sync.yaml +++ b/packages/pool_light_switch_sync.yaml @@ -1,17 +1,60 @@ automation: - - alias: Turn on pool light with downstairs lounge switch + - id: poollight_sync_tasmo_on + alias: "Sync TASMO → Pool Light (turn ON)" trigger: - platform: state - entity_id: switch.tasmo_ks811t_0707_downstloun_2c - to: "on" + - platform: state + entity_id: switch.tasmo_ks811t_0707_downstloun_2c + to: "on" + condition: + - condition: state + entity_id: switch.esp_poollightpower_power_output + state: "off" action: - service: homeassistant.turn_on - entity_id: switch.tasmo_athplug_5103_1 - - alias: Turn off pool light with downstairs lounge switch + - service: switch.turn_on + target: + entity_id: switch.esp_poollightpower_power_output + + - id: poollight_sync_tasmo_off + alias: "Sync TASMO → Pool Light (turn OFF)" trigger: - platform: state - entity_id: switch.tasmo_ks811t_0707_downstloun_2c - to: "off" + - platform: state + entity_id: switch.tasmo_ks811t_0707_downstloun_2c + to: "off" + condition: + - condition: state + entity_id: switch.esp_poollightpower_power_output + state: "on" action: - service: homeassistant.turn_off - entity_id: switch.tasmo_athplug_5103_1 + - service: switch.turn_off + target: + entity_id: switch.esp_poollightpower_power_output + + - id: poollight_sync_athplug_on + alias: "Sync Pool Light → TASMO (turn ON)" + trigger: + - platform: state + entity_id: switch.esp_poollightpower_power_output + to: "on" + condition: + - condition: state + entity_id: switch.tasmo_ks811t_0707_downstloun_2c + state: "off" + action: + - service: switch.turn_on + target: + entity_id: switch.tasmo_ks811t_0707_downstloun_2c + + - id: poollight_sync_athplug_off + alias: "Sync Pool Light → TASMO (turn OFF)" + trigger: + - platform: state + entity_id: switch.esp_poollightpower_power_output + to: "off" + condition: + - condition: state + entity_id: switch.tasmo_ks811t_0707_downstloun_2c + state: "on" + action: + - service: switch.turn_off + target: + entity_id: switch.tasmo_ks811t_0707_downstloun_2c diff --git a/packages/simulation_lights.yaml b/packages/simulation_lights.yaml index 5b101e1..bb1c3ba 100644 --- a/packages/simulation_lights.yaml +++ b/packages/simulation_lights.yaml @@ -1,29 +1,7 @@ -#simulation_lights: -# name: Simulation Lights -# # Lights included in 'away from home' random patterns -# entities: -# - switch.tasmo_ks811d_1242_entrance_a # Entranceway Main Lights -# - switch.tasmo_ks811d_0302_entrybath_a # Entranceway Guest Bathroom Lights -# - switch.tasmo_ks811d_6110_kitchen_a # Main Kitchen, Main Lights -# - switch.tasmo_ks811d_6110_kitchen_b # Main Kitchen, Bench Lights -# - switch.tasmo_ks811s_2940_hallway_1a # Hallway Main Lights -# - switch.tasmo_ks811d_1701_stairs_2a # Stairs, Lower ceiling lights -# - switch.tasmo_ks811t_0702_lounge_3a # Lounge Main, South -# - switch.tasmo_ks811t_0702_lounge_3b # Lounge Main, Middle -# - switch.tasmo_ks811t_0702_lounge_3c # Lounge Main, North (above stairs) -# - switch.tasmo_ks811t_0707_downstloun_2a # Foxhole Lounge Main Lights -# - switch.tasmo_ks811t_0707_downstloun_2b # Foxhole Lounge Wall Lights -# - switch.tasmo_ks811s_3136_downstbed2_1a # Foxhole Craft Room Lights -# - switch.tasmo_ks811t_3642_downstbed1_1a # Foxhole Main Bedroom, Main Lights -# - switch.tasmo_ks811t_2192_downstkitch_1a # Foxhole Kitchen Entry Lights -# - switch.tasmo_ks811t_2192_downstkitch_1b # Foxhole Kitchen Main Lights -# - switch.tasmo_ks811t_1181_downstbath_a # Foxhole Downstairs Bathroom Main Lights -# - switch.tasmo_s4chan_4231_underhouselights_b # Underhouse Main Lights - switch: - platform: group name: Simulation Lights - # Lights included in 'away from home' random patternsentities: + # Lights included in 'away from home' random pattern entities: - switch.tasmo_ks811d_1242_entrance_a # Entranceway Main Lights - switch.tasmo_ks811d_0302_entrybath_a # Entranceway Guest Bathroom Lights @@ -38,7 +16,8 @@ switch: - switch.tasmo_ks811t_0707_downstloun_2b # Foxhole Lounge Wall Lights - switch.tasmo_ks811s_3136_downstbed2_1a # Foxhole Craft Room Lights - switch.tasmo_ks811t_3642_downstbed1_1a # Foxhole Main Bedroom, Main Lights - - switch.tasmo_ks811t_2192_downstkitch_1a # Foxhole Kitchen Entry Lights - - switch.tasmo_ks811t_2192_downstkitch_1b # Foxhole Kitchen Main Lights - - switch.esp_downstbathswitch_relay_main_light # Foxhole Downstairs Bathroom Main Lights - switch.tasmo_s4chan_4231_underhouselights_b # Underhouse Main Lights + - switch.esp_downstkitchlights_relay_1_dining_light + - switch.esp_downstkitchlights_relay_2_kitchen_light + - switch.esp_downstbathswitch_relay_1_main_lights + - switch.esp_downstbathswitch_relay_2_cabinet_light