diff --git a/esphome/common/network_common.yaml b/esphome/common/network_common.yaml index 0796710..bb7eb0a 100644 --- a/esphome/common/network_common.yaml +++ b/esphome/common/network_common.yaml @@ -54,7 +54,7 @@ wifi: # Define dns domain / suffix to add to hostname domain: "${dns_domain}" -captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails +#captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails ############################################# # Enable Over the Air Update Capability diff --git a/esphome/esp-downstbathtowelrail.yaml b/esphome/esp-downstbathtowelrail.yaml index 16dc4f7..bfb7c97 100644 --- a/esphome/esp-downstbathtowelrail.yaml +++ b/esphome/esp-downstbathtowelrail.yaml @@ -67,7 +67,7 @@ substitutions: # Device Settings relay_icon: "mdi:heating-coil" - log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + log_level: "ERROR" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE update_interval: "20s" # update time for for general sensors etc # Timer Settings @@ -113,6 +113,12 @@ esphome: friendly_name: "${friendly_name}" comment: "${description_comment}" # Appears on the esphome page in HA area: "${device_area}" + platformio_options: + build_flags: + - "-Os" # optimize for size + - "-Wl,--gc-sections" # drop unused code/data + - "-fno-exceptions" # strip C++ exceptions + - "-fno-rtti" # strip C++ RTTI on_boot: priority: 900 # High priority to run after globals are initialized then: @@ -166,7 +172,7 @@ preferences: flash_write_interval: 5min mdns: - disabled: false + disabled: true ########################################################################################## # ESPHome Logging Enable @@ -225,13 +231,13 @@ globals: - id: evening_on type: int restore_value: False - initial_value: "${morning_off_default}" + initial_value: "${evening_on_default}" # Evening Off time (minutes from midnight), - id: evening_off type: int - restore_value: true - initial_value: "${morning_off_default}" + restore_value: False + initial_value: "${evening_off_default}" # Boost Duration (minutes), - id: boost_duration @@ -281,137 +287,124 @@ 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'. - #################################################### + ############################ + # Morning On time => "HH:MM" + ############################ - 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 + topic: "${mqtt_timer_topic}/morning-on" + 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); + int h = 0, m = 0; + if (sscanf(x.c_str(), "%2d:%2d", &h, &m) == 2) { + id(morning_on) = h * 60 + m; + ESP_LOGI("timer","Received new Morning On: %02d:%02d", h, m); } 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 + topic: "${mqtt_timer_topic}/morning-off" + internal: true 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); + int h = 0, m = 0; + if (sscanf(x.c_str(), "%2d:%2d", &h, &m) == 2) { + id(morning_off) = h * 60 + m; + ESP_LOGI("timer","Received new Morning Off: %02d:%02d", h, m); } 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 + topic: "${mqtt_timer_topic}/evening-on" + internal: true 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); + int h = 0, m = 0; + if (sscanf(x.c_str(), "%2d:%2d", &h, &m) == 2) { + id(evening_on) = h * 60 + m; + ESP_LOGI("timer","Received new Evening On: %02d:%02d", h, m); } 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 + topic: "${mqtt_timer_topic}/evening-off" + internal: true 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); + int h = 0, m = 0; + if (sscanf(x.c_str(), "%2d:%2d", &h, &m) == 2) { + id(evening_off) = h * 60 + m; + ESP_LOGI("timer","Received new Evening Off: %02d:%02d", h, m); } else { ESP_LOGW("timer","Invalid Evening Off format: %s", x.c_str()); } - #################################################### - # Boost duration => 1 - 1439 - #################################################### + + ############################ + # Boost duration => integer minutes (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 + topic: "${mqtt_timer_topic}/boost-time" + internal: true 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()); + int v = 0; + // Parse as integer + if (sscanf(x.c_str(), "%d", &v) == 1 && v >= 1 && v <= 1439) { + id(boost_duration) = v; + ESP_LOGI("boost_time","Received new Boost Duration: %d mins", v); } else { - id(boost_duration) = static_cast(v); + ESP_LOGW("boost_time","Invalid boost_time '%s'", x.c_str()); } #################################################### - # Subscribe to operation mode: - # OFF, ON, TIMER, BOOST - # We do case-insensitive compare using strcasecmp - # (Requires typically included in ESPHome) + # Subscribe to operation mode: OFF, ON, TIMER, BOOST #################################################### - # MQTT subscription: set mode, then immediately re-evaluate relay - platform: mqtt_subscribe id: timer_operation_mode_topic topic: "${mqtt_timer_topic}/operation" - internal: True # No need to show this in Home Assistant as there is a sensor that shows the set value + internal: true on_value: then: - lambda: |- - if (strcasecmp(x.c_str(), "TIMER") == 0) { + // Check only the first character for mode + char c = x.c_str()[0]; + if (c == 'T') { // “TIMER” id(operation_mode) = 2; - ESP_LOGI("timer","Operation mode set to TIMER"); - } else if (strcasecmp(x.c_str(), "ON") == 0) { - id(operation_mode) = 1; - ESP_LOGI("timer","Operation mode set to ON"); - } else if (strcasecmp(x.c_str(), "OFF") == 0) { - id(operation_mode) = 0; - ESP_LOGI("timer","Operation mode set to OFF"); - } else if (strcasecmp(x.c_str(), "BOOST") == 0) { + } else if (c == 'O') { // “ON” or “OFF” + // second letter N→ON, F→OFF + id(operation_mode) = (x.size() > 1 && x[1] == 'N') ? 1 : 0; + } else if (c == 'B') { // “BOOST” id(operation_mode) = 3; - id(boost_timer) = 0; - ESP_LOGI("timer","Operation mode set to BOOST"); + id(boost_timer) = 0; } else { - ESP_LOGW("timer","Invalid operation mode: %s", x.c_str()); + ESP_LOGW("timer","Invalid mode: %s", x.c_str()); } - script.execute: evaluate_relay_state diff --git a/esphome/esp-mainbathfancombo.yaml b/esphome/esp-mainbathfancombo.yaml index 074facf..794820d 100644 --- a/esphome/esp-mainbathfancombo.yaml +++ b/esphome/esp-mainbathfancombo.yaml @@ -17,19 +17,34 @@ # If NOT using a secrets file, just replace these with the passwords etc (in quotes) ############################################# substitutions: + # Device Naming 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. + + # Project Naming + 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. + + # 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-mainbathfancombo_ip + + # Device Settings + #relay_icon: "mdi:heating-coil" 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 + # MQTT Controls + mqtt_main_command_topic: !secret mqtt_main_command_topic + #mqtt_main_status_topic: !secret mqtt_main_status_topic + mqtt_remote_device1_command_topic: "${mqtt_main_command_topic}/masterbath-towelrail/operation" + mqtt_remote_device1_command1: "BOOST" + ############################################# # Included Common Packages # https://esphome.io/components/esphome.html @@ -71,6 +86,12 @@ esphome: project: name: "${project_name}" version: "${project_version}" + platformio_options: + build_flags: + - "-Os" # optimize for size + - "-Wl,--gc-sections" # drop unused code/data + - "-fno-exceptions" # strip C++ exceptions + - "-fno-rtti" # strip C++ RTTI # on_boot: # priority: 200 # then: @@ -90,7 +111,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 @@ -99,6 +120,8 @@ logger: # STATUS LED # https://esphome.io/components/status_led.html ############################################# +# Status LED for KS-811 is gpio2 +############################################# status_led: pin: number: GPIO2 @@ -108,6 +131,8 @@ status_led: # BINARY SENSORS # https://esphome.io/components/binary_sensor/ ############################################# +# Buttons for KS-811-2 are GPIO16, GPIO05 +############################################# binary_sensor: - platform: gpio pin: @@ -116,21 +141,43 @@ binary_sensor: inverted: True name: "Button 1: Extract Fan" on_press: - - switch.toggle: Relay_1 + - switch.toggle: "Relay_1" - platform: gpio pin: number: GPIO05 mode: INPUT - inverted: True + inverted: true name: "Button 2: IR Heater" - on_press: - - switch.toggle: Relay_2 + filters: + - delayed_on: 20ms + - delayed_off: 20ms + + on_multi_click: + # ── Single click: one quick press & release ───────────────── + - timing: + - ON for at most 400ms + - OFF for at least 200ms + then: + - switch.toggle: "Relay_2" + + # ── Double click: two quick presses/releases ─────────────── + - timing: + - ON for at most 400ms + - OFF for at most 400ms + - ON for at most 400ms + - OFF for at least 200ms + then: + - mqtt.publish: + topic: "${mqtt_remote_device1_command_topic}" + payload: "${mqtt_remote_device1_command1}" ############################################# # SWITCH COMPONENT # https://esphome.io/components/switch/ ############################################# +# Relays for KS-811-2 are GPIO13, GPIO12 +############################################# switch: - platform: gpio name: "Relay 1: Extract Fan" @@ -141,6 +188,8 @@ switch: name: "Relay 2: IR Heater" pin: GPIO12 id: Relay_2 + # Always run the fan when IR heater on. Not critical for this heat/lamp product + # as it has its own failsafes and fan switching on temp, but an added safety measure on_turn_on: - switch.turn_on: Relay_1 + switch.turn_on: "Relay_1" diff --git a/esphome/esp-maindishwasherspower.yaml b/esphome/esp-maindishwasherspower.yaml new file mode 100644 index 0000000..a312134 --- /dev/null +++ b/esphome/esp-maindishwasherspower.yaml @@ -0,0 +1,169 @@ +########################################################################################## +########################################################################################## +# MAIN DISHWASHER POWER +# Controlled by a Athom Smart Plug V1 +# package_import_url: github://athom-tech/athom-configs/athom-smart-plug.yaml +# +# V1.1 2025-06-12 package added for energy entities +# 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-maindishwasherspower" + friendly_name: "Main Dishwashers Power" + description_comment: "Main Dishwashers Power Monitor :: Athom Smart Plug Power Monitor V1" + 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.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-maindishwasherspower_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_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}" + + # Web and MQTT Packages + #common_webportal: !include + # file: common/webportal_common.yaml + #common_mqtt: !include + # file: common/mqtt_common.yaml + + # Device Specific included packages + common_athompowermonV1: !include + file: common/athompowermonv1_common.yaml + vars: + local_friendly_name: "${friendly_name}" + local_current_limit: "${current_limit}" + +########################################################################################## +# ESPHome +# https://esphome.io/components/esphome.html +########################################################################################## +esphome: + name: "${device_name}" + friendly_name: "${friendly_name}" + comment: "${description_comment}" # Appears on the esphome page in HA + area: "${device_area}" + name_add_mac_suffix: False + min_version: 2024.6.0 + project: + name: "${project_name}" + version: "${project_version}" + 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 + +########################################################################################## +# 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; + +################################################################################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +################################################################################################# +switch: + - platform: gpio + name: "Power Output" + pin: GPIO14 + id: relay + restore_mode: RESTORE_DEFAULT_ON # 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 07dacb2..f965243 100644 --- a/esphome/esp-masterbathtowelrail.yaml +++ b/esphome/esp-masterbathtowelrail.yaml @@ -66,8 +66,9 @@ substitutions: static_ip_address: !secret esp-masterbathtowelrail_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 + relay_icon: "mdi:heating-coil" + log_level: "ERROR" # 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/masterbath-towelrail" # Topics you will use to change stuff @@ -91,8 +92,8 @@ packages: file: common/api_common.yaml vars: local_api_key: "${api_key}" -# common_webportal: !include -# file: common/webportal_common.yaml + common_webportal: !include + file: common/webportal_common.yaml common_mqtt: !include file: common/mqtt_common.yaml common_sntp: !include @@ -112,6 +113,12 @@ esphome: friendly_name: "${friendly_name}" comment: "${description_comment}" # Appears on the esphome page in HA area: "${device_area}" + platformio_options: + build_flags: + - "-Os" # optimize for size + - "-Wl,--gc-sections" # drop unused code/data + - "-fno-exceptions" # strip C++ exceptions + - "-fno-rtti" # strip C++ RTTI on_boot: priority: 900 # High priority to run after globals are initialized then: @@ -159,28 +166,41 @@ esphome: ############################################# esp8266: board: esp01_1m # The original sonoff basic - restore_from_flash: true # restore some values on reboot + restore_from_flash: True # restore some values on reboot preferences: flash_write_interval: 5min mdns: - disabled: false + disabled: true -############################################# +########################################################################################## # 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) + 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 -############################################# +#################################################### +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +# Relay Switch (Sonoff Basic Relay on GPIO12) +#################################################### +switch: + - platform: gpio + name: "Towel Rail Power" + pin: GPIO12 + id: relay + restore_mode: RESTORE_DEFAULT_OFF + icon: "${relay_icon}" + +########################################################################################## # 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 @@ -198,31 +218,31 @@ globals: # Morning On time (minutes from midnight), - id: morning_on type: int - restore_value: true + restore_value: False initial_value: "${morning_on_default}" # Morning Off time (minutes from midnight), - id: morning_off type: int - restore_value: true + restore_value: False initial_value: "${morning_off_default}" # Evening On time (minutes from midnight), - id: evening_on type: int - restore_value: true - initial_value: "${morning_off_default}" + restore_value: False + initial_value: "${evening_on_default}" # Evening Off time (minutes from midnight), - id: evening_off type: int - restore_value: true - initial_value: "${morning_off_default}" + restore_value: False + initial_value: "${evening_off_default}" # Boost Duration (minutes), - id: boost_duration type: int - restore_value: true + restore_value: False initial_value: "${boost_duration_default}" #################################################### @@ -235,7 +255,7 @@ globals: - id: operation_mode type: int restore_value: false - initial_value: "3" + initial_value: "2" #################################################### # current_mins is set if SNTP is invalid. @@ -258,152 +278,135 @@ globals: 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'. - #################################################### + ############################ + # Morning On time => "HH:MM" + ############################ - 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 + topic: "${mqtt_timer_topic}/morning-on" + 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); + int h = 0, m = 0; + if (sscanf(x.c_str(), "%2d:%2d", &h, &m) == 2) { + id(morning_on) = h * 60 + m; + ESP_LOGI("timer","Received new Morning On: %02d:%02d", h, m); } 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 + topic: "${mqtt_timer_topic}/morning-off" + internal: true 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); + int h = 0, m = 0; + if (sscanf(x.c_str(), "%2d:%2d", &h, &m) == 2) { + id(morning_off) = h * 60 + m; + ESP_LOGI("timer","Received new Morning Off: %02d:%02d", h, m); } 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 + topic: "${mqtt_timer_topic}/evening-on" + internal: true 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); + int h = 0, m = 0; + if (sscanf(x.c_str(), "%2d:%2d", &h, &m) == 2) { + id(evening_on) = h * 60 + m; + ESP_LOGI("timer","Received new Evening On: %02d:%02d", h, m); } 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 + topic: "${mqtt_timer_topic}/evening-off" + internal: true 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); + int h = 0, m = 0; + if (sscanf(x.c_str(), "%2d:%2d", &h, &m) == 2) { + id(evening_off) = h * 60 + m; + ESP_LOGI("timer","Received new Evening Off: %02d:%02d", h, m); } else { ESP_LOGW("timer","Invalid Evening Off format: %s", x.c_str()); } - #################################################### - # Boost duration => 1 - 1439 - #################################################### + + ############################ + # Boost duration => integer minutes (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 + topic: "${mqtt_timer_topic}/boost-time" + internal: true 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()); + int v = 0; + // Parse as integer + if (sscanf(x.c_str(), "%d", &v) == 1 && v >= 1 && v <= 1439) { + id(boost_duration) = v; + ESP_LOGI("boost_time","Received new Boost Duration: %d mins", v); } else { - id(boost_duration) = static_cast(v); + ESP_LOGW("boost_time","Invalid boost_time '%s'", x.c_str()); } #################################################### - # Subscribe to operation mode: - # OFF, ON, TIMER, BOOST - # We do case-insensitive compare using strcasecmp - # (Requires typically included in ESPHome) + # Subscribe to operation mode: OFF, ON, TIMER, BOOST #################################################### - platform: mqtt_subscribe - name: "Operation Mode Setting" id: timer_operation_mode_topic - 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 + topic: "${mqtt_timer_topic}/operation" + internal: true 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) { + // Check only the first character for mode + char c = x.c_str()[0]; + if (c == 'T') { // “TIMER” id(operation_mode) = 2; - ESP_LOGI("timer","Operation mode set to TIMER"); - } else if (strcasecmp(x.c_str(), "ON") == 0) { - id(operation_mode) = 1; - ESP_LOGI("timer","Operation mode set to ON"); - } else if (strcasecmp(x.c_str(), "OFF") == 0) { - id(operation_mode) = 0; - ESP_LOGI("timer","Operation mode set to OFF"); - } else if (strcasecmp(x.c_str(), "BOOST") == 0) { + } else if (c == 'O') { // “ON” or “OFF” + // second letter N→ON, F→OFF + id(operation_mode) = (x.size() > 1 && x[1] == 'N') ? 1 : 0; + } else if (c == 'B') { // “BOOST” id(operation_mode) = 3; - id(boost_timer) = 0; // Reset the BOOST timer to zero - ESP_LOGI("timer","Operation mode set to BOOST"); + id(boost_timer) = 0; } else { - ESP_LOGW("timer","Invalid operation mode: %s", x.c_str()); + ESP_LOGW("timer","Invalid mode: %s", x.c_str()); } + - script.execute: evaluate_relay_state ###################################################### # Expose the current operation mode (OFF, ON, TIMER, BOOST) @@ -480,27 +483,79 @@ text_sensor: return { std::string(buff) }; update_interval: "${update_interval}" - ###################################################### - # Expose the "Boost time" time as text (HH:MM) - ###################################################### +########################################################################################## +# NUMBER COMPONENT +# https://esphome.io/components/number/index.html +########################################################################################## +number: + # A value for setting operation mode via HTTP - platform: template - name: "Timeclock: Boost Time" - lambda: |- - // e.g. "120 Mins" - return std::to_string(id(boost_duration)) + " Mins"; - update_interval: "${update_interval}" + name: "Operation Mode Number" + id: operation_mode_number + internal: true # ← hides it from HA + min_value: 0 + max_value: 3 + step: 1 + set_action: + - lambda: |- + // x holds the incoming value (0–3) + id(operation_mode) = int(x); -############################################# +########################################################################################## +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +########################################################################################## +binary_sensor: + - platform: gpio + pin: + number: GPIO3 + mode: INPUT_PULLUP + inverted: true + name: "Power Button" + id: power_button + filters: + - delayed_on: 20ms + on_click: + - min_length: 20ms + max_length: 500ms + then: + - lambda: |- + if (id(relay).state) { + // Relay is ON: turn it OFF and set mode to 0 (TIMER) + id(relay).turn_off(); + id(operation_mode) = 2; + } else { + // Relay is OFF: turn it ON and set mode to 3 (BOOST) + id(relay).turn_on(); + id(operation_mode) = 3; + } + + - platform: template + name: "Relay Status" + lambda: |- + return id(relay).state; + +########################################################################################## # Sensors # https://esphome.io/components/text_sensor/index.html -############################################# +########################################################################################## sensor: - platform: template - name: "Mins from Midnight" + name: "Timeclock: Boost Duration" + id: boost_duration_time unit_of_measurement: "mins" - accuracy_decimals: 0 + accuracy_decimals: "0" update_interval: "${update_interval}" - internal: True + lambda: |- + return id(boost_duration); + + - platform: template + name: "Mins from Midnight" + id: mins_from_midnight + unit_of_measurement: "mins" + accuracy_decimals: "0" + update_interval: "${update_interval}" + internal: True # No need to show this in Home Assistant lambda: |- return id(current_mins); @@ -530,16 +585,7 @@ sensor: // 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 @@ -548,17 +594,14 @@ button: - platform: template name: "Boost now" id: boost_button - icon: "mdi:play-circle-outline" # optional, pick any MaterialDesign icon you like + icon: "mdi:play-circle-outline" 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 + # 1) reset BOOST timer and set mode + - lambda: |- + id(boost_timer) = 0; + id(operation_mode) = 3; + # 2) immediately re-evaluate relay state + - script.execute: evaluate_relay_state ################################################################################################# # SELECT COMPONENT @@ -568,125 +611,99 @@ select: - platform: template name: "Operation Mode" id: operation_mode_select - update_interval: 5s # poll every 5 s for external changes + update_interval: 5s options: - "OFF" - "ON" - "TIMER" - "BOOST" - # Getter: maps your integer into one of the four strings + # show the current mode lambda: |- switch (id(operation_mode)) { - case 1: return std::string("ON"); - case 2: return std::string("TIMER"); - case 3: return std::string("BOOST"); + 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 + # when changed in HA, set mode & re-evaluate set_action: - lambda: |- - if (x == "OFF") id(operation_mode) = 0; - else if (x == "ON") id(operation_mode) = 1; - else if (x == "TIMER") id(operation_mode) = 2; - else { - 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)); + if (x == "OFF") { id(operation_mode) = 0; } + else if (x == "ON") { id(operation_mode) = 1; } + else if (x == "TIMER") { id(operation_mode) = 2; } + else { // BOOST + id(boost_timer) = 0; + id(operation_mode) = 3; + } + - script.execute: evaluate_relay_state -#################################################### -# Check every minute to decide relay state -#################################################### -interval: - - interval: "1min" # Must be 1min as this is used to calculate times +################################################################################################# +# SCRIPT COMPONENT +# https://esphome.io/components/script.html +################################################################################################# +# Script: evaluate and drive the relay +script: + - id: evaluate_relay_state then: - lambda: |- - // Do we have correct time from SNTP? If not... + int mode = id(operation_mode); + + // BOOST just forces the relay on + if (mode == 3) { + id(relay).turn_on(); + return; + } + + // OFF → always off + if (mode == 0) { + id(relay).turn_off(); + return; + } + + // ON → always on + if (mode == 1) { + id(relay).turn_on(); + return; + } + + // TIMER → follow schedule windows + { + bool should_on = false; + if (id(current_mins) >= id(morning_on) && id(current_mins) < id(morning_off)) + should_on = true; + if (id(current_mins) >= id(evening_on) && id(current_mins) < id(evening_off)) + should_on = true; + if (should_on) id(relay).turn_on(); + else id(relay).turn_off(); + } + +################################################################################################# +# INTERVAL COMPONENT +# https://esphome.io/components/interval.html +################################################################################################# +# Interval: bumps time (even if no SNTP), then calls the script to evaluate relay state +interval: + - interval: "1min" + then: + - lambda: |- + // — update current_mins via SNTP or fallback if (!id(sntp_time).now().is_valid()) { - id(current_mins) += 1; + id(current_mins)++; if (id(current_mins) >= 1440) id(current_mins) = 0; } else { auto now = id(sntp_time).now(); id(current_mins) = now.hour * 60 + now.minute; } - // operation_mode: - // 0 = OFF - // 1 = ON - // 2 = TIMER - // 3 = BOOST - int mode = id(operation_mode); - - ////////////////////////////////////////////////// - // BOOST MODE: Relay ON for 'boost_duration' - // minutes, then automatically revert to TIMER. - ////////////////////////////////////////////////// - if (mode == 3) { - 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 'boost_duration' minutes => switch to TIMER + // — if in BOOST, advance boost_timer and expire when done + if (id(operation_mode) == 3) { + id(boost_timer)++; + if (id(boost_timer) >= id(boost_duration)) { id(operation_mode) = 2; - id(mqtt_client).publish("${mqtt_timer_topic}/operation", "TIMER"); - } - // Skip the rest of the logic - ESP_LOGI("boost_timer", "boost_timer=%d", id(boost_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(); + //id(mqtt_client).publish("${mqtt_timer_topic}/operation", "TIMER"); } } - - - + - script.execute: evaluate_relay_state diff --git a/esphome/esp-officeduallights.yaml b/esphome/esp-officeduallights.yaml new file mode 100644 index 0000000..ab5e07b --- /dev/null +++ b/esphome/esp-officeduallights.yaml @@ -0,0 +1,298 @@ +############################################# +############################################# +# OFFICE DUAL LIGHT CONTROL +# - Overhead cool white lights +# - Right hand warm bunker light +# +# Controlled by a Sonoff Dual R1 +# A sonoff dual uses serial data to turn the relays on/off +# +# V2.3 - 2025-06-18 Added MQTT direct relay control +# +# NOTES: +# Command the lights on with MQTT +# ${mqtt_command_topic}/relay2/set ON or OFF +# ${mqtt_command_topic}/relay2/set ON or OFF +# +########################################################################################## +########################################################################################## + +########################################################################################## +# 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-officeduallights" + friendly_name: "Office Dual Lights" + description_comment: "Dual Office Lights (Overhead and right hand Bunker) :: Sonoff Dual R1" + device_area: "Office" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + mqtt_device_name: "office-dual-lights" + + # Project Naming + project_name: "Sonoff Technologies.Sonoff Dual R1" # Project Details + project_version: "v2.3" # 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-officeduallights_ip + mqtt_main_command_topic: !secret mqtt_main_command_topic + + # Device Settings + relay_icon: "mdi:lightbulb-group" + log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + + # MQTT Controls + mqtt_topic: "${mqtt_main_command_topic}/${mqtt_device_name}" # Topic we will use to command stuff from external withougt HA + #mqtt_status_topic: "${mqtt_main_status_topic}/esphome/${device_name}" # Topic we will use to read stuff from external withougt HA + #mqtt_main_status_topic: !secret mqtt_main_status_topic + #mqtt_remote_device1_command_topic: "${mqtt_main_command_topic}/masterbath-towelrail/operation" + #mqtt_remote_device1_command1: "BOOST" + +########################################################################################## +# PACKAGES: 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}" + platformio_options: + build_flags: + - "-Os" # optimize for size + - "-Wl,--gc-sections" # drop unused code/data + - "-fno-exceptions" # strip C++ exceptaions + - "-fno-rtti" # strip C++ RTTI + +########################################################################################## +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +########################################################################################## +esp8266: + board: esp01_1m # The original sonoff basic + restore_from_flash: False # restore some values on reboot + +preferences: + flash_write_interval: 5min + +mdns: + disabled: True + +########################################################################################## +# ESPHome Logging Enable +# https://esphome.io/components/logger.html +########################################################################################## +# NOTE: Sonoff Dual CANNOT send serial data to log +############################################### +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, Serial control) + #esp8266_store_log_strings_in_flash: false + #tx_buffer_size: 64 + +########################################################################################## +# UART Bus +# https://esphome.io/components/uart.html +########################################################################################## +uart: + tx_pin: GPIO01 + rx_pin: GPIO03 + baud_rate: 19200 + #data_bits: 8 + #stop_bits: 1 + #parity: EVEN + +########################################################################################## +# MQTT COMMANDS +# This adds device-specific MQTT command triggers to the common MQTT configuration. +########################################################################################## +mqtt: + # Direct MQTT control for relays via serial commands + on_message: + # Relay 1 control + - topic: "${mqtt_topic}/relay1/set" + payload: "ON" + then: + - switch.turn_on: relay_1 + - light.turn_on: light_1 + - topic: "${mqtt_topic}/relay1/set" + payload: "OFF" + then: + - switch.turn_off: relay_1 + - light.turn_off: light_1 + + # Relay 2 control + - topic: "${mqtt_topic}/relay2/set" + payload: "ON" + then: + - switch.turn_on: relay_2 + - light.turn_on: light_2 + - topic: "${mqtt_topic}/relay2/set" + payload: "OFF" + then: + - switch.turn_off: relay_2 + - light.turn_off: light_2 + +########################################################################################## +# STATUS LED +# https://esphome.io/components/status_led.html +########################################################################################## +# Sonoff Dual LED is GPIO13 +############################################# +status_led: + pin: + number: GPIO13 + inverted: yes + +########################################################################################## +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +########################################################################################## +# Sonoff Dual R1 buttons are GPIO04, GPIO14 +############################################# +binary_sensor: + - platform: gpio + pin: + number: GPIO4 + mode: INPUT_PULLUP + inverted: true + id: button_1 + on_press: + - switch.toggle: relay_1 + + - platform: gpio + pin: + number: GPIO14 + mode: INPUT_PULLUP + inverted: true + id: button_2 + on_press: + - switch.toggle: relay_2 + +########################################################################################## +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +########################################################################################## +# Sonoff Dual R1 requires serial data to switch +############################################# +switch: + - platform: template + name: "Dual L1 Relay" + id: relay_1 + optimistic: true + icon: "${relay_icon}" + internal: true + turn_on_action: + - mqtt.publish: + topic: "${mqtt_topic}/relay1/state" + payload: "ON" + - if: + condition: + switch.is_off: relay_2 + then: + - uart.write: [0xA0, 0x04, 0x01, 0xA1] + else: + - uart.write: [0xA0, 0x04, 0x03, 0xA1] + turn_off_action: + - mqtt.publish: + topic: "${mqtt_topic}/relay1/state" + payload: "OFF" + - if: + condition: + switch.is_off: relay_2 + then: + - uart.write: [0xA0, 0x04, 0x00, 0xA1] + else: + - uart.write: [0xA0, 0x04, 0x02, 0xA1] + - platform: template + name: "Dual L2 Relay" + id: relay_2 + optimistic: true + icon: "${relay_icon}" + internal: true + turn_on_action: + - mqtt.publish: + topic: "${mqtt_topic}/relay2/state" + payload: "ON" + - if: + condition: + switch.is_off: relay_1 + then: + - uart.write: [0xA0, 0x04, 0x02, 0xA1] + else: + - uart.write: [0xA0, 0x04, 0x03, 0xA1] + turn_off_action: + - mqtt.publish: + topic: "${mqtt_topic}/relay2/state" + payload: "OFF" + - if: + condition: + switch.is_off: relay_1 + then: + - uart.write: [0xA0, 0x04, 0x00, 0xA1] + else: + - uart.write: [0xA0, 0x04, 0x01, 0xA1] + +################################################################################ +# TEMPLATE OUTPUTS: drive the real relays when the light state changes +################################################################################ +output: + - platform: template + id: light_output_1 + type: binary + write_action: + - lambda: |- + if (state) id(relay_1).turn_on(); + else id(relay_1).turn_off(); + + - platform: template + id: light_output_2 + type: binary + write_action: + - lambda: |- + if (state) id(relay_2).turn_on(); + else id(relay_2).turn_off(); + +########################################################################################## +# LIGHT COMPONENT +# https://esphome.io/components/light/ +########################################################################################## +light: + - platform: binary + name: "Light 1 (Right Hand Bunker Light)" + id: light_1 + output: light_output_1 + + - platform: binary + name: "Light 2 (Cool White Overhead Lights)" + id: light_2 + output: light_output_2 diff --git a/esphome/esp-officeelvcontrol.yaml b/esphome/esp-officeelvcontrol.yaml new file mode 100644 index 0000000..deab9de --- /dev/null +++ b/esphome/esp-officeelvcontrol.yaml @@ -0,0 +1,192 @@ +############################################# +############################################# +# OFFICE ELV CONTROL +# Controlled by a Sonoff Basic R1 +# +# V1.0 2025-06-14 Initial Version +# +########################################################################################## +########################################################################################## + +########################################################################################## +# 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-officeelvcontrol" + friendly_name: "Office ELV Control" + mqtt_device_name: "office-elv-control" + description_comment: "ELV Power Supply control in the Office (under desk) :: Sonoff Basic" + device_area: "Office" # 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: "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-officeelvcontrol_ip + mqtt_main_command_topic: !secret mqtt_main_command_topic + + # Device Settings + relay_icon: "mdi:generator-portable" + log_level: "ERROR" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for for general sensors etc + + # MQTT Controls + mqtt_command_topic: "${mqtt_main_command_topic}/${mqtt_device_name}/operation" # Topic we will use to command stuff from external withougt HA + #mqtt_status_topic: "${mqtt_main_status_topic}/esphome/${device_name}" # Topic we will use to read stuff from external withougt HA + #mqtt_main_status_topic: !secret mqtt_main_status_topic + #mqtt_remote_device1_command_topic: "${mqtt_main_command_topic}/masterbath-towelrail/operation" + #mqtt_remote_device1_command1: "BOOST" + +########################################################################################## +# PACKAGES: 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}" +# platformio_options: +# build_flags: +# - "-Os" # optimize for size +# - "-Wl,--gc-sections" # drop unused code/data +# - "-fno-exceptions" # strip C++ exceptions +# - "-fno-rtti" # strip C++ RTTI + + +########################################################################################## +# 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 + +########################################################################################## +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +########################################################################################## +# Sonoff Basic R1 Relay Switch is GPIO12 +############################################# +switch: + - platform: gpio + name: "ELV Power Supply" + pin: GPIO12 + id: relay + restore_mode: RESTORE_DEFAULT_OFF + icon: "${relay_icon}" + +########################################################################################## +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +########################################################################################## +# Sonoff Basic R1 Button is GPIO03 +############################################# +binary_sensor: + + - platform: gpio + pin: + number: GPIO03 + mode: INPUT_PULLUP + inverted: true + name: "Power Button" + id: power_button + filters: + - delayed_on: 20ms + on_click: + - min_length: 20ms + max_length: 500ms + then: + - lambda: |- + if (id(relay).state) { + // Relay is ON: turn it OFF + id(relay).turn_off(); + } else { + // Relay is OFF: turn it ON + id(relay).turn_on(); + } + + # Mimics actual relay status (but not controllable) + - platform: template + name: "Relay Status" + lambda: |- + return id(relay).state; + +########################################################################################## +# STATUS LED +# https://esphome.io/components/status_led.html +########################################################################################## +# Sonoff Basic R1 LED is GPIO13 +############################################# +status_led: + pin: + number: GPIO13 + inverted: yes + +########################################################################################## +# Text Sensors +# https://esphome.io/components/text_sensor/index.html +########################################################################################## +text_sensor: + - platform: mqtt_subscribe + name: "Office ELV MQTT Command" + id: office_elv_cmd + topic: "${mqtt_command_topic}" + internal: true # hide from HA + on_value: + then: + - lambda: |- + // payload is x (std::string) + if (x == "ON") { + id(relay).turn_on(); + } else if (x == "OFF") { + id(relay).turn_off(); + } + diff --git a/esphome/esp-poollightspower.yaml b/esphome/esp-poollightspower.yaml index f21014b..c2c7dda 100644 --- a/esphome/esp-poollightspower.yaml +++ b/esphome/esp-poollightspower.yaml @@ -197,13 +197,13 @@ globals: - id: evening_on type: int restore_value: False - initial_value: "${morning_off_default}" + initial_value: "${evening_on_default}" # Evening Off time (minutes from midnight), - id: evening_off type: int - restore_value: true - initial_value: "${morning_off_default}" + restore_value: False + initial_value: "${evening_off_default}" # Boost Duration (minutes), - id: boost_duration diff --git a/esphome/esp-poolpumppower.yaml b/esphome/esp-poolpumppower.yaml index 1025ce7..eb09e34 100644 --- a/esphome/esp-poolpumppower.yaml +++ b/esphome/esp-poolpumppower.yaml @@ -78,16 +78,12 @@ substitutions: 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 + evening_off_default: "1350" # Default in minutes from midnight. Default 22:30 => 1350 => 1440 is midnight ########################################################################################## # PACKAGES # https://esphome.io/components/esphome.html ########################################################################################## -########################################################################################## -# Included Common Packages -# https://esphome.io/components/esphome.html -########################################################################################## packages: common_wifi: !include file: common/network_common.yaml @@ -199,13 +195,13 @@ globals: - id: evening_on type: int restore_value: False - initial_value: "${morning_off_default}" + initial_value: "${evening_on_default}" # Evening Off time (minutes from midnight), - id: evening_off type: int - restore_value: true - initial_value: "${morning_off_default}" + restore_value: False + initial_value: "${evening_off_default}" # Boost Duration (minutes), - id: boost_duration