From 6edf7384fb5e033684fa722a799d542c93959565 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 3 Oct 2024 16:37:52 +0100 Subject: [PATCH] Underfloor heating for bathroom --- bathroomfloor.yaml | 230 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 bathroomfloor.yaml diff --git a/bathroomfloor.yaml b/bathroomfloor.yaml new file mode 100644 index 0000000..3de7774 --- /dev/null +++ b/bathroomfloor.yaml @@ -0,0 +1,230 @@ +# +# https://www.aliexpress.com/item/1005004099215436.html +# +# GPIO23: Onboard blue LED (used to show MQTT connectivity) +# GPIO16: Onboard relay (bathroom fan) +# GPIO25: Used as output for NTC bridge +# GPIO32: ADC, midpoint of NTC bridge +# +# The underfloor heating NTC thermistor is connected as follows: +# +# GPIO25 (3.3v) +# | +# ▯ 10 kΩ fixed resistor +# | +# --- GPIO24 (ADC) +# | +# ▯ 10 kΩ NTC under floor +# | +# ⏚ GND +# +# Rather than leaving current flowing through the thermistor at all times and +# potentially introduicing errors by warming it up, we only turn GPIO25 on +# when it's time to take a reading. +# +# So we disable the periodic measurement on the ADC sensor, then periodically +# turn on GPIO25, trigger *three* readings a second apart, and turn GPIO25 off +# again. The ADC sensor has a filter to average the three readings. + +substitutions: + name: bathroomfloor + domo_ufh: "912" + domo_temp: "913" + domo_thresh: "911" + +esphome: + name: ${name} + +esp32: + board: esp32-gateway + framework: + type: esp-idf + +packages: + base: !include base.yaml + +wifi: + power_save_mode: none + networks: + - ssid: !secret wifi_ssid + password: !secret wifi_pw + bssid: !secret wndr3800_bssid + priority: 1 + +globals: + - id: relay_stable_time + type: time_t + initial_value: '0' + + - id: temp_setpoint + type: float + initial_value: '21.0' + restore_value: true + + - id: ufh_manual_delay + type: int + initial_value: '1800' + restore_value: true + + - id: ufh_auto_delay + type: int + initial_value: '120' + restore_value: true + + +time: + - platform: sntp + +mqtt: + on_connect: + then: + - light.turn_on: blue_led + - delay: 2s # Too soon and the first messages don't get through! + - lambda: |- + id(tell_domo_nvalue)->execute(${domo_ufh}, id(ufh_relay).state); + id(tell_domo_nsvalues)->execute(${domo_thresh}, 2, std::to_string(id(temp_setpoint))); + + on_disconnect: + then: + - light.turn_off: blue_led + + on_json_message: + - topic: domoticz/out + then: + - lambda: |- + int idx = x["idx"]; + int nvalue = x["nvalue"].as(); + + // ESP_LOGD("on_json_message", x["name"]); + switch (idx) { + case ${domo_ufh}: /* UFH relay */ + if (nvalue) { + id(ufh_relay).turn_on(); + } else { + id(ufh_relay).turn_off(); + } + // Delay for longer if turned on manually + id(relay_stable_time) = ::time(NULL) + id(ufh_manual_delay); + break; + + case ${domo_thresh}: /* Temperature setpoint */ + id(temp_setpoint) = x["svalue1"].as(); + ESP_LOGD("mqtt", "Got temp_setpoint %f", id(temp_setpoint)); + break; + } + +light: + - platform: status_led + internal: true + pin: GPIO23 + id: blue_led + restore_mode: ALWAYS_OFF + +switch: + - platform: gpio + id: ntc_vcc + pin: GPIO25 + restore_mode: ALWAYS_OFF + + - platform: gpio + name: "Bathroom UFH" + id: ufh_relay + pin: GPIO16 + restore_mode: ALWAYS_OFF + on_turn_on: + then: + - lambda: |- + id(tell_domo_nvalue)->execute(${domo_ufh}, 1); + id(relay_stable_time) = ::time(NULL) + id(ufh_auto_delay); + on_turn_off: + then: + - lambda: |- + id(tell_domo_nvalue)->execute(${domo_ufh}, 0); + id(relay_stable_time) = ::time(NULL) + id(ufh_auto_delay + 30); + +sensor: + - platform: ntc + sensor: ntc_resistance + calibration: + # Hiwell E91.716 gives these in detail. The SunStone Touchstat manual just says + # "10kΩ at 25°C, 12.1kΩ at 20°C, 14.7kΩ at 15°C, which looks basically the same. + # ESPHome only wants three. + # - 22.0706 kOhm -> 5°C + # - 17.9600 kOhm -> 10°C + # - 14.6962 kOhm -> 15°C + # - 12.0911 kOhm -> 20°C + # - 10.0000 kOhm -> 25°C + # - 8.3124 kOhm -> 30°C + # - 6.9434 kOhm -> 35°C + # - 5.82525 kOhm -> 40°C + # Actual readings from SunStone Touchstat + - 15.0 kOhm -> 13.9°C +# - 12.55 kOhm - > 17.6°C + - 12.30 kOhm -> 18.2°C + - 9.97 kOhm -> 23.5°C + name: "Bathroom floor temperature" + filters: + - clamp: + min_value: 5 + max_value: 35 + ignore_out_of_range: true + on_value: + then: + lambda: |- + if (!isnan(x)) + id(tell_domo_svalue)->execute(${domo_temp}, std::to_string(x)); + + bool cur_state = id(ufh_relay).state; + bool want_state; + if (x > id(temp_setpoint)) + want_state = false; + else if (x < id (temp_setpoint)) + want_state = true; + else + return; + + if (want_state != cur_state) { + time_t now = ::time(NULL); + if (now < id(relay_stable_time)) { + ESP_LOGD("UFH", "Too soon to turn %s (%ld seconds)", want_state ? "ON" : "OFF", + now - id(relay_stable_time)); + } else { + id(ufh_relay).toggle(); + } + } + + - platform: resistance + id: ntc_resistance + sensor: ntc_adc + reference_voltage: 3.28v # measured + resistor: 9.97 kOhm + configuration: DOWNSTREAM + filters: + - sliding_window_moving_average: + window_size: 3 + send_every: 3 + send_first_at: 3 + + - platform: adc + id: ntc_adc + attenuation: 12dB + update_interval: never + pin: GPIO34 + filters: + - multiply: 0.987 # Calibrated vs. multimeter + # 0.80 -> 0.78 + # 2.52 -> 2.49 + # 1.99 -> 1.96 + +interval: + - interval: 60s + then: + - switch.turn_on: ntc_vcc + - delay: 2s + - component.update: ntc_adc + - delay: 1s + - component.update: ntc_adc + - delay: 1s + - component.update: ntc_adc + - switch.turn_off: ntc_vcc + -- 2.50.1