esphome: name: hall-sat-thermostat friendly_name: Hall SAT Thermostat esp32: board: esp32-c6-devkitc-1 framework: type: esp-idf # Enable logging logger: # Enable Home Assistant API api: encryption: key: "xxxx" ota: - platform: esphome password: "xxxx" wifi: networks: - ssid: xxxx password: xxxx - ssid: xxxx password: xxxx # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "Hall-Sat-Thermostat" password: "xxxx" captive_portal: # I2C bus configuration i2c: sda: GPIO22 scl: GPIO23 scan: true frequency: 100kHz substitutions: temp_offset: "-0.8" # SHT35 sensor sensor: - platform: sht3xd address: 0x44 temperature: name: "Hall Temperature" accuracy_decimals: 2 filters: - offset: ${temp_offset} humidity: name: "Hall Humidity" accuracy_decimals: 2 heater_enabled: false update_interval: 30s # ===================== # Sun sensor for dimming # ===================== - platform: homeassistant id: sun_elevation entity_id: sun.sun attribute: elevation internal: true # ===================== # Globals # ===================== globals: - id: blink_state type: bool restore_value: no initial_value: 'false' - id: flash_brightness type: float restore_value: no initial_value: '0.0' - id: flash_direction type: int restore_value: no initial_value: '1' # ===================== # GPIO outputs (PWM for dimming) # ===================== output: - platform: ledc id: gpio_output_a pin: GPIO00 frequency: 1000Hz - platform: ledc id: gpio_output_h pin: GPIO01 frequency: 1000Hz - platform: ledc id: gpio_output_c pin: GPIO02 frequency: 1000Hz # ===================== # HA switches # ===================== switch: - platform: homeassistant id: ha_switch_state_a entity_id: switch.heating_away_true - platform: homeassistant id: ha_switch_state_h entity_id: switch.heating_home_true - platform: homeassistant id: ha_switch_state_c entity_id: switch.heating_comfort_true - platform: homeassistant id: ha_override entity_id: switch.heating_off_true # ===================== # Text sensor for current thermostat mode # ===================== text_sensor: - platform: homeassistant id: current_thermostat_mode entity_id: climate.hall attribute: preset_mode # ===================== # Binary sensors # ===================== binary_sensor: # Momentary push button to cycle thermostat mode - platform: gpio pin: number: GPIO17 mode: INPUT_PULLUP inverted: true name: "Thermostat Mode Button" filters: - delayed_on: 50ms on_press: then: - homeassistant.service: service: climate.set_preset_mode data: entity_id: climate.hall data_template: preset_mode: >- {% set current = states.climate.hall.attributes.preset_mode %} {% if current == 'away' %}home {% elif current == 'home' %}comfort {% else %}away {% endif %} # Heating Off Override sensor - platform: homeassistant id: heating_off_override entity_id: switch.heating_off_true # ===================== # Interval for flashing / controlling GPIOs # ===================== interval: - interval: 50ms # Faster interval for smooth fading then: - lambda: |- // Calculate brightness based on sun elevation // Full brightness above 0°, dims from 0° to -6° (civil twilight), min 10% below -6° float base_brightness = 1.0; if (!isnan(id(sun_elevation).state)) { float elevation = id(sun_elevation).state; if (elevation > 0) { base_brightness = 1.0; // Full brightness during day } else if (elevation > -6) { // Fade from 100% at 0° to 10% at -6° base_brightness = 0.1 + (0.9 * (elevation + 6) / 6); } else { base_brightness = 0.1; // Minimum 10% brightness at night } } // Determine if GPIO26 should flash bool flash_mode = (id(current_thermostat_mode).state == "none" || id(current_thermostat_mode).state == "activity" || id(current_thermostat_mode).state == "sleep") && !id(heating_off_override).state; // Soft flash: fade in and out smoothly if (flash_mode) { // Adjust flash brightness (0.0 to 1.0) float step = 0.05; // Adjust this for faster/slower fade (0.05 = ~1 second cycle) id(flash_brightness) += step * id(flash_direction); // Reverse direction at limits if (id(flash_brightness) >= 1.0) { id(flash_brightness) = 1.0; id(flash_direction) = -1; } else if (id(flash_brightness) <= 0.0) { id(flash_brightness) = 0.0; id(flash_direction) = 1; } } else { id(flash_brightness) = 1.0; // Full on for steady mode } // GPIO25 (output_a) steady control with dimming if (id(ha_switch_state_a).state && !id(heating_off_override).state) id(gpio_output_a).set_level(base_brightness); else id(gpio_output_a).set_level(0); // GPIO26 (output_h) flash / steady / off with dimming if (!id(heating_off_override).state) { if (flash_mode) { // Apply both base brightness (sun) and flash brightness (fade effect) id(gpio_output_h).set_level(base_brightness * id(flash_brightness)); } else if (id(current_thermostat_mode).state == "home") { // Steady ON only for home id(gpio_output_h).set_level(base_brightness); } else { // OFF for away / comfort id(gpio_output_h).set_level(0); } } else { id(gpio_output_h).set_level(0); } // GPIO27 (output_c) steady control with dimming if (id(ha_switch_state_c).state && !id(heating_off_override).state) id(gpio_output_c).set_level(base_brightness); else id(gpio_output_c).set_level(0);