From 0c3ba9cad85663ede0baedef84e43c0c734d101f Mon Sep 17 00:00:00 2001 From: root Date: Sun, 18 May 2025 16:35:38 +1200 Subject: [PATCH] various esphome changes --- .../__pycache__/ts0207_rain.cpython-312.pyc | Bin 4013 -> 0 bytes .../__pycache__/ts0207_rain.cpython-313.pyc | Bin 4127 -> 0 bytes .../__pycache__/ts0601_smoke.cpython-312.pyc | Bin 7458 -> 0 bytes .../__pycache__/ts0601_smoke.cpython-313.pyc | Bin 7667 -> 0 bytes .../ts0601_smoke_heimann.cpython-313.pyc | Bin 5094 -> 0 bytes .../ts0601_smoke_heimann2.cpython-313.pyc | Bin 5095 -> 0 bytes .../ts0601_valve_garden.cpython-312.pyc | Bin 4678 -> 0 bytes .../ts0601_valve_garden.cpython-313.pyc | Bin 4720 -> 0 bytes esphome/common/mqtt_common.yaml | 21 + .../common/multiclick_pushbutton_common.yaml | 77 +++ esphome/common/network_common.yaml | 75 ++ ...ulticlick_pushbutton_common copy.yaml.old2 | 76 +++ ...old_click_pushbutton_common copy.yaml.copy | 76 +++ esphome/common/secrets copy.yaml | 1 + esphome/common/sensors_common.yaml | 96 +++ esphome/esp-attobat.yaml | 1 + esphome/esp-datapower-a.yaml | 509 ++++++++++++++ esphome/esp-datapower-b.yaml | 504 ++++++++++++++ esphome/esp-downstbathswitch.yaml | 282 ++++---- esphome/esp-downstbathswitch.yaml.changes | 215 ++++++ esphome/esp-entbtproxy.yaml | 2 + esphome/esp-entmulti.yaml | 4 +- esphome/esp-leafbat.yaml | 1 + esphome/esp-masterbathtowelrail copy.yaml.old | 639 ++++++++++++++++++ esphome/esp-masterbathtowelrail.yaml | 339 +++++----- esphome/esp-midesklamp1s.yaml | 75 +- esphome/esp-nexmulti1.yaml | 86 +++ esphome/esp-occupancyoffice.yaml | 4 +- esphome/esp-occupancystair.yaml | 4 +- group/foxhole_lights.yaml | 4 +- packages/appliance_status_mqttfeed.yaml | 1 + packages/climate_master_bedroom_dehum.yaml | 25 + .../climate_master_bedroom_night_cycle.yaml | 18 +- packages/mqtt_statestream.yaml | 20 +- packages/simulation_lights.yaml | 2 +- packages/weewx.yaml | 12 +- 36 files changed, 2813 insertions(+), 356 deletions(-) delete mode 100644 custom_zha_quirks/__pycache__/ts0207_rain.cpython-312.pyc delete mode 100644 custom_zha_quirks/__pycache__/ts0207_rain.cpython-313.pyc delete mode 100644 custom_zha_quirks/__pycache__/ts0601_smoke.cpython-312.pyc delete mode 100644 custom_zha_quirks/__pycache__/ts0601_smoke.cpython-313.pyc delete mode 100644 custom_zha_quirks/__pycache__/ts0601_smoke_heimann.cpython-313.pyc delete mode 100644 custom_zha_quirks/__pycache__/ts0601_smoke_heimann2.cpython-313.pyc delete mode 100644 custom_zha_quirks/__pycache__/ts0601_valve_garden.cpython-312.pyc delete mode 100644 custom_zha_quirks/__pycache__/ts0601_valve_garden.cpython-313.pyc create mode 100644 esphome/common/mqtt_common.yaml create mode 100644 esphome/common/multiclick_pushbutton_common.yaml create mode 100644 esphome/common/network_common.yaml create mode 100644 esphome/common/old2.multiclick_pushbutton_common copy.yaml.old2 create mode 100644 esphome/common/old_multiold_click_pushbutton_common copy.yaml.copy create mode 100644 esphome/common/secrets copy.yaml create mode 100644 esphome/common/sensors_common.yaml create mode 100644 esphome/esp-datapower-a.yaml create mode 100644 esphome/esp-datapower-b.yaml create mode 100644 esphome/esp-downstbathswitch.yaml.changes create mode 100644 esphome/esp-masterbathtowelrail copy.yaml.old create mode 100644 esphome/esp-nexmulti1.yaml create mode 100644 packages/climate_master_bedroom_dehum.yaml diff --git a/custom_zha_quirks/__pycache__/ts0207_rain.cpython-312.pyc b/custom_zha_quirks/__pycache__/ts0207_rain.cpython-312.pyc deleted file mode 100644 index 44dae3fbd2fbc7d5fd486dcd01eabfd9dc794b04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4013 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 diff --git a/custom_zha_quirks/__pycache__/ts0601_smoke.cpython-312.pyc b/custom_zha_quirks/__pycache__/ts0601_smoke.cpython-312.pyc deleted file mode 100644 index 430b37fdf7fd48f64f0928e2b440667563a67be3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7458 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 W`F(~I_lKug$Nhz8aH`>;-}FDg4wH@m diff --git a/custom_zha_quirks/__pycache__/ts0601_smoke_heimann.cpython-313.pyc b/custom_zha_quirks/__pycache__/ts0601_smoke_heimann.cpython-313.pyc deleted file mode 100644 index ad50a5f73fb3bf9a7b316d8652717f656bdf6b3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5094 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 Vckm&B!Nc*Bg6+=QBLW@Q>0fshGlT#D diff --git a/custom_zha_quirks/__pycache__/ts0601_smoke_heimann2.cpython-313.pyc b/custom_zha_quirks/__pycache__/ts0601_smoke_heimann2.cpython-313.pyc deleted file mode 100644 index c281141d7bcf172ea7a61f559646436e5836cc24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5095 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 Wc5mSF>O*Xdt(1T&og diff --git a/custom_zha_quirks/__pycache__/ts0601_valve_garden.cpython-312.pyc b/custom_zha_quirks/__pycache__/ts0601_valve_garden.cpython-312.pyc deleted file mode 100644 index 8fcab4f04216865b6b6ff0c9eb1688e1951c5db9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4678 zcmb^!Yi}FJagTRAK6Ii;iju51WlM1=HK)1K zk#@uesEW943ba7-p|uPIZ~_#l1E)apsXwA$MC1eM1p)*J3RFOW(vSiN`O?nrk$Q#A z1MNzjot>F|&1>iW*4gPr&@vlU{B!`J&)J~W z0;5TWWI9zR$DM|3x>Og(9fsTVs9uiCMu(}W3ddbWr|DCDW|!J!`c=OfPy=RA4Vv9* zH|M*JklCa5n7wMR*{AlIC)5*WzuIpOr~{nuF$T>cb%^6$W7r%~N6b-m)C{X(b4(pG zPpT))Q|c*mTpc$j)CmDO5Ej;Bt&1J>`1C`#8*A#*4z!5$jyp(K@DLt*C^hL3PVWS| z51)J}HmQ@G+67cUcI{KoY&ar;N_VonkqX~R8Mkm)$G386Y(;n2ym&;Q(tIvmf?llL zOhKp2m90|2oabUN_GSv@yq+rM3i*gkomWy;E=}b{QYaTKDke%P8eA#d#$>LL&*ZXY z!X>G^n#Or-Q7M@-G0f|jUwCC{Zb3`_bY+2h7UJ_OiKTdQmHL+AD{Dz@F1EIsT)4hU zJC+mk3$aygDSkCUyAo^3BV6yw^~BYs*n+k+4=3x17nW1`awe57l?fccfP-TaansQJ zO0sadR3f<>_6^primi90u>{G3#IQ23zOxP%5>SuTi-@*pmrbG$`^)brw+*(W8 z!^VkxB9oyV`)GWb4zt16_0XJ_wrym?rg_n>AI5hPM&P_ib?AaBg0VyzWJ?`BM0~k2 z&1m7n#BkcaZuoYtbTfQZ6HD{actoOZP0OcDtZCG%X=Xt$8w^)8?X_~sXmVVdrWewh z_5s>ur~7JpD$VUUmF8Zcflq*Vj{MA;Dp}L#r)MIW0{0LS#cO_)vMsbPSEJA+=q@(SD6%`?sGf{SR}U-)U~3?U_5jZLaQnS5||# z8;7trLZAZ1)g2WG4mhwF-}yIic60#bB!CYDD!gv7 zCBkGJ;8*S!jg)yqPhG5xuseM1YS9*d9;7W6CY$KJz zkSGJ}P-Ggwp+k|gF#fjlEU-Dv^IU!)zQ6d9SUok{JWoUITF1L{LNOiepXy*rqJ3EiU@4_y+ntnC%Ur#3XrJZ&fNS0`%+d zLxU#zq41EM722C%_MgM9e{|dxf9-UF`JY3-ll~&!79wJ0A^~c+O>!k1E)~L=0$e(L z)KLpMj)K=IhpF+1lRB+3=!p9H5{F^r`XaAuwb*+z@xwX6?!C1eij(5 z1tzP3$y#8l8kqVdFthnmUGmkWo~qRISZG8mN@Rb{-cgzBM@yNP!adoD@wKK63vNKH9YRnF9vL15NG zU$D& zI=zn(0J~OUdQQV{zqP84-3OrfqlFrlbVpXePF=(@cC9lnJl3!q=xyqcb>?En8t%Z# zI`g_?4g1xC*&Mzh~MqM-GP;Q$J+nr4!9Th;S*Wc z_dc}z-%9)o!n?YB`Fc`YiLEUX_Ph}nmuMF2T23eI6(Ai9 z@aTq5Ttt}v5Dx(Av2xjbiha*eB@x#WSFdWZrNwJWo|NobnNIleV$7ek>Pzy~|LlcLPNG)`(8amhTILCqw1qH(IjsJP-{i%;;|9bJ` zi`!#Y8r@#Mui@zmc^l4PK;B$xOd?OPu5=#S=R$S(!nf{I4~^DCPt`*+jV?EA6>PIr zp8{KnKG;h1cgw$g*$!r=gUAnf=BF;Bkj=6$Kp6@$=s0h?k(~Bi)P`JW7n3Pb$Tk$;v-QFd~X+eHl`2s=5?f)OZ CTtHR; diff --git a/custom_zha_quirks/__pycache__/ts0601_valve_garden.cpython-313.pyc b/custom_zha_quirks/__pycache__/ts0601_valve_garden.cpython-313.pyc deleted file mode 100644 index fa461d98b90dac7794f14144833b2901c79cb2b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4720 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 + # delay(${local_doubleclick_duration}); + # ${local_relay_id}->turn_off(); + # # Triple Click: three quick press/release cycles + ## - timing: + # - ON for at most 1s + ## - OFF for at most 1s + # - ON for at most 1s + # - OFF for at most 1s + # - ON for at most 1s + # - OFF for at least 0.5s + # then: + # - lambda: |- + # ESP_LOGD(${local_gpio}, "Triple click detected: turning ",${local_relay_id}," on for %d ms.", ${local_tripleclick_duration}); + # ${local_relay_id}->turn_on(); + # delay(${local_tripleclick_duration}); + # ${local_relay_id}->turn_off(); + # # Hold: button held for at least 4 seconds + # - timing: + # - ON for at least 4s + +## then: +# - lambda: |- +# ESP_LOGD(${local_gpio}, "Hold detected: turning ",${local_relay_id}," on indefinitely."); +# ${local_relay_id}->turn_on(); diff --git a/esphome/common/network_common.yaml b/esphome/common/network_common.yaml new file mode 100644 index 0000000..effa5c0 --- /dev/null +++ b/esphome/common/network_common.yaml @@ -0,0 +1,75 @@ +substitutions: + ############################################## + # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS + # If NOT using a secrets file, just replace these with the passwords etc (in quotes) + ############################################# + wifi_ssid: !secret ha_wifi_ssid + wifi_password: !secret ha_wifi_password + fallback_ap_password: !secret fallback_ap_password + + # Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken + wifi_fast_connect: "false" + + # Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs) + dns_domain: ".local" + + # Automatically add the mac address to the name + # eg so you can use a single firmware for all devices + add_mac_suffix: "false" + + # Add these if we are giving it a static ip, or remove them in the Wifi section + static_ip_subnet: !secret ha_wifi_subnet + static_ip_gateway: !secret ha_wifi_gateway + static_ip_dns1: !secret ha_wifi_gateway + +############################################# +# Common Wifi Settings +# https://esphome.io/components/wifi.html +# +# Power Save mode (can reduce wifi reliability) +# NONE (least power saving, Default for ESP8266) +# LIGHT (Default for ESP32) +# HIGH (most power saving) +############################################# +wifi: + ssid: ${wifi_ssid} + password: ${wifi_password} + #power_save_mode: LIGHT # https://esphome.io/components/wifi.html#wifi-power-save-mode + manual_ip: # optional static IP address + static_ip: ${local_static_ip_address} + gateway: ${static_ip_gateway} + 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 + password: ${fallback_ap_password} + ap_timeout: 30min # Time until it brings up fallback AP. default is 1min + # Allow rapid re-connection to previously connect WiFi SSID, skipping scan of all SSID + fast_connect: "${wifi_fast_connect}" + # Define dns domain / suffix to add to hostname + domain: "${dns_domain}" + +captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails + +############################################# +# Enable the Home Assistant API +# https://esphome.io/components/api.html +############################################# +api: + encryption: + key: ${local_api_key} + +############################################# +# Enable Over the Air Update Capability +# https://esphome.io/components/ota.html?highlight=ota +############################################# +ota: + - platform: esphome + password: ${local_ota_pass} + +############################################# +# Safe Mode +# Safe mode will detect boot loops +# https://esphome.io/components/safe_mode +############################################# +safe_mode: diff --git a/esphome/common/old2.multiclick_pushbutton_common copy.yaml.old2 b/esphome/common/old2.multiclick_pushbutton_common copy.yaml.old2 new file mode 100644 index 0000000..0a67520 --- /dev/null +++ b/esphome/common/old2.multiclick_pushbutton_common copy.yaml.old2 @@ -0,0 +1,76 @@ +#substitutions: +# local_gpioinvert: "True" +# local_singleclick_duration: "300000" # 5 minutes in ms +# local_doubleclick_duration: "1800000" # 30 minutes in ms +# local_tripleclick_duration: "7200000" # 2 hours in ms + +#globals: +# - id: single_click_delay +# type: long +# restore_value: no +# initial_value: ${local_singleclick_duration} +# - id: double_click_delay +# type: long +# restore_value: no +# initial_value: ${local_doubleclick_duration} +# - id: triple_click_delay +# type: long +# restore_value: no +# initial_value: ${local_tripleclick_duration} + +binary_sensor: + - platform: gpio + pin: + number: ${local_gpio} + mode: INPUT + inverted: True + name: ${local_name} + on_multi_click: + # Single Click: one press (short press/release) + - timing: + - ON for at most 1s + - OFF for at least 0.5s + then: + - lambda: |- + if (id(local_relay_id).state) { + ESP_LOGD(id(local_gpio},"Single click: ",id(local_relay_id)," is on, turning it off."); + id(local_relay_id).turn_off(); + } else { + ESP_LOGD(id(local_gpio), "Single click: ",id(local_relay_id)," is off, turning it on for %d ms.", id(local_singleclick_duraton)); + id(local_relay_id).turn_on(); + delay(id(local_singleclick_duration)); + id(local_relay_id).turn_off(); + } + # Double Click: two quick press/release cycles + - timing: + - ON for at most 1s + - OFF for at most 1s + - ON for at most 1s + - OFF for at least 0.5s + then: + - lambda: |- + ESP_LOGD(id(local_gpio), "Double click detected: turning ",id(local_relay_id)," on for %d ms.", id(local_doubleclick_duration)); + id(local_relay_id).turn_on(); + delay(id(local_doubleclick_duration)); + id(local_relay_id).turn_off(); + # Triple Click: three quick press/release cycles + - timing: + - ON for at most 1s + - OFF for at most 1s + - ON for at most 1s + - OFF for at most 1s + - ON for at most 1s + - OFF for at least 0.5s + then: + - lambda: |- + ESP_LOGD(id(local_gpio), "Triple click detected: turning ",id(local_relay_id)," on for %d ms.", id(local_tripleclick_duration)); + id(local_relay_id).turn_on(); + delay(id(local_tripleclick_duration)); + id(local_relay_id).turn_off(); + # Hold: button held for at least 4 seconds + - timing: + - ON for at least 4s + then: + - lambda: |- + ESP_LOGD(id(local_gpio), "Hold detected: turning ",id(local_relay_id)," on indefinitely."); + id(local_relay_id).turn_on(); diff --git a/esphome/common/old_multiold_click_pushbutton_common copy.yaml.copy b/esphome/common/old_multiold_click_pushbutton_common copy.yaml.copy new file mode 100644 index 0000000..a1a7e62 --- /dev/null +++ b/esphome/common/old_multiold_click_pushbutton_common copy.yaml.copy @@ -0,0 +1,76 @@ +defaults: + local_gpioinvert: "True" + local_singleclick_duration: "300000" # 5 minutes in ms + local_doubleclick_duration: "1800000" # 30 minutes in ms + local_tripleclick_duration: "7200000" # 2 hours in ms + +#globals: +# - id: single_click_delay +# type: long +# restore_value: no +# initial_value: ${local_singleclick_duration} +# - id: double_click_delay +# type: long +# restore_value: no +# initial_value: ${local_doubleclick_duration} +# - id: triple_click_delay +# type: long +# restore_value: no +# initial_value: ${local_tripleclick_duration} + +binary_sensor: + - platform: gpio + pin: + number: ${local_gpio} + mode: INPUT + inverted: ${local_gpioinvert} + name: ${local_name} + on_multi_click: + # Single Click: one press (short press/release) + - timing: + - ON for at most 1s + - OFF for at least 0.5s + then: + - lambda: |- + if (id(${local_relay_id}).state) { + ESP_LOGD("${local_gpio}", "Single click: ${local_relay_id} is on, turning it off."); + id(${local_relay_id}).turn_off(); + } else { + ESP_LOGD("${local_gpio}", "Single click: ${local_relay_id} is off, turning it on for %d ms.", id(single_click_delay)); + id(${local_relay_id}).turn_on(); + delay(id(single_click_delay)); + id(${local_relay_id}).turn_off(); + } + # Double Click: two quick press/release cycles + - timing: + - ON for at most 1s + - OFF for at most 1s + - ON for at most 1s + - OFF for at least 0.5s + then: + - lambda: |- + ESP_LOGD(${local_gpio}, "Double click detected: turning ",${local_relay_id}," on for %d ms.", id(double_click_delay)); + id(${local_relay_id}).turn_on(); + delay(id(double_click_delay)); + id(${local_relay_id}).turn_off(); + # Triple Click: three quick press/release cycles + - timing: + - ON for at most 1s + - OFF for at most 1s + - ON for at most 1s + - OFF for at most 1s + - ON for at most 1s + - OFF for at least 0.5s + then: + - lambda: |- + ESP_LOGD(${local_gpio}, "Triple click detected: turning ",${local_relay_id}," on for %d ms.", id(triple_click_delay)); + id(${local_relay_id}).turn_on(); + delay(id(triple_click_delay)); + id(${local_relay_id}).turn_off(); + # Hold: button held for at least 4 seconds + - timing: + - ON for at least 4s + then: + - lambda: |- + ESP_LOGD(${local_gpio}, "Hold detected (threshold %d ms): turning ",${local_relay_id}," on indefinitely."); + id(${local_relay_id}).turn_on(); diff --git a/esphome/common/secrets copy.yaml b/esphome/common/secrets copy.yaml new file mode 100644 index 0000000..257cb74 --- /dev/null +++ b/esphome/common/secrets copy.yaml @@ -0,0 +1 @@ +<<: !include ../secrets.yaml \ No newline at end of file diff --git a/esphome/common/sensors_common.yaml b/esphome/common/sensors_common.yaml new file mode 100644 index 0000000..8c7ae7b --- /dev/null +++ b/esphome/common/sensors_common.yaml @@ -0,0 +1,96 @@ +############################################# +# GENERAL COMMON SENSORS +# https://esphome.io/components/sensor/ +############################################# +sensor: + - platform: uptime # Uptime for this device in seconds + name: "Uptime (s): ${local_friendly_name}" + update_interval: ${local_update_interval} + id: uptime_sensor + entity_category: "diagnostic" + - platform: wifi_signal # Wifi Strength + name: "Wifi dB: ${local_friendly_name}" + id: wifi_signal_db + update_interval: ${local_update_interval} + entity_category: "diagnostic" + - platform: copy # Reports the WiFi signal strength in % + source_id: wifi_signal_db + name: "WiFi Percent: ${local_friendly_name}" + filters: + - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0); + unit_of_measurement: "% Max" + entity_category: "diagnostic" + device_class: "" + +############################################# +# Text Sensors +# https://esphome.io/components/text_sensor/index.html +############################################# +text_sensor: + ###################################################### + # General ESPHome Info + ###################################################### + - platform: version + name: "Version: ${local_friendly_name}" + entity_category: "diagnostic" + + - platform: wifi_info + ip_address: + name: "IP Address: ${local_friendly_name}" + + - platform: uptime # Uptime for this device human readable + name: "Uptime: ${local_friendly_name}" + icon: mdi:clock-start + update_interval: ${local_update_interval} + entity_category: "diagnostic" + + ###################################################### + # Creates a sensor showing when the device was last restarted + # Uptime template sensor, and SNTP are needed + ###################################################### + #- platform: template + # name: ${local_friendly_name} Last Restart + # id: device_last_restart + # icon: mdi:clock + # entity_category: diagnostic + # #device_class: timestamp + + ###################################################### + # Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds + ###################################################### + #- platform: template + # name: "Uptime" + # entity_category: diagnostic + # lambda: |- + # int seconds = (id(uptime_sensor).state); + # int days = seconds / (24 * 3600); + # seconds = seconds % (24 * 3600); + # int hours = seconds / 3600; + # seconds = seconds % 3600; + # int minutes = seconds / 60; + # seconds = seconds % 60; + # if ( days > 3650 ) { + # return { "Starting up" }; + # } else if ( days ) { + # return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() }; + # } else if ( hours ) { + # return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() }; + # } else if ( minutes ) { + # return { (String(minutes) +"m "+ String(seconds) +"s").c_str() }; + # } else { + # return { (String(seconds) +"s").c_str() }; + # } + # icon: mdi:clock-start + +button: + - platform: safe_mode + name: "Safe Mode Restart: ${local_friendly_name}" + entity_category: "diagnostic" + - platform: restart + name: "Restart: ${local_friendly_name}" + entity_category: "diagnostic" + disabled_by_default: true + - platform: factory_reset + name: "FACTORY RESET: ${local_friendly_name}" + entity_category: "diagnostic" + disabled_by_default: true diff --git a/esphome/esp-attobat.yaml b/esphome/esp-attobat.yaml index b5104c8..a77a1b0 100644 --- a/esphome/esp-attobat.yaml +++ b/esphome/esp-attobat.yaml @@ -137,6 +137,7 @@ mqtt: topic_prefix: ${mqtt_topic}/${devicename} username: ${mqtt_username} password: ${mqtt_password} + discovery: False # enable entity discovery (true is default, we don't want two HA Instances) # Availability Topic birth_message: diff --git a/esphome/esp-datapower-a.yaml b/esphome/esp-datapower-a.yaml new file mode 100644 index 0000000..ad0dfa9 --- /dev/null +++ b/esphome/esp-datapower-a.yaml @@ -0,0 +1,509 @@ +############################################# +############################################# +# Data Cupboard Power Monitor A +# Athom Smart Plug Power Monitor ESP32-C3 +# +# V1.0 2025-03-13 Initial Version +# +# based on https://github.com/athom-tech/esp32-configs/blob/main/athom-smart-plug.yaml +# +# SUMMARY +# Smart plug with power moniroting. It has limited ability to +# accidentally turn it off as it powers equipment in a data cupboard. +# It does have the ability to turn off and back on automatically +# a short time later, just in case a remote system reset is needed. +# Also, the button on the side can't be bumped, you have to hold +# it down for a few seconds to power it off. +# The yaml code also calculates various power use summaries. +# +############################################# +############################################# + +substitutions: + ############################################# + # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS + # If NOT using a secrets file, just replace these with the passwords etc (in quotes) + ############################################# + devicename: "esp-datapower-a" + friendly_name: "Data Cupboard Power Monitor A" + description_comment: "Smart plug power Monitor A, Data Cupboard" + room: "Data Cupboard" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + api_key: !secret esp-datapower-a_api_key # unfortunately you can't use substitutions inside secrets names + ota_pass: !secret esp-datapower-a_ota_pass # unfortunately you can't use substitutions inside secrets names + static_ip_address: !secret esp-datapower-a_ip + + 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 + + # Project Details + project_name: "Athom Technology.Smart Plug V3" + project_version: "v1.0.7" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + + # Restore the relay (GPO switch) upon reboot to state: + relay_restore_mode: RESTORE_DEFAULT_ON + + # Current Limit in Amps. AU Plug = 10. IL, BR, EU, UK, US Plug = 16. + current_limit : "10" + + # Hide the ENERGY sensor that shows kWh consumed, but with no time period associated with it. Resets when device restarted and reflashed. + hide_energy_sensor: "true" + # Enable or disable the use of IPv6 networking on the device + ipv6_enable: "false" + # Power plug icon selection. Change to reflect the type/country of powr plug in use, this will update the power plug icon shown next to the switch + power_plug_type: "power-socket-au" # Options: power-socket-au | power-socket-ch | power-socket-de | power-socket-eu | power-socket-fr | power-socket-it | power-socket-jp | power-socket-uk | power-socket-us | + +############################################# +# SYSTEM SPECIFIC VARIABLE SUBSTITUTIONS +############################################# + + timezone: "Pacific/Auckland" + sntp_update_interval: 6h # Set the duration between the sntp service polling + # Network time servers https://www.ntppool.org/zone/@ + sntp_server_1: !secret ntp_server_1 + sntp_server_2: !secret ntp_server_2 + sntp_server_3: !secret ntp_server_3 + + wifi_ssid: !secret ha_wifi_ssid + wifi_password: !secret ha_wifi_password + fallback_ap_password: !secret fallback_ap_password + + # Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken + wifi_fast_connect: "false" + + # Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs) + dns_domain: ".local" + + # Automatically add the mac address to the name + # eg so you can use a single firmware for all devices + add_mac_suffix: "false" + + # Add these if we are giving it a static ip, or remove them in the Wifi section + static_ip_subnet: !secret ha_wifi_subnet + static_ip_gateway: !secret ha_wifi_gateway + + mqtt_server: !secret ha_mqtt_server + mqtt_username: !secret ha_mqtt_username + mqtt_password: !secret ha_mqtt_password + mqtt_topic: "esphome" #main topic for the mqtt server, call it what you like + + # Add these if we are using the internal web server (this is pretty processor intensive) + #web_server_username: !secret web_server_username + #web_server_password: !secret web_server_password + +############################################# +# Included Common Packages +# https://esphome.io/components/esphome.html +############################################# +packages: + wifi: !include common/wifi_common.yaml + +############################################# +# 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_add_mac_suffix: "${add_mac_suffix}" + min_version: 2024.6.0 + project: + name: "${project_name}" + version: "${project_version}" + platformio_options: + board_build.mcu: esp32c3 + board_build.variant: esp32c3 + board_build.flash_mode: dio + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +#esp8266: +# board: esp01_1m # The original sonoff basic +esp32: + board: esp32-c3-devkitm-1 + flash_size: 4MB + variant: ESP32C3 + framework: + type: arduino + version: recommended + +preferences: + flash_write_interval: 5min + +############################################# +# 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 + +############################################# +# Enable the Home Assistant API +# https://esphome.io/components/api.html +############################################# +api: + encryption: + key: ${api_key} + +############################################# +# Enable Over the Air Update Capability +# https://esphome.io/components/ota.html?highlight=ota +############################################# +ota: + - platform: esphome + password: ${ota_pass} + +############################################# +# Safe Mode +# Safe mode will detect boot loops +# https://esphome.io/components/safe_mode +############################################# +safe_mode: + +############################################# +# Wifi Settings +# https://esphome.io/components/wifi.html +# +# Power Save mode (can reduce wifi reliability) +# NONE (least power saving, Default for ESP8266) +# LIGHT (Default for ESP32) +# HIGH (most power saving) +############################################# +##ifi: +# ssid: ${wifi_ssid} +# password: ${wifi_password} + #power_save_mode: LIGHT # https://esphome.io/components/wifi.html#wifi-power-save-mode +# manual_ip: # optional static IP address +# static_ip: ${static_ip_address} +# gateway: ${static_ip_gateway} +# subnet: ${static_ip_subnet} +# ap: # Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode +# ssid: ${devicename} AP +# password: ${fallback_ap_password} +# ap_timeout: 30min # Time until it brings up fallback AP. default is 1min +# # Allow rapid re-connection to previously connect WiFi SSID, skipping scan of all SSID +# fast_connect: "${wifi_fast_connect}" +# # Define dns domain / suffix to add to hostname +# domain: "${dns_domain}" + +#captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails + +############################################# +# Real time clock time source for ESPHome +# If it's invalid, we fall back to an internal clock +# https://esphome.io/components/time/index.html +# https://esphome.io/components/time/sntp +############################################# +time: + - platform: sntp + id: sntp_time + # Define the timezone of the device + timezone: "${timezone}" + # Change sync interval from default 5min to 6 hours (or as set in substitutions) + update_interval: ${sntp_update_interval} + # Set specific sntp servers to use + servers: + - "${sntp_server_1}" + - "${sntp_server_2}" + - "${sntp_server_3}" + # Publish the time the device was last restarted + on_time_sync: + then: + # Update last restart time, but only once. + - if: + condition: + lambda: 'return id(device_last_restart).state == "";' + then: + - text_sensor.template.publish: + id: device_last_restart + state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");' + +############################################# +# MQTT Monitoring +# https://esphome.io/components/mqtt.html?highlight=mqtt +# MUST also have api enabled if you enable MQTT +############################################# +mqtt: + broker: ${mqtt_server} + topic_prefix: ${mqtt_topic}/${devicename} + username: ${mqtt_username} + password: ${mqtt_password} + discovery: false # enable entity discovery (true is default, we don't want two HA Instances) + +############################################# +# Web Portal for display and monitoring +# Turning this off is maybe a good idea to save resources, +# especially on an esp8266. +# https://esphome.io/components/web_server.html +############################################# +#web_server: +# port: 80 +# auth: +# username: ${web_server_username} # probably a good idea to secure it +# password: ${web_server_password} + + + + +network: + enable_ipv6: ${ipv6_enable} + +esp32_improv: + authorizer: none + +dashboard_import: + package_import_url: github://athom-tech/esp32-configs/athom-smart-plug.yaml + +uart: + rx_pin: GPIO20 + baud_rate: 4800 + data_bits: 8 + stop_bits: 1 + parity: EVEN + +globals: + - id: total_energy + type: float + restore_value: yes + initial_value: '0.0' + +# - id: restore_mode +# type: int +# restore_value: yes +# initial_value: "2" # 0 = Always_Off. 1 = Restore_Power_Off. 2 = Always_On. + +binary_sensor: + - platform: status + name: "Status" + icon: mdi:check-network-outline + entity_category: diagnostic + + - platform: gpio + pin: + number: GPIO3 + mode: INPUT_PULLUP + inverted: true + name: "Power Button" + id: power_button + disabled_by_default: true + on_multi_click: + - timing: + - ON for at most 10s + - OFF for at least 2s + then: + - switch.toggle: relay +# - timing: +# - ON for at least 4s +# then: +# - button.press: Reset + + - platform: template + name: "Relay Status" + lambda: |- + return id(relay).state; + +sensor: + - platform: uptime + name: "Uptime Sensor" + id: uptime_sensor + entity_category: diagnostic + internal: true + + - platform: wifi_signal + name: "WiFi Signal dB" + id: wifi_signal_db + update_interval: 60s + entity_category: "diagnostic" + + - platform: copy + source_id: wifi_signal_db + name: "WiFi Signal Percent" + filters: + - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0); + unit_of_measurement: "Signal %" + entity_category: "diagnostic" + device_class: "" + + - 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: "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} + + - 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 + +button: +# - platform: restart +# name: "Restart" +# entity_category: config + +# - platform: factory_reset +# name: "Factory Reset" +# id: Reset +# entity_category: config + +# - platform: safe_mode +# name: "Safe Mode" +# internal: false +# entity_category: config + + - platform: template + name: "Turn Off Relay Temporarily" + on_press: + then: + - switch.turn_off: relay + - delay: 5s + - switch.turn_on: relay + +switch: + - platform: gpio + name: "Switch" + pin: GPIO5 + id: relay + restore_mode: ALWAYS_ON # Ensures the relay is on at boot + internal: true # Hides the switch from Home Assistant + #icon: mdi:${power_plug_type} # Don't need an icon if we can't see it... + +light: + - platform: status_led + name: "Status LED" + id: blue_led + icon: mdi:lightbulb-outline + disabled_by_default: false + pin: + inverted: true + number: GPIO6 + +text_sensor: + - platform: wifi_info + ip_address: + name: "IP Address" + icon: mdi:ip-network + entity_category: diagnostic + ssid: + name: "Connected SSID" + icon: mdi:wifi-strength-2 + entity_category: diagnostic + mac_address: + name: "Mac Address" + icon: mdi:network-pos + entity_category: diagnostic + + # Creates a sensor showing when the device was last restarted + - platform: template + name: 'Last Restart' + id: device_last_restart + icon: mdi:clock + entity_category: diagnostic +# device_class: timestamp + + # Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds + - platform: template + name: "Uptime" + entity_category: diagnostic + lambda: |- + int seconds = (id(uptime_sensor).state); + int days = seconds / (24 * 3600); + seconds = seconds % (24 * 3600); + int hours = seconds / 3600; + seconds = seconds % 3600; + int minutes = seconds / 60; + seconds = seconds % 60; + if ( days > 3650 ) { + return { "Starting up" }; + } else if ( days ) { + return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() }; + } else if ( hours ) { + return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() }; + } else if ( minutes ) { + return { (String(minutes) +"m "+ String(seconds) +"s").c_str() }; + } else { + return { (String(seconds) +"s").c_str() }; + } + icon: mdi:clock-start + diff --git a/esphome/esp-datapower-b.yaml b/esphome/esp-datapower-b.yaml new file mode 100644 index 0000000..96fc23e --- /dev/null +++ b/esphome/esp-datapower-b.yaml @@ -0,0 +1,504 @@ +############################################# +############################################# +# Data Cupboard Power Monitor B +# Athom Smart Plug Power Monitor ESP32-C3 +# +# V1.0 2025-03-13 Initial Version +# +# based on https://github.com/athom-tech/esp32-configs/blob/main/athom-smart-plug.yaml +# +# SUMMARY +# Smart plug with power moniroting. It has limited ability to +# accidentally turn it off as it powers equipment in a data cupboard. +# It does have the ability to turn off and back on automatically +# a short time later, just in case a remote system reset is needed. +# Also, the button on the side can't be bumped, you have to hold +# it down for a few seconds to power it off. +# The yaml code also calculates various power use summaries. +# +############################################# +############################################# + +substitutions: + ############################################# + # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS + # If NOT using a secrets file, just replace these with the passwords etc (in quotes) + ############################################# + devicename: "esp-datapower-b" + friendly_name: "Data Cupboard Power Monitor B" + description_comment: "Smart plug power Monitor B, Data Cupboard" + room: "Data Cupboard" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + api_key: !secret esp-datapower-b_api_key # unfortunately you can't use substitutions inside secrets names + ota_pass: !secret esp-datapower-b_ota_pass # unfortunately you can't use substitutions inside secrets names + static_ip_address: !secret esp-datapower-b_ip + + 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 + + # Project Details + project_name: "Athom Technology.Smart Plug V3" + project_version: "v1.0.7" # Project V denotes release of yaml file, allowing checking of deployed vs latest version + + # Restore the relay (GPO switch) upon reboot to state: + relay_restore_mode: RESTORE_DEFAULT_ON + + # Current Limit in Amps. AU Plug = 10. IL, BR, EU, UK, US Plug = 16. + current_limit : "10" + + # Hide the ENERGY sensor that shows kWh consumed, but with no time period associated with it. Resets when device restarted and reflashed. + hide_energy_sensor: "true" + # Enable or disable the use of IPv6 networking on the device + ipv6_enable: "false" + # Power plug icon selection. Change to reflect the type/country of powr plug in use, this will update the power plug icon shown next to the switch + power_plug_type: "power-socket-au" # Options: power-socket-au | power-socket-ch | power-socket-de | power-socket-eu | power-socket-fr | power-socket-it | power-socket-jp | power-socket-uk | power-socket-us | + + + +############################################# +# SYSTEM SPECIFIC VARIABLE SUBSTITUTIONS +############################################# + + timezone: "Pacific/Auckland" + sntp_update_interval: 6h # Set the duration between the sntp service polling + # Network time servers https://www.ntppool.org/zone/@ + sntp_server_1: !secret ntp_server_1 + sntp_server_2: !secret ntp_server_2 + sntp_server_3: !secret ntp_server_3 + + wifi_ssid: !secret ha_wifi_ssid + wifi_password: !secret ha_wifi_password + fallback_ap_password: !secret fallback_ap_password + + # Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken + wifi_fast_connect: "false" + + # Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs) + dns_domain: ".local" + + # Automatically add the mac address to the name + # eg so you can use a single firmware for all devices + add_mac_suffix: "false" + + # Add these if we are giving it a static ip, or remove them in the Wifi section + static_ip_subnet: !secret ha_wifi_subnet + static_ip_gateway: !secret ha_wifi_gateway + + mqtt_server: !secret ha_mqtt_server + mqtt_username: !secret ha_mqtt_username + mqtt_password: !secret ha_mqtt_password + mqtt_topic: "esphome" #main topic for the mqtt server, call it what you like + + # Add these if we are using the internal web server (this is pretty processor intensive) + #web_server_username: !secret web_server_username + #web_server_password: !secret web_server_password + + +############################################# +# 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_add_mac_suffix: "${add_mac_suffix}" + min_version: 2024.6.0 + project: + name: "${project_name}" + version: "${project_version}" + platformio_options: + board_build.mcu: esp32c3 + board_build.variant: esp32c3 + board_build.flash_mode: dio + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +#esp8266: +# board: esp01_1m # The original sonoff basic +esp32: + board: esp32-c3-devkitm-1 + flash_size: 4MB + variant: ESP32C3 + framework: + type: arduino + version: recommended + +preferences: + flash_write_interval: 5min + +############################################# +# 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 + +############################################# +# Enable the Home Assistant API +# https://esphome.io/components/api.html +############################################# +api: + encryption: + key: ${api_key} + +############################################# +# Enable Over the Air Update Capability +# https://esphome.io/components/ota.html?highlight=ota +############################################# +ota: + - platform: esphome + password: ${ota_pass} + +############################################# +# Safe Mode +# Safe mode will detect boot loops +# https://esphome.io/components/safe_mode +############################################# +safe_mode: + +############################################# +# Wifi Settings +# https://esphome.io/components/wifi.html +# +# Power Save mode (can reduce wifi reliability) +# NONE (least power saving, Default for ESP8266) +# LIGHT (Default for ESP32) +# HIGH (most power saving) +############################################# +wifi: + ssid: ${wifi_ssid} + password: ${wifi_password} + #power_save_mode: LIGHT # https://esphome.io/components/wifi.html#wifi-power-save-mode + manual_ip: # optional static IP address + static_ip: ${static_ip_address} + gateway: ${static_ip_gateway} + subnet: ${static_ip_subnet} + ap: # Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode + ssid: ${devicename} AP + password: ${fallback_ap_password} + ap_timeout: 30min # Time until it brings up fallback AP. default is 1min + # Allow rapid re-connection to previously connect WiFi SSID, skipping scan of all SSID + fast_connect: "${wifi_fast_connect}" + # Define dns domain / suffix to add to hostname + domain: "${dns_domain}" + +captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails + +############################################# +# Real time clock time source for ESPHome +# If it's invalid, we fall back to an internal clock +# https://esphome.io/components/time/index.html +# https://esphome.io/components/time/sntp +############################################# +time: + - platform: sntp + id: sntp_time + # Define the timezone of the device + timezone: "${timezone}" + # Change sync interval from default 5min to 6 hours (or as set in substitutions) + update_interval: ${sntp_update_interval} + # Set specific sntp servers to use + servers: + - "${sntp_server_1}" + - "${sntp_server_2}" + - "${sntp_server_3}" + # Publish the time the device was last restarted + on_time_sync: + then: + # Update last restart time, but only once. + - if: + condition: + lambda: 'return id(device_last_restart).state == "";' + then: + - text_sensor.template.publish: + id: device_last_restart + state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");' + +############################################# +# MQTT Monitoring +# https://esphome.io/components/mqtt.html?highlight=mqtt +# MUST also have api enabled if you enable MQTT +############################################# +mqtt: + broker: ${mqtt_server} + topic_prefix: ${mqtt_topic}/${devicename} + username: ${mqtt_username} + password: ${mqtt_password} + discovery: False # enable entity discovery (true is default, we don't want two HA Instances) + +############################################# +# Web Portal for display and monitoring +# Turning this off is maybe a good idea to save resources, +# especially on an esp8266. +# https://esphome.io/components/web_server.html +############################################# +#web_server: +# port: 80 +# auth: +# username: ${web_server_username} # probably a good idea to secure it +# password: ${web_server_password} + + + + +network: + enable_ipv6: ${ipv6_enable} + +esp32_improv: + authorizer: none + +dashboard_import: + package_import_url: github://athom-tech/esp32-configs/athom-smart-plug.yaml + +uart: + rx_pin: GPIO20 + baud_rate: 4800 + data_bits: 8 + stop_bits: 1 + parity: EVEN + +globals: + - id: total_energy + type: float + restore_value: yes + initial_value: '0.0' + +# - id: restore_mode +# type: int +# restore_value: yes +# initial_value: "2" # 0 = Always_Off. 1 = Restore_Power_Off. 2 = Always_On. + +binary_sensor: + - platform: status + name: "Status" + icon: mdi:check-network-outline + entity_category: diagnostic + + - platform: gpio + pin: + number: GPIO3 + mode: INPUT_PULLUP + inverted: true + name: "Power Button" + id: power_button + disabled_by_default: true + on_multi_click: + - timing: + - ON for at most 10s + - OFF for at least 2s + then: + - switch.toggle: relay +# - timing: +# - ON for at least 4s +# then: +# - button.press: Reset + + - platform: template + name: "Relay Status" + lambda: |- + return id(relay).state; + +sensor: + - platform: uptime + name: "Uptime Sensor" + id: uptime_sensor + entity_category: diagnostic + internal: true + + - platform: wifi_signal + name: "WiFi Signal dB" + id: wifi_signal_db + update_interval: 60s + entity_category: "diagnostic" + + - platform: copy + source_id: wifi_signal_db + name: "WiFi Signal Percent" + filters: + - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0); + unit_of_measurement: "Signal %" + entity_category: "diagnostic" + device_class: "" + + - 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: "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} + + - 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 + +button: +# - platform: restart +# name: "Restart" +# entity_category: config + +# - platform: factory_reset +# name: "Factory Reset" +# id: Reset +# entity_category: config + +# - platform: safe_mode +# name: "Safe Mode" +# internal: false +# entity_category: config + + - platform: template + name: "Turn Off Relay Temporarily" + on_press: + then: + - switch.turn_off: relay + - delay: 5s + - switch.turn_on: relay + +switch: + - platform: gpio + name: "Switch" + pin: GPIO5 + id: relay + restore_mode: ALWAYS_ON # Ensures the relay is on at boot + internal: true # Hides the switch from Home Assistant + #icon: mdi:${power_plug_type} # Don't need an icon if we can't see it... + +light: + - platform: status_led + name: "Status LED" + id: blue_led + icon: mdi:lightbulb-outline + disabled_by_default: false + pin: + inverted: true + number: GPIO6 + +text_sensor: + - platform: wifi_info + ip_address: + name: "IP Address" + icon: mdi:ip-network + entity_category: diagnostic + ssid: + name: "Connected SSID" + icon: mdi:wifi-strength-2 + entity_category: diagnostic + mac_address: + name: "Mac Address" + icon: mdi:network-pos + entity_category: diagnostic + + # Creates a sensor showing when the device was last restarted + - platform: template + name: 'Last Restart' + id: device_last_restart + icon: mdi:clock + entity_category: diagnostic +# device_class: timestamp + + # Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds + - platform: template + name: "Uptime" + entity_category: diagnostic + lambda: |- + int seconds = (id(uptime_sensor).state); + int days = seconds / (24 * 3600); + seconds = seconds % (24 * 3600); + int hours = seconds / 3600; + seconds = seconds % 3600; + int minutes = seconds / 60; + seconds = seconds % 60; + if ( days > 3650 ) { + return { "Starting up" }; + } else if ( days ) { + return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() }; + } else if ( hours ) { + return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() }; + } else if ( minutes ) { + return { (String(minutes) +"m "+ String(seconds) +"s").c_str() }; + } else { + return { (String(seconds) +"s").c_str() }; + } + icon: mdi:clock-start \ No newline at end of file diff --git a/esphome/esp-downstbathswitch.yaml b/esphome/esp-downstbathswitch.yaml index 6f278f9..fa44bae 100644 --- a/esphome/esp-downstbathswitch.yaml +++ b/esphome/esp-downstbathswitch.yaml @@ -10,27 +10,14 @@ # INSTRUCTIONS # - # -# 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 2 hours then TIMER (also on startup) -# ############################################# ############################################# substitutions: - - ############################################# - # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS - # If NOT using a secrets file, just replace these with the passwords etc (in quotes) - ############################################# +############################################# +# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +############################################# devicename: "esp-downstbathswitch" friendly_name: "Downstairs Bath Lightswitch" description_comment: "Downstairs Bathroom Main Lightswitch using a Zemismart KS-811 Triple Push Button. Main Light (1), Cabinet Light (2), Extract Fan (3)" @@ -39,35 +26,45 @@ substitutions: ota_pass: !secret esp-downstbathswitch_ota_pass # unfortunately you can't use substitutions inside secrets names static_ip_address: !secret esp-downstbathswitch_ip - #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 - #timezone: "Pacific/Auckland" # For setting clock with snmp - - -############################################# -# MY SYSTEM VARIABLE SUBSTITUTIONS -############################################# - wifi_ssid: !secret ha_wifi_ssid - wifi_password: !secret ha_wifi_password - fallback_ap_password: !secret fallback_ap_password - - # Add these if we are giving it a static ip, or remove them in the Wifi section - static_ip_subnet: !secret ha_wifi_subnet - static_ip_gateway: !secret ha_wifi_gateway - - mqtt_server: !secret ha_mqtt_server - mqtt_username: !secret ha_mqtt_username - mqtt_password: !secret ha_mqtt_password - mqtt_topic: "esphome" #main topic for the mqtt server, call it what you like - - # Add these if we are using the internal web server (this is pretty processor intensive) - #web_server_username: !secret web_server_username - #web_server_password: !secret web_server_password - 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_api_key: ${api_key} + local_ota_pass: ${ota_pass} + common_mqtt: !include + file: common/mqtt_common.yaml + common_general_sensors: !include + file: common/sensors_common.yaml + vars: + 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 @@ -76,14 +73,13 @@ esphome: name: ${devicename} friendly_name: ${friendly_name} comment: ${description_comment} #Appears on the esphome page in HA - ############################################# # ESP Platform and Framework # https://esphome.io/components/esp32.html ############################################# esp8266: - board: esp01_1m # The original sonoff basic + board: esp01_1m ############################################# # ESPHome Logging Enable @@ -95,52 +91,6 @@ logger: #esp8266_store_log_strings_in_flash: false #tx_buffer_size: 64 -############################################# -# Enable the Home Assistant API -# https://esphome.io/components/api.html -############################################# -api: - encryption: - key: ${api_key} - -############################################# -# Enable Over the Air Update Capability -# https://esphome.io/components/ota.html?highlight=ota -############################################# -ota: - - platform: esphome - password: ${ota_pass} - -############################################# -# Safe Mode -# Safe mode will detect boot loops -# https://esphome.io/components/safe_mode -############################################# -safe_mode: - -############################################# -# Wifi Settings -# https://esphome.io/components/wifi.html -# -# Power Save mode (can reduce wifi reliability) -# NONE (least power saving, Default for ESP8266) -# LIGHT (Default for ESP32) -# HIGH (most power saving) -############################################# -wifi: - ssid: ${wifi_ssid} - password: ${wifi_password} - #power_save_mode: LIGHT # https://esphome.io/components/wifi.html#wifi-power-save-mode - manual_ip: # optional static IP address - static_ip: ${static_ip_address} - gateway: ${static_ip_gateway} - subnet: ${static_ip_subnet} - ap: # Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode - ssid: ${devicename} AP - password: ${fallback_ap_password} - ap_timeout: 30min # Time until it brings up fallback AP. default is 1min - -captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails ############################################# # Real time clock time source for ESPHome @@ -152,35 +102,6 @@ time: - platform: sntp id: sntp_time -############################################# -# MQTT Monitoring -# https://esphome.io/components/mqtt.html?highlight=mqtt -# MUST also have api enabled if you enable MQTT -############################################# -mqtt: - broker: ${mqtt_server} - topic_prefix: ${mqtt_topic}/${devicename} - username: ${mqtt_username} - password: ${mqtt_password} - #discovery: True # enable entity discovery (true is default) - #discover_ip: True # enable device discovery (true is default) - -############################################# -# Global Variables for use in automations etc -# https://esphome.io/guides/automations.html?highlight=globals#global-variables -############################################# -#globals: - -############################################# -# TEXT SENSORS -# https://esphome.io/components/text_sensor/ -############################################# -text_sensor: - - platform: version - name: ${friendly_name} Version - - platform: wifi_info - ip_address: - name: ${friendly_name} IP Address ############################################# # STATUS LED @@ -191,6 +112,7 @@ status_led: number: GPIO2 inverted: yes + ############################################# # BINARY SENSORS # https://esphome.io/components/binary_sensor/ @@ -201,25 +123,107 @@ binary_sensor: number: GPIO16 mode: INPUT inverted: True - name: "Light Switch 1 (Main)" + name: "Button: Light Switch 1 (Main)" on_press: - switch.toggle: Relay_1 + - platform: gpio pin: number: GPIO05 mode: INPUT inverted: True - name: "Light Switch 2 (Cabinet)" + name: "Button: Light Switch 2 (Cabinet)" on_press: - switch.toggle: Relay_2 + - platform: gpio pin: - number: GPIO04 + number: GPIO4 mode: INPUT inverted: True - name: "Extract Fan" - on_press: - - switch.toggle: Relay_3 + name: "Button: Light Switch 3 (Fan)" + on_multi_click: + # Single click: 1 press → relay on for 5 minutes + - timing: + - ON for at most 0.5s + - OFF for at least 0.5s + then: + - if: + condition: + lambda: 'return id(Relay_3).state;' + then: + - switch.turn_off: Relay_3 + else: + - switch.turn_on: Relay_3 + - delay: 5min + - switch.turn_off: Relay_3 + + # Double click: 2 presses → relay on for 30 minutes + - timing: + - ON for at most 0.5s + - OFF for at most 0.5s + - ON for at most 0.5s + - OFF for at least 0.5s + then: + - if: + condition: + lambda: 'return id(Relay_3).state;' + then: + - switch.turn_off: Relay_3 + else: + - switch.turn_on: Relay_3 + - delay: 30min + - switch.turn_off: Relay_3 + + # Triple click: 3 presses → relay on for 4 hours + - timing: + - ON for at most 0.5s + - OFF for at most 0.5s + - ON for at most 0.5s + - OFF for at most 0.5s + - ON for at most 0.5s + - OFF for at least 0.2s + then: + - if: + condition: + lambda: 'return id(Relay_3).state;' + then: + - switch.turn_off: Relay_3 + else: + - switch.turn_on: Relay_3 + - delay: 4h + - switch.turn_off: Relay_3 + + # Hold: pressed for 1 second or more → relay toggles permanently + - timing: + - ON for at least 2s + then: + - if: + condition: + lambda: 'return id(Relay_3).state;' + then: + - switch.turn_off: Relay_3 + else: + - switch.turn_on: Relay_3 + +# - platform: gpio +# pin: +# number: GPIO04 +# mode: INPUT +# inverted: True +# name: "Button: Light Switch 3 (Fan)" +# on_press: +# - switch.toggle: Relay_3 + +# - platform: gpio +# pin: +# number: GPIO05 +# mode: INPUT +# inverted: True +# name: "Button: Light Switch 2 (Cabinet)" +# on_press: +# - switch.toggle: Relay_2 + ############################################# # SWITCH COMPONENT @@ -227,38 +231,16 @@ binary_sensor: ############################################# switch: - platform: gpio - name: "Main Light" + name: "Relay: Main Light" pin: GPIO13 id: Relay_1 - platform: gpio - name: "Cabinet Light" + name: "Relay: Cabinet Light" pin: GPIO12 id: Relay_2 - platform: gpio - name: "Extract Fan" + name: "Relay: Extract Fan" pin: GPIO14 id: Relay_3 -############################################# -# GENERAL SENSORS -# https://esphome.io/components/sensor/ -############################################# -sensor: - - platform: uptime # Uptime for this device - name: ${friendly_name} Uptime - update_interval: ${update_interval} - entity_category: "diagnostic" - - platform: wifi_signal # Wifi Strength - name: ${friendly_name} Wifi Signal dB - id: wifi_signal_db - update_interval: ${update_interval} - entity_category: "diagnostic" - - platform: copy # Reports the WiFi signal strength in % - source_id: wifi_signal_db - name: ${friendly_name} WiFi Signal Percent - filters: - - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0); - unit_of_measurement: "Signal %" - entity_category: "diagnostic" - device_class: "" diff --git a/esphome/esp-downstbathswitch.yaml.changes b/esphome/esp-downstbathswitch.yaml.changes new file mode 100644 index 0000000..38e9fef --- /dev/null +++ b/esphome/esp-downstbathswitch.yaml.changes @@ -0,0 +1,215 @@ +############################################# +############################################# +# DOWNSTAIRS BATHROOM MAIN LIGHTSWITCH +# Zemismart KS-811 Triple push button +# +# V1.0 2025-02-14 Initial Version +# +# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/ +# +# INSTRUCTIONS +# - +# +############################################# +############################################# + +substitutions: + ############################################# + # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS + # If NOT using a secrets file, just replace these with the passwords etc (in quotes) + ############################################# + devicename: "esp-downstbathswitch" + friendly_name: "Downstairs Bath Lightswitch" + description_comment: "Downstairs Bathroom Main Lightswitch using a Zemismart KS-811 Triple Push Button. Main Light (1), Cabinet Light (2), Extract Fan (3)" + + 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 + + update_interval: 60s # update time for for general sensors etc + hold_threshold: "4000ms" # Hold threshold as a compile-time constant + +############################################# +# 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_api_key: ${api_key} + local_ota_pass: ${ota_pass} + common_mqtt: !include + file: common/mqtt_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: ${devicename} + friendly_name: ${friendly_name} + comment: ${description_comment} #Appears on the esphome page in HA + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m + +############################################# +# ESPHome Logging Enable +# 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) + #esp8266_store_log_strings_in_flash: false + #tx_buffer_size: 64 + + +############################################# +# Real time clock time source for ESPHome +# If it's invalid, we fall back to an internal clock +# https://esphome.io/components/time/ +# https://esphome.io/components/time/sntp +############################################# +time: + - platform: sntp + id: sntp_time + + +############################################# +# STATUS LED +# https://esphome.io/components/status_led.html +############################################# +status_led: + pin: + number: GPIO2 + inverted: yes + +globals: + - id: single_click_delay + type: long + restore_value: no + initial_value: '300000' # 5 minutes in ms + - id: double_click_delay + type: long + restore_value: no + initial_value: '1800000' # 30 minutes in ms + - id: triple_click_delay + type: long + restore_value: no + initial_value: '7200000' # 2 hours in ms + - id: hold_threshold_val + type: long + restore_value: no + initial_value: '4000' # 4 seconds in ms + + +############################################# +# BINARY SENSORS +# https://esphome.io/components/binary_sensor/ +############################################# +binary_sensor: + - platform: gpio + pin: + number: GPIO16 + mode: INPUT + inverted: True + name: "Button: Light Switch 1 (Main)" + on_press: + - switch.toggle: Relay_1 + + - platform: gpio + pin: + number: GPIO05 + mode: INPUT + inverted: True + name: "Button: Light Switch 2 (Cabinet)" + on_press: + - switch.toggle: Relay_2 + + - platform: gpio + pin: + number: GPIO04 + mode: INPUT + inverted: True + name: "Button: Extract Fan" + on_multi_click: + # Single Click: one press (short press/release) + - timing: + - ON for at most 1s + - OFF for at least 0.5s + then: + - lambda: |- + if (id(Relay_3).state) { + ESP_LOGD("button3", "Single click: Relay_3 is on, turning it off."); + id(Relay_3).turn_off(); + } else { + ESP_LOGD("button3", "Single click: Relay_3 is off, turning it on for %d ms.", id(single_click_delay)); + id(Relay_3).turn_on(); + delay(id(single_click_delay)); + id(Relay_3).turn_off(); + } + # Double Click: two quick press/release cycles + - timing: + - ON for at most 1s + - OFF for at most 1s + - ON for at most 1s + - OFF for at least 0.5s + then: + - lambda: |- + ESP_LOGD("button3", "Double click detected: turning Relay_3 on for %d ms.", id(double_click_delay)); + id(Relay_3).turn_on(); + delay(id(double_click_delay)); + id(Relay_3).turn_off(); + # Triple Click: three quick press/release cycles + - timing: + - ON for at most 1s + - OFF for at most 1s + - ON for at most 1s + - OFF for at most 1s + - ON for at most 1s + - OFF for at least 0.5s + then: + - lambda: |- + ESP_LOGD("button3", "Triple click detected: turning Relay_3 on for %d ms.", id(triple_click_delay)); + id(Relay_3).turn_on(); + delay(id(triple_click_delay)); + id(Relay_3).turn_off(); + # Hold: button held for at least 4 seconds + - timing: + - ON for at least 4s + then: + - lambda: |- + ESP_LOGD("button3", "Hold detected (threshold %d ms): turning Relay_3 on indefinitely.", id(hold_threshold_val)); + id(Relay_3).turn_on(); + + +############################################# +# SWITCH COMPONENT +# https://esphome.io/components/switch/ +############################################# +switch: + - platform: gpio + name: "Relay: Main Light" + pin: GPIO13 + id: Relay_1 + - platform: gpio + name: "Relay: Cabinet Light" + pin: GPIO12 + id: Relay_2 + - platform: gpio + name: "Relay: Extract Fan" + pin: GPIO14 + id: Relay_3 + + diff --git a/esphome/esp-entbtproxy.yaml b/esphome/esp-entbtproxy.yaml index ff1614f..55b4896 100644 --- a/esphome/esp-entbtproxy.yaml +++ b/esphome/esp-entbtproxy.yaml @@ -136,6 +136,8 @@ mqtt: topic_prefix: ${mqtt_topic}/${devicename} username: ${mqtt_username} password: ${mqtt_password} + discovery: false # enable entity discovery (true is default) + #discover_ip: True # enable device discovery (true is default) ############################################# diff --git a/esphome/esp-entmulti.yaml b/esphome/esp-entmulti.yaml index e552047..a6b3ce1 100644 --- a/esphome/esp-entmulti.yaml +++ b/esphome/esp-entmulti.yaml @@ -151,8 +151,8 @@ mqtt: topic_prefix: ${mqtt_topic}/${devicename} username: ${mqtt_username} password: ${mqtt_password} - discovery: True # enable entity discovery (true is default) - discover_ip: True # enable device discovery (true is default) + discovery: False # enable entity discovery (true is default) + #discover_ip: True # enable device discovery (true is default) ############################################# # i2c bus diff --git a/esphome/esp-leafbat.yaml b/esphome/esp-leafbat.yaml index e61fcbf..e15e1d3 100644 --- a/esphome/esp-leafbat.yaml +++ b/esphome/esp-leafbat.yaml @@ -143,6 +143,7 @@ mqtt: topic_prefix: ${mqtt_topic}/${devicename} username: ${mqtt_username} password: ${mqtt_password} + discovery: False # enable entity discovery (true is default, we don't want two HA Instances) # Availability Topic birth_message: diff --git a/esphome/esp-masterbathtowelrail copy.yaml.old b/esphome/esp-masterbathtowelrail copy.yaml.old new file mode 100644 index 0000000..ff07908 --- /dev/null +++ b/esphome/esp-masterbathtowelrail copy.yaml.old @@ -0,0 +1,639 @@ +############################################# +############################################# +# MASTER BATHROOM HEATED TOWEL RAIL +# Controlled by a Sonoff Basic +# +# 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 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) +# +############################################# +############################################# + + +substitutions: + + ############################################# + # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS + # If NOT using a secrets file, just replace these with the passwords etc (in quotes) + ############################################# + + mqtt_timer_topic: "viewroad-commands/masterbath-towelrail" # Topics you will use to change stuff + startup_duration: "10" # Minutes to stay ON in STARTUP mode before reverting to TIMER + + devicename: "esp-masterbathtowelrail" + friendly_name: "Master Bathroom Towelrail" + description_comment: "Sonoff Basic controlling ON/OFF/Timer for the Heated Towel Rail in the Master Bathroom" + + 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 + + update_interval: "300s" # Update time for for general sensors etc. + + ############################################# + # SYSTEM SPECIFIC VARIABLE SUBSTITUTIONS + ############################################# + + timezone: "Pacific/Auckland" + sntp_update_interval: 6h # Set the duration between the sntp service polling + # Network time servers https://www.ntppool.org/zone/@ + # Make sure you have some DNS, or use IP addresses only here. + sntp_server_1: !secret ntp_server_1 + sntp_server_2: !secret ntp_server_2 + sntp_server_3: !secret ntp_server_3 + +############################################# +# 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_api_key: ${api_key} + local_ota_pass: ${ota_pass} + common_mqtt: !include + file: common/mqtt_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: ${devicename} + friendly_name: ${friendly_name} + comment: ${description_comment} # Appears on the esphome page in HA + 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 fallback_time * 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 { + // fallback_time is in minutes; convert to seconds + current_time_s = id(fallback_time) * 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 + +############################################# +# ESPHome Logging Enable +# https://esphome.io/components/logger.html +############################################# +logger: + level: DEBUG #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 + +############################################# +# Real time clock time source for ESPHome +# If it's invalid, we fall back to an internal clock +# https://esphome.io/components/time/index.html +# https://esphome.io/components/time/sntp +############################################# +time: + - platform: sntp + id: sntp_time + # Define the timezone of the device + timezone: "${timezone}" + # Change sync interval from default 5min to 6 hours (or as set in substitutions) + update_interval: "${sntp_update_interval}" + # Set specific sntp servers to use + servers: + - "${sntp_server_1}" + - "${sntp_server_2}" + - "${sntp_server_3}" + # Publish the time the device was last restarted + on_time_sync: + then: + - logger.log: "Synchronised sntp clock" + - text_sensor.template.publish: + id: time_sync + state: "SNTP clock Syncd" + # Update last restart time, but only once. + - if: + condition: + lambda: 'return id(device_last_restart).state == "";' + then: + - text_sensor.template.publish: + id: device_last_restart + state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");' + +############################################# +# 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), + # default 05:00 => 300 + - id: morning_on + type: int + restore_value: true + initial_value: "300" + + # Morning Off time (minutes from midnight), + # default 07:00 => 420 + - id: morning_off + type: int + restore_value: true + initial_value: "420" + + # Evening On time (minutes from midnight), + # default 21:00 => 1260 + - id: evening_on + type: int + restore_value: true + initial_value: "1260" + + # Evening Off time (minutes from midnight), + # default 00:00 => 0 => treat as midnight + - id: evening_off + type: int + restore_value: true + initial_value: "0" + + #################################################### + # operation_mode: + # 0 = OFF + # 1 = ON + # 2 = TIMER + # 3 = STARTUP + #################################################### + - id: operation_mode + type: int + restore_value: false + initial_value: "3" + + #################################################### + # fallback_time is used 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: fallback_time + type: int + restore_value: false + initial_value: "720" + + - id: current_mins + type: int + restore_value: false + initial_value: '0' + + #################################################### + # 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" + + + # Time period string for converting to seconds + #- id: update_interval_string + # type: std::string + # restore_value: false + # max_restore_data_length: 12 + # initial_value: ${update_interval} + +############################################# +# 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} + + + + ###################################################### + # 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} + + ###################################################### + # Creates a sensor showing when the device was last restarted + # Uptime template sensor, and SNTP are needed + ###################################################### + - platform: template + name: "Last Restart: ${friendly_name}" + id: device_last_restart + update_interval: ${update_interval} + icon: mdi:clock + entity_category: diagnostic + #device_class: timestamp + + - platform: template + name: "Time Sync Status" + id: time_sync + update_interval: ${update_interval} + entity_category: diagnostic + + - platform: template + name: "Internal Time" + id: time_text + update_interval: ${update_interval} + entity_category: diagnostic + lambda: |- + auto time_text = id(sntp_time).now().strftime("%H:%M:%S - %d-%m-%Y"); + return { time_text }; + +sensor: + - platform: template + name: "Mins from Midnight" + #unit_of_measurement: "s" + #accuracy_decimals: 0 + update_interval: 1s + lambda: |- + return id(current_mins); + + +#################################################### +# Relay Switch (Sonoff Basic Relay on GPIO12) +#################################################### +switch: + - platform: gpio + name: "Towel Rail Power" + pin: GPIO12 + id: relay + restore_mode: RESTORE_DEFAULT_OFF + +#################################################### +# Check every minute to decide relay state +#################################################### +interval: + - interval: "1min" + then: + - lambda: |- + // 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) ++ ; // 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 + 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 fallback_time + ////////////////////////////////////////////////// + if (mode == 2) { + //auto now = id(sntp_time).now(); + //bool have_sntp = now.is_valid(); + + //int current_mins; + //if (!have_sntp) { + //if (!id(time_sync).has_state()) { + //if 1 == 1 { + // SNTP not available => fallback clock + current_mins = id(fallback_time); + // increment the fallback clock by 1 minute + id(fallback_time) += 1; + // wrap around at 1440 => next day + if (id(fallback_time) >= 1440) { + id(fallback_time) = 0; + //} + //} else { + // Use real time from SNTP + // current_mins = now.hour * 60 + now.minute; + } + + bool should_on = false; + + // If evening_off == 0 => treat as midnight => 1440 + int evening_off_local = id(evening_off); + if (evening_off_local == 0) { + evening_off_local = 1440; + } + + // 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(morning_on) < id(morning_off)) { + if (current_mins >= id(morning_on) && current_mins < id(morning_off)) { + should_on = true; + } + } + + // Check evening window + // Example: evening_on=540 => 09:00, evening_off=1440 => midnight + if (id(evening_on) < evening_off_local) { + if (current_mins >= id(evening_on) && current_mins < evening_off_local) { + 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-masterbathtowelrail.yaml b/esphome/esp-masterbathtowelrail.yaml index 53de751..16d86aa 100644 --- a/esphome/esp-masterbathtowelrail.yaml +++ b/esphome/esp-masterbathtowelrail.yaml @@ -3,21 +3,22 @@ # MASTER BATHROOM HEATED TOWEL RAIL # Controlled by a Sonoff Basic # +# 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 2 hours then go into timer mode (this allows you to just turn it on to get some heat immediately) +# - 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 +# - 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 20 seconds (with ~2 secs in between to allow it to boot) +# - 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 @@ -29,49 +30,61 @@ # 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 2 hours then TIMER (also on startup) +# mqtt_timer_topic/operation/STARTUP : Turn on for (startup_duration) hours then TIMER (also on startup) # ############################################# ############################################# -############################################# -# VARIABLE SUBSTITUTIONS -# Give the device a useful name & description here -# and change values accordingly. -############################################# substitutions: + + ############################################# + # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS + # If NOT using a secrets file, just replace these with the passwords etc (in quotes) + ############################################# 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 - timezone: "Pacific/Auckland" # For setting clock with snmp devicename: "esp-masterbathtowelrail" friendly_name: "Master Bathroom Towelrail" description_comment: "Sonoff Basic controlling ON/OFF/Timer for the Heated Towel Rail in the Master Bathroom" - - # If NOT using a secrets file, just replace these with the passwords etc (in quotes) + 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 - wifi_ssid: !secret wifi_ssid - wifi_password: !secret wifi_password - fallback_ap_password: !secret fallback_ap_password - - # Add these if we are giving it a static ip, or remove them in the Wifi section - #static_ip_address: !secret esp-occupancyoffice_static_ip - #static_ip_gateway: !secret esp-occupancyoffice_gateway - #static_ip_subnet: !secret esp-occupancyoffice_subnet + static_ip_address: !secret esp-masterbathtowelrail_ip - mqtt_server: !secret mqtt_server - mqtt_username: !secret mqtt_username - mqtt_password: !secret mqtt_password - mqtt_topic: "esphome" #main topic for the mqtt server, call it what you like + update_interval: "60s" # Update time for for general sensors etc. - # Add these if we are using the internal web server (this is pretty processor intensive) - #web_server_username: !secret web_server_username - #web_server_password: !secret web_server_password + ############################################# + # SYSTEM SPECIFIC VARIABLE SUBSTITUTIONS + ############################################# - update_interval: 60s # update time for for general sensors etc + timezone: "Pacific/Auckland" + sntp_update_interval: 6h # Set the duration between the sntp service polling + # Network time servers https://www.ntppool.org/zone/@ + # Make sure you have some DNS, or use IP addresses only here. + sntp_server_1: !secret ntp_server_1 + sntp_server_2: !secret ntp_server_2 + sntp_server_3: !secret ntp_server_3 +############################################# +# 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_api_key: ${api_key} + local_ota_pass: ${ota_pass} + common_mqtt: !include + file: common/mqtt_common.yaml + common_general_sensors: !include + file: common/sensors_common.yaml + vars: + local_friendly_name: ${friendly_name} + local_update_interval: ${update_interval} ############################################# # ESPHome @@ -80,13 +93,13 @@ substitutions: esphome: name: ${devicename} friendly_name: ${friendly_name} - comment: ${description_comment} #a ppears on the esphome page in HA + comment: ${description_comment} # Appears on the esphome page in HA on_boot: - priority: 900 # high priority to run after globals are initialized + 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 fallback_time * 60. + // using SNTP if available, otherwise current_mins * 60. bool have_sntp = id(sntp_time).now().is_valid(); int current_time_s = 0; @@ -94,15 +107,15 @@ esphome: auto now = id(sntp_time).now(); current_time_s = now.hour * 3600 + now.minute * 60 + now.second; } else { - // fallback_time is in minutes; convert to seconds - current_time_s = id(fallback_time) * 60; + // 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 20 seconds, increment boot_count; otherwise reset to 1 - if (diff >= 0 && diff <= 20) { + // If within 30 seconds, increment boot_count; otherwise reset to 1 + if (diff >= 0 && diff <= 30) { id(boot_count)++; } else { id(boot_count) = 1; @@ -134,58 +147,11 @@ esp8266: # https://esphome.io/components/logger.html ############################################# logger: - level: INFO #INFO Level suggested, or DEBUG for testing + level: DEBUG #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 -############################################# -# Enable the Home Assistant API -# https://esphome.io/components/api.html -############################################# -api: - encryption: - key: ${api_key} - -############################################# -# Enable Over the Air Update Capability -# https://esphome.io/components/ota.html?highlight=ota -############################################# -ota: - - platform: esphome - password: ${ota_pass} - -############################################# -# Safe Mode -# Safe mode will detect boot loops -# https://esphome.io/components/safe_mode -############################################# -safe_mode: - -############################################# -# Wifi Settings -# https://esphome.io/components/wifi.html -# -# Power Save mode (can reduce wifi reliability) -# NONE (least power saving, Default for ESP8266) -# LIGHT (Default for ESP32) -# HIGH (most power saving) -############################################# -wifi: - ssid: ${wifi_ssid} - password: ${wifi_password} - #power_save_mode: LIGHT # https://esphome.io/components/wifi.html#wifi-power-save-mode - #manual_ip: # optional static IP address - #static_ip: ${static_ip_address} - #gateway: ${static_ip_gateway} - #subnet: ${static_ip_subnet} - ap: # Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode - ssid: ${devicename} AP - password: ${fallback_ap_password} - ap_timeout: 30min # Time until it brings up fallback AP. default is 1min - -captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails - ############################################# # Real time clock time source for ESPHome # If it's invalid, we fall back to an internal clock @@ -195,19 +161,30 @@ captive_portal: # extra fallback mechanism for when connecting if the configure time: - platform: sntp id: sntp_time - -############################################# -# MQTT Monitoring -# https://esphome.io/components/mqtt.html?highlight=mqtt -# MUST also have api enabled if you enable MQTT -############################################# -mqtt: - broker: ${mqtt_server} - topic_prefix: ${mqtt_topic}/${devicename} - username: ${mqtt_username} - password: ${mqtt_password} - #discovery: True # enable entity discovery (true is default) - #discover_ip: True # enable device discovery (true is default) + # Define the timezone of the device + timezone: "${timezone}" + # Change sync interval from default 5min to 6 hours (or as set in substitutions) + update_interval: "${sntp_update_interval}" + # Set specific sntp servers to use + servers: + - "${sntp_server_1}" + - "${sntp_server_2}" + - "${sntp_server_3}" + # Publish the time the device was last restarted + on_time_sync: + then: + - logger.log: "Synchronised sntp clock" + - text_sensor.template.publish: + id: time_sync + state: "SNTP clock Syncd" + # Update last restart time, but only once. + - if: + condition: + lambda: 'return id(device_last_restart).state == "";' + then: + - text_sensor.template.publish: + id: device_last_restart + state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");' ############################################# # Global Variables for use in automations etc @@ -221,7 +198,7 @@ globals: restore_value: true initial_value: "0" - # Counts how many consecutive boots have occurred within 10 seconds + # Counts how many consecutive boots have occurred (within X seconds) - id: boot_count type: int restore_value: true @@ -249,11 +226,11 @@ globals: initial_value: "1260" # Evening Off time (minutes from midnight), - # default 00:00 => 0 => treat as midnight + # default 24:00 => 1440 => treat as midnight - id: evening_off type: int restore_value: true - initial_value: "0" + initial_value: "1440" #################################################### # operation_mode: @@ -268,15 +245,15 @@ globals: initial_value: "3" #################################################### - # fallback_time is used if SNTP is invalid. + # 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: fallback_time + - id: current_mins type: int restore_value: false - initial_value: "720" + initial_value: "720" # 720 is 12:00 Noon #################################################### # startup_timer: counts minutes in STARTUP mode @@ -288,6 +265,7 @@ globals: restore_value: false initial_value: "0" + ############################################# # Text Sensors # https://esphome.io/components/text_sensor/index.html @@ -297,7 +275,6 @@ text_sensor: ############################ # MQTT Subscriptions ############################ - #################################################### # Subscribe to the Morning On time, format "HH:MM" # We check x.size() == 5 and x[2] == ':', @@ -305,9 +282,9 @@ text_sensor: # std::string uses 'substr', not 'substring'. #################################################### - platform: mqtt_subscribe - name: "Morning On Time" + name: "Morning On Time Setting" id: morning_on_topic - topic: "${mqtt_timer_topic}/morning-on" + topic: "${mqtt_timer_topic}/morning-on" # Stored in the format HH:MM internal: True on_value: then: @@ -321,14 +298,13 @@ text_sensor: } else { ESP_LOGW("timer","Invalid Morning On format: %s", x.c_str()); } - #################################################### # Morning Off time => "HH:MM" #################################################### - platform: mqtt_subscribe - name: "Morning Off Time" + name: "Morning Off Time Setting" id: morning_off_topic - topic: "${mqtt_timer_topic}/morning-off" + 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: @@ -341,14 +317,13 @@ text_sensor: } else { ESP_LOGW("timer","Invalid Morning Off format: %s", x.c_str()); } - #################################################### # Evening On time => "HH:MM" #################################################### - platform: mqtt_subscribe - name: "Evening On Time" + name: "Evening On Time Setting" id: evening_on_topic - topic: "${mqtt_timer_topic}/evening-on" + 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: @@ -361,14 +336,13 @@ text_sensor: } else { ESP_LOGW("timer","Invalid Evening On format: %s", x.c_str()); } - #################################################### # Evening Off time => "HH:MM" #################################################### - platform: mqtt_subscribe - name: "Evening Off Time" + name: "Evening Off Time Setting" id: evening_off_topic - topic: "${mqtt_timer_topic}/evening-off" + 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: @@ -381,7 +355,6 @@ text_sensor: } else { ESP_LOGW("timer","Invalid Evening Off format: %s", x.c_str()); } - #################################################### # Subscribe to operation mode: # OFF, ON, TIMER, STARTUP @@ -389,9 +362,9 @@ text_sensor: # (Requires typically included in ESPHome) #################################################### - platform: mqtt_subscribe - name: "Timer Operation Mode" + name: "Operation Mode Setting" id: timer_operation_mode_topic - topic: "${mqtt_timer_topic}/operation" + 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: @@ -412,7 +385,7 @@ text_sensor: ESP_LOGI("timer","Operation mode set to OFF"); } else if (strcasecmp(x.c_str(), "STARTUP") == 0) { id(operation_mode) = 3; - id(startup_timer) = 0; + 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()); @@ -438,7 +411,7 @@ text_sensor: # Expose the "Morning On" time as a text (HH:MM) ###################################################### - platform: template - name: "Morning On Time State" + name: "Timeclock: Morning On Time" lambda: |- int hour = id(morning_on) / 60; int minute = id(morning_on) % 60; @@ -452,7 +425,7 @@ text_sensor: # Expose the "Morning Off" time as a text (HH:MM) ###################################################### - platform: template - name: "Morning Off Time State" + name: "Timeclock: Morning Off Time" lambda: |- int hour = id(morning_off) / 60; int minute = id(morning_off) % 60; @@ -467,7 +440,7 @@ text_sensor: # Expose the "Evening On" time as a text (HH:MM) ###################################################### - platform: template - name: "Evening On Time State" + name: "Timeclock: Evening On Time" lambda: |- int hour = id(evening_on) / 60; int minute = id(evening_on) % 60; @@ -482,7 +455,7 @@ text_sensor: # Expose the "Evening Off" time as a text (HH:MM) ###################################################### - platform: template - name: "Evening Off Time State" + name: "Timeclock: Evening Off Time" lambda: |- int hour = id(evening_off) / 60; int minute = id(evening_off) % 60; @@ -494,25 +467,45 @@ text_sensor: update_interval: ${update_interval} ###################################################### - # ESPHome Info + # Creates a sensor showing when the device was last restarted + # Uptime template sensor, and SNTP are needed ###################################################### - - platform: version - name: ${friendly_name} Version - - platform: wifi_info - ip_address: - name: ${friendly_name} IP Address + - platform: template + name: "Last Restart: ${friendly_name}" + id: device_last_restart + update_interval: ${update_interval} + icon: mdi:clock + entity_category: diagnostic + + - platform: template + name: "Time Sync Status" + id: time_sync + update_interval: ${update_interval} + entity_category: diagnostic + + - platform: template + name: "Internal Time" + id: time_text + update_interval: ${update_interval} + entity_category: diagnostic + lambda: |- + auto time_text = id(sntp_time).now().strftime("%H:%M:%S - %d-%m-%Y"); + return { time_text }; ############################################# -# General Sensors -# https://esphome.io/components/sensor/index.html +# Sensors +# https://esphome.io/components/text_sensor/index.html ############################################# sensor: - - platform: uptime # Uptime for this device - name: ${friendly_name} Uptime - update_interval: ${update_interval} - - platform: wifi_signal # Wifi Strength - name: ${friendly_name} Wifi Signal - update_interval: ${update_interval} + - platform: template + name: "Mins from Midnight" + unit_of_measurement: "mins" + accuracy_decimals: 0 + update_interval: ${update_interval} + internal: True + lambda: |- + return id(current_mins); + #################################################### # Relay Switch (Sonoff Basic Relay on GPIO12) @@ -528,9 +521,27 @@ switch: # Check every minute to decide relay state #################################################### interval: - - interval: ${update_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 @@ -543,7 +554,7 @@ interval: // minutes, then automatically revert to TIMER. ////////////////////////////////////////////////// if (mode == 3) { - id(startup_timer)++; + 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 @@ -551,8 +562,10 @@ interval: } 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; } @@ -574,50 +587,26 @@ interval: ////////////////////////////////////////////////// // TIMER MODE => follow morning/evening schedule - // using SNTP if valid, else fallback_time + // using SNTP if valid, else current_mins ////////////////////////////////////////////////// - if (mode == 2) { - auto now = id(sntp_time).now(); - bool have_sntp = now.is_valid(); - - int current_mins; - if (!have_sntp) { - // SNTP not available => fallback clock - current_mins = id(fallback_time); - // increment the fallback clock by 1 minute - id(fallback_time) += 1; - // wrap around at 1440 => next day - if (id(fallback_time) >= 1440) { - id(fallback_time) = 0; - } - } else { - // Use real time from SNTP - current_mins = now.hour * 60 + now.minute; - } + if (mode == 2) + { bool should_on = false; - // If evening_off == 0 => treat as midnight => 1440 - int evening_off_local = id(evening_off); - if (evening_off_local == 0) { - evening_off_local = 1440; - } - // 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(morning_on) < id(morning_off)) { - if (current_mins >= id(morning_on) && current_mins < id(morning_off)) { - 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=540 => 09:00, evening_off=1440 => midnight - if (id(evening_on) < evening_off_local) { - if (current_mins >= id(evening_on) && current_mins < evening_off_local) { - should_on = true; - } + // 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 @@ -626,4 +615,8 @@ interval: } else { id(relay).turn_off(); } - } \ No newline at end of file + } + + + + diff --git a/esphome/esp-midesklamp1s.yaml b/esphome/esp-midesklamp1s.yaml index f4e9625..6abbe0b 100644 --- a/esphome/esp-midesklamp1s.yaml +++ b/esphome/esp-midesklamp1s.yaml @@ -165,8 +165,8 @@ mqtt: topic_prefix: ${mqtt_topic}/${device_name} username: ${mqtt_username} password: ${mqtt_password} - discovery: True # enable entity discovery (true is default) - discover_ip: True # enable device discovery (true is default) + discovery: False # enable entity discovery (true is default) + #discover_ip: True # enable device discovery (true is default) ############################################# @@ -291,5 +291,72 @@ light: warm_white: output_ww cold_white_color_temperature: 4800 K warm_white_color_temperature: 2500 K # 2500k is the original value of the lamp. To correct binning for 2700k to look more like 2700k use 2650k instead - restore_mode: ALWAYS_ON - gamma_correct: 0 \ No newline at end of file + restore_mode: ALWAYS_OFF + gamma_correct: 0 + + + + +#globals: +# - id: fade_brightness +# type: float +# initial_value: '0.0' +# - id: fade_step +# type: float +# initial_value: '0.0' + + +# This script fades the light ON to full brightness over 20 seconds. +#script: +# - id: fade_on +# then: +# # First, capture the current brightness. +# - lambda: |- +# // If the light is off, current brightness will be 0.0. +# float current = id(light1).current_values.get_brightness(); +# id(fade_brightness) = current; +# // Compute the step size so that after 100 steps (200ms each) we reach full brightness. +# id(fade_step) = (1.0 - current) / 100.0; +# - repeat: +# count: 100 +# then: +# - lambda: |- +# id(fade_brightness) += id(fade_step); +# if (id(fade_brightness) > 1.0) { +# id(fade_brightness) = 1.0; +# } +# // Update the light’s brightness. +# id(light1).turn_on({ brightness: id(fade_brightness) }); +# - delay: 200ms + +# This script fades the light OFF over 20 seconds. +# - id: fade_off +# then: +# - lambda: |- +# // Capture current brightness (if the light is on). +# float current = id(light1).current_values.get_brightness(); +# id(fade_brightness) = current; +# // Compute step size so that after 100 steps we reach 0. +# id(fade_step) = current / 100.0; +# - repeat: +# count: 100 +# then: +# - lambda: |- +# id(fade_brightness) -= id(fade_step); +# if (id(fade_brightness) < 0.0) { +# id(fade_brightness) = 0.0; +# } +# id(light1).turn_on({ brightness: id(fade_brightness) }); +# - delay: 200ms +# # Finally, ensure the light is turned off. +# - lambda: |- +# id(light1).turn_off(); + +#switch: +# - platform: template +# name: "Fade on and off" +# id: virtual_switch +# turn_on_action: +# - script.execute: fade_on +# turn_off_action: +# - script.execute: fade_off \ No newline at end of file diff --git a/esphome/esp-nexmulti1.yaml b/esphome/esp-nexmulti1.yaml new file mode 100644 index 0000000..8811157 --- /dev/null +++ b/esphome/esp-nexmulti1.yaml @@ -0,0 +1,86 @@ +############################################# +############################################# +# +# +# +# V1.0 2025-02-14 Initial Version +# +# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/ +# +# INSTRUCTIONS +# - +# +############################################# +############################################# + +substitutions: +############################################# +# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +############################################# + devicename: "esp-nexmulti1" + friendly_name: "esp-nexmulti1" + description_comment: "Multisensor 1 test" + + api_key: !secret esp-nexmulti1_api_key # unfortunately you can't use substitutions inside secrets names + ota_pass: !secret esp-nexmulti1_ota_pass # unfortunately you can't use substitutions inside secrets names + static_ip_address: !secret esp-nexmulti1_ip + + 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_api_key: ${api_key} + local_ota_pass: ${ota_pass} + common_mqtt: !include + file: common/mqtt_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: ${devicename} + friendly_name: ${friendly_name} + comment: ${description_comment} #Appears on the esphome page in HA + +############################################# +# ESP Platform and Framework +# https://esphome.io/components/esp32.html +############################################# +esp8266: + board: esp01_1m + +############################################# +# ESPHome Logging Enable +# 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) + #esp8266_store_log_strings_in_flash: false + #tx_buffer_size: 64 + + +############################################# +# Real time clock time source for ESPHome +# If it's invalid, we fall back to an internal clock +# https://esphome.io/components/time/ +# https://esphome.io/components/time/sntp +############################################# +#time: +# - platform: sntp +# id: sntp_time + diff --git a/esphome/esp-occupancyoffice.yaml b/esphome/esp-occupancyoffice.yaml index 4c019dc..08d1e42 100644 --- a/esphome/esp-occupancyoffice.yaml +++ b/esphome/esp-occupancyoffice.yaml @@ -183,8 +183,8 @@ mqtt: topic_prefix: ${mqtt_topic}/${devicename} username: ${mqtt_username} password: ${mqtt_password} - discovery: True # enable entity discovery (true is default) - discover_ip: True # enable device discovery (true is default) + discovery: False # enable entity discovery (true is default) + #discover_ip: True # enable device discovery (true is default) ############################################# diff --git a/esphome/esp-occupancystair.yaml b/esphome/esp-occupancystair.yaml index 757d5e7..4110936 100644 --- a/esphome/esp-occupancystair.yaml +++ b/esphome/esp-occupancystair.yaml @@ -150,8 +150,8 @@ mqtt: topic_prefix: ${mqtt_topic}/${devicename} username: ${mqtt_username} password: ${mqtt_password} - discovery: True # enable entity discovery (true is default) - discover_ip: True # enable device discovery (true is default) + discovery: false # enable entity discovery (true is default) + #discover_ip: True # enable device discovery (true is default) ############################################# # Bluetooth diff --git a/group/foxhole_lights.yaml b/group/foxhole_lights.yaml index 2886a33..ee69ad4 100644 --- a/group/foxhole_lights.yaml +++ b/group/foxhole_lights.yaml @@ -11,5 +11,5 @@ foxhole_lights: - 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.tasmo_ks811t_1181_downstbath_a # Downstairs Bathroom Main Lights - - switch.tasmo_ks811t_1181_downstbath_b # Downstairs Bathroom Mirror Lights + - switch.esp_downstbathswitch_relay_main_light # Downstairs Bathroom Main Lights + - switch.esp_downstbathswitch_relay_cabinet_light # Downstairs Bathroom Mirror Lights diff --git a/packages/appliance_status_mqttfeed.yaml b/packages/appliance_status_mqttfeed.yaml index 0571d8c..9b82326 100644 --- a/packages/appliance_status_mqttfeed.yaml +++ b/packages/appliance_status_mqttfeed.yaml @@ -1,6 +1,7 @@ mqtt: sensor: - unique_id: washing_machine_finished + device_class: timestamp name: "Washer Finished" state_topic: "viewroad-status/activityfeed/Washer_complete" icon: mdi:washing-machine diff --git a/packages/climate_master_bedroom_dehum.yaml b/packages/climate_master_bedroom_dehum.yaml new file mode 100644 index 0000000..c3f1081 --- /dev/null +++ b/packages/climate_master_bedroom_dehum.yaml @@ -0,0 +1,25 @@ +automation: + +# Automation to turn on drying at 9pm in the master bedroom (if switched on ith the helper) + - id: master_bedroom_offpeak_dehumidify + alias: Master Bedroom Off-Peak Dehumidify + mode: single + trigger: + - platform: time + at: "21:20:00" + condition: + - condition: state + entity_id: input_boolean.master_bedroom_offpeak_dehumidify + state: "on" + action: + - service: climate.set_hvac_mode + target: + entity_id: climate.master_bedroom + data: + hvac_mode: dry + - wait_for_trigger: + - platform: time + at: "00:00:00" + - service: climate.turn_off + target: + entity_id: climate.master_bedroom \ No newline at end of file diff --git a/packages/climate_master_bedroom_night_cycle.yaml b/packages/climate_master_bedroom_night_cycle.yaml index 8b98d6e..95e8b97 100644 --- a/packages/climate_master_bedroom_night_cycle.yaml +++ b/packages/climate_master_bedroom_night_cycle.yaml @@ -13,11 +13,11 @@ input_select: input_boolean: heat_pump_automation: - name: Enable Heat Master Bedroom Night Cycling + name: Enable Heat Pump Master Bedroom Night Cycling initial: off automation: - - alias: "Master Bedroom Night Cycling" + - alias: "Master Bedroom Heat Pump Night Cycling" description: "Turns the climate.master_bedroom to 'cool' for 15 minutes every hour if temperature is below the selected threshold." trigger: - platform: time_pattern # Use time_pattern to run the automation on a recurring schedule. @@ -31,7 +31,6 @@ automation: - condition: template value_template: > {{ states('sensor.master_bedroom_environment_zth01_temperature_2') | float > states('input_select.heat_pump_temperature_threshold') | float }} - # Actions: Define the steps of the automation. action: # Action 1: Turn on the heat pump by setting HVAC mode to "cool". @@ -48,7 +47,6 @@ automation: - service: climate.turn_off target: entity_id: climate.master_bedroom - mode: single # Ensures only one instance of this automation runs at a time. # always switch off the automation at 5am @@ -73,3 +71,15 @@ automation: target: entity_id: climate.master_bedroom mode: single + + # ensure the automation is off on a HA reboot + - alias: "Ensure Heat Pump Automation is Off on Startup" + description: "Turns off the heat pump automation (input_boolean.heat_pump_automation) when Home Assistant starts." + trigger: + - platform: homeassistant + event: start + action: + - service: input_boolean.turn_off + target: + entity_id: input_boolean.heat_pump_automation + mode: single diff --git a/packages/mqtt_statestream.yaml b/packages/mqtt_statestream.yaml index e911487..b606181 100644 --- a/packages/mqtt_statestream.yaml +++ b/packages/mqtt_statestream.yaml @@ -1,12 +1,12 @@ -mqtt_statestream: - base_topic: hamqtt - publish_attributes: true - publish_timestamps: true - include: - entities: - - sensor.main_bathroom_humidity_change_rate - entity_globs: - - sensor.*_zt* +#mqtt_statestream: +# base_topic: hamqtt +# publish_attributes: true +# publish_timestamps: true +# include: +# entities: +# - sensor.main_bathroom_humidity_change_rate +# entity_globs: +# - sensor.*_zt* #exclude: # entity_globs: - # - sensor.*zoruno* + # - sensor.*zoruno* \ No newline at end of file diff --git a/packages/simulation_lights.yaml b/packages/simulation_lights.yaml index 9f1d76f..5b101e1 100644 --- a/packages/simulation_lights.yaml +++ b/packages/simulation_lights.yaml @@ -40,5 +40,5 @@ switch: - 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.esp_downstbathswitch_relay_main_light # Foxhole Downstairs Bathroom Main Lights - switch.tasmo_s4chan_4231_underhouselights_b # Underhouse Main Lights diff --git a/packages/weewx.yaml b/packages/weewx.yaml index 2e305b8..42059fb 100644 --- a/packages/weewx.yaml +++ b/packages/weewx.yaml @@ -15,42 +15,42 @@ mqtt: - unique_id: weewx_outTemp_C state_topic: "weewx/outTemp_C" name: "Weewx Outdoor Temperature" - unit_of_measurement: "C" + unit_of_measurement: "°C" device_class: "temperature" value_template: "{{ value | round(1) }}" icon: "mdi:thermometer" - unique_id: weewx_inTemp_C state_topic: "weewx/inTemp_C" name: "Weewx Indoor Temperature" - unit_of_measurement: "C" + unit_of_measurement: "°C" device_class: "temperature" value_template: "{{ value | round(1) }}" icon: "mdi:thermometer" - unique_id: weewx_humidex_C state_topic: "weewx/humidex_C" name: "Weewx Humidex" - unit_of_measurement: "C" + unit_of_measurement: "°C" device_class: "temperature" value_template: "{{ value | round(1) }}" icon: "mdi:thermometer" - unique_id: weewx_dewpoint_C state_topic: "weewx/dewpoint_C" name: "Weewx Dewpoint" - unit_of_measurement: "C" + unit_of_measurement: "°C" device_class: "temperature" value_template: "{{ value | round(1) }}" icon: "mdi:thermometer" - unique_id: weewx_heatindex_C state_topic: "weewx/heatindex_C" name: "Weewx Heat Index" - unit_of_measurement: "C" + unit_of_measurement: "°C" device_class: "temperature" value_template: "{{ value | round(1) }}" icon: "mdi:thermometer" - unique_id: weewx_windchill_C state_topic: "weewx/windchill_C" name: "Weewx Windchill" - unit_of_measurement: "C" + unit_of_measurement: "°C" device_class: "temperature" value_template: "{{ value | round(1) }}" icon: "mdi:thermometer"