--- /dev/null
+#
+# https://www.aliexpress.com/item/1005004099215436.html
+#
+# GPIO23: Onboard blue LED (used to show MQTT connectivity)
+# GPIO16: Onboard relay (UFH)
+# GPIO25: Used as output for NTC bridge
+# GPIO32: ADC, midpoint of NTC bridge
+# GPIO33: DHT22 for cooker humidity/temp
+#
+# The underfloor heating NTC thermistor is connected as follows:
+#
+# GPIO25 (3.3v)
+# |
+# ▯ 10 kΩ fixed resistor
+# |
+# --- GPIO32 (ADC)
+# |
+# ▯ 10 kΩ NTC under floor
+# |
+# ⏚ GND
+#
+# Rather than leaving current flowing through the thermistor at all times and
+# potentially introducing 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: kitchen
+ room_name: "Kitchen"
+ domo_ufh: "916"
+ domo_temp: "917"
+ domo_thresh: "915"
+
+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
+
+script:
+ - id: back_to_auto
+ mode: restart
+ then:
+ - delay: 30 min
+ - climate.control:
+ id: ntc_climate
+ mode: HEAT
+
+time:
+ - platform: sntp
+ on_time:
+ - seconds: 0
+ minutes: 0
+ hours: 6
+ then:
+ climate.control:
+ id: ntc_climate
+ mode: HEAT
+ target_temperature: 21°C
+
+ - seconds: 0
+ minutes: 30
+ hours: 09
+ then:
+ climate.control:
+ id: ntc_climate
+ mode: HEAT
+ target_temperature: 10°C
+
+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_svalue)->execute(${domo_thresh},
+ std::to_string(id(ntc_climate).target_temperature));
+
+ 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<int>();
+
+ // 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();
+ }
+ {
+ auto offcall = id(ntc_climate).make_call();
+ offcall.set_mode("OFF");
+ offcall.perform();
+ }
+ id(back_to_auto).execute();
+ break;
+
+ case ${domo_thresh}: /* Temperature setpoint */
+ double temp = x["svalue1"].as<double>();
+ auto call = id(ntc_climate).make_call();
+ call.set_mode("HEAT");
+ call.set_target_temperature(temp);
+ call.perform();
+ ESP_LOGD("mqtt", "Got temp_setpoint %f", temp);
+ break;
+ }
+
+light:
+ - platform: status_led
+ pin: GPIO23
+ id: blue_led
+ restore_mode: ALWAYS_OFF
+
+switch:
+ - platform: gpio
+ id: ntc_vcc
+ pin: GPIO25
+ restore_mode: ALWAYS_OFF
+
+ - platform: gpio
+ name: "Kitchen UFH"
+ id: ufh_relay
+ pin: GPIO16
+ restore_mode: ALWAYS_OFF
+ on_turn_on:
+ then:
+ - lambda: |-
+ id(tell_domo_nvalue)->execute(${domo_ufh}, 1);
+ on_turn_off:
+ then:
+ - lambda: |-
+ id(tell_domo_nvalue)->execute(${domo_ufh}, 0);
+
+sensor:
+ - platform: dht
+ pin:
+ number: GPIO33
+ mode:
+ input: true
+ pullup: true
+ model: DHT22
+ temperature:
+ name: "${room_name} Temperature"
+ humidity:
+ name: "${room_name} Humidity"
+ update_interval: 10s
+
+ - platform: ntc
+ id: ntc_temperature
+ 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
+ filters:
+ - exponential_moving_average:
+ send_every: 1
+ send_first_at: 1
+ - 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));
+
+ - platform: resistance
+ id: ntc_resistance
+ sensor: ntc_adc
+ reference_voltage: 3.28v # measured
+ resistor: 9.97 kOhm
+ configuration: DOWNSTREAM
+ filters:
+ - median:
+ window_size: 5
+ send_every: 5
+ send_first_at: 5
+
+ - platform: adc
+ id: ntc_adc
+ attenuation: 12dB
+ update_interval: never
+ pin: GPIO32
+ filters:
+ - multiply: 0.987 # Calibrated vs. multimeter
+ # 0.80 -> 0.78
+ # 2.52 -> 2.49
+ # 1.99 -> 1.96
+
+climate:
+ - platform: thermostat
+ id: ntc_climate
+ name: "Kitchen floor"
+ sensor: ntc_temperature
+ min_heating_off_time: 300s
+ min_heating_run_time: 300s
+ min_idle_time: 30s
+ heat_deadband: 0.2°C
+ heat_action:
+ - switch.turn_on: ufh_relay
+ idle_action:
+ - switch.turn_off: ufh_relay
+ on_control:
+ - lambda: |-
+ auto temp = x.get_target_temperature();
+ if (temp && temp.value() != id(ntc_climate).target_temperature) {
+ ESP_LOGI("CLIMATE", "on_control, set target %f to %f",
+ id(ntc_climate).target_temperature, temp.value());
+ id(tell_domo_svalue)->execute(${domo_thresh},
+ std::to_string(temp.value()));
+ }
+
+
+interval:
+ - interval: 60s
+ then:
+ - switch.turn_on: ntc_vcc
+ - delay: 1s
+ - component.update: ntc_adc
+ - delay: 0.5s
+ - component.update: ntc_adc
+ - delay: 0.5s
+ - component.update: ntc_adc
+ - delay: 0.5s
+ - component.update: ntc_adc
+ - delay: 0.5s
+ - component.update: ntc_adc
+ - switch.turn_off: ntc_vcc
+
+