Automating Reef Tank Temperature Control using ESPHome + Home Assistant

The next step in my reef automation is adding temperature control. I started off with a cheap $30 standalone thermostat module – it works well but it’s super basic and the reviews said temperature can drift over time. I wanted to add a few features to my system…

  • Two temperature probes – Only one is used to control the thermostat but the second is included in some of the safety systems. These are in different chambers of the sump too just to add another another bit of redundancy.
  • A siren to alert when any alarms are triggered.
  • High temperature alarm – If either temperature probe exceeds the high limit then the system turns off the thermostat and triggers an alarm. This sets off a siren that goes off every few minutes. Once the alarm is acknowledged in Home Assistant then the temperature control starts back up.
  • Low temperature alarm – If either temperature falls below the low limit then it triggers the low alarm. This just sets off the siren every few minutes so I can investigate what might be happening.
  • Temperature “heartbeat” – If a new temperature value isn’t measured at least every 2 minutes (e.g. a sensor fails) then it triggers the high alarm.
  • Phone notification – It’s super easy to set an automation in Home Assistant so that anytime an alarm triggers it sends a notification on my phone. So whether I’m in my house or away from home traveling I can know what’s going on with my tank.

I’m using an esp32 as the heart of the reef tank controller. I chose the esp32 because it has more inputs (and because I had one on hand). The extra inputs will become important in some later upgrades. The firmware is ESPHome which integrates super easily with Home Assistant. ESPHome is also a surprising combination of simple and powerful. The same way Arduino simplifies C-programming, ESPHome simplifies automation. There’s very little actual programming. Mostly just including modules and configuring them.

Pretty much all of the automation happens on the esp32 – Home Assistant is just used as a nice front panel to display data and to acknowledge alarms. Keeping the automation local to the esp32 means if WiFi goes down then my tank doesn’t go down with it. The firmware configuration is at the bottom of this page.

My philosophy in planning all of the reef automations has been “redundancy”. I.E. two heaters, two temperature probes, etc. I put the two temperature probes in different sections of the sump. During typical operation they should read pretty similar values. But if the flow stops for some reason these temperatures might deviate from each other which if significant enough would set off an alarm.

Schematic of the Sump

Here’s a rough list of parts and prices to give an idea of how much I’ve spent to implement this:

PartModelPrice
Solid State RelayAD-SSR810-DC-48Z$25
Temperature ProbesDS18B20$8
DIN Rail$5
ESP32$6
DC Power SupplyMEAN WELL HDR-15-5$14
Receptacles, wiring, connectors, etc$10
Buzzer$2
Rough Total$70

So the price might look a little high compared to the standalone $30 module, but a lot of the cost will be spread out across other projects (auto top off, auto water change, dosing).

#substitutions is a list of variables basically. here you can easily change the high/low limits and setpoints
substitutions:
  temperature_setpoint: '25.5'
  temperature_alarm_low: '23.3'
  temperature_alarm_high: '27.5'
  temperature_hysteresis: '0.3'

esphome:
  name: reefbrain
  platform: ESP32
  board: nodemcu-32s
  on_boot:
    priority: -10
    # ...
    then:
      #on boot we turn the thermostat on ("HEAT" mode in my case) and set the desired setpoint
      - climate.control:
          id: reef_temp_control
          mode: HEAT
          target_temperature: ${temperature_setpoint}
      #This script begins the monitor that makes sure we receive a new temperature measurement at least every 2 minutes
      #If this script isn't called every 2 minutes it will start the high temperature alarm
      #Everytime we call the script the timer resets. I call this the temperature "heartbeat"
      - script.execute: temperature_heartbeat

wifi:
  ssid: "Wifi"
  password: "Wifi_Password"
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Reefbrain Fallback Hotspot"
    password: "MRF8tFN7ooul"
  #use_address: 192.168.1.20
  #You can delete the manual IP config if you want IP address to be assigned automatically
  manual_ip:
    static_ip: 192.168.1.41
    gateway: 192.168.1.1
    subnet: 255.255.255.0
    
captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

#I highly recommend passwords! It will prevent you from sending the wrong firmware to the wrong device.
ota:
  password: reefbrain

dallas:
  #Temperature sensors
  - pin: GPIO32
    update_interval: 15s

sensor:

  #Temperature Sensor 1   
  - platform: dallas
    address: 0xB1031297794BF628
    id: reef_temp_1
    name: "Reef Temperature 1"
    filters:
      #I manually calibrated my temperature sensors and figured out this offset
      - offset: 1.14
      - filter_out: nan
      - sliding_window_moving_average:
          window_size: 4
          send_every: 4
    #the next section triggers alarms and the temperature heartbeat
    #every time a valid value is received (anything besides NaN) this section is called
    on_value: 
      then:
        - if: #if temperature is in range, go ahead and reset the temperature heartbeat timer
            condition:
              sensor.in_range:
                id: reef_temp_1
                above: ${temperature_alarm_low}
                below: ${temperature_alarm_high}
            then:
            - script.execute: temperature_heartbeat
        - if: #if temperature is below the low limit then trigger the low alarm
            condition:
              sensor.in_range:
                id: reef_temp_1
                below: ${temperature_alarm_low}
            then:
            - switch.turn_on: alarm_temperature_low
        - if: #if temperature is above the high limit then trigger the high alarm
            condition:
              sensor.in_range:
                id: reef_temp_1
                above: ${temperature_alarm_high}
            then:
            - switch.turn_on: alarm_temperature_high
        
  #Temperature Sensor 2 
  - platform: dallas
    address: 0x86031497798E6228
    id: reef_temp_2
    name: "Reef Temperature 2"
    filters:
      #I manually calibrated my temperature sensors and figured out this offset
      - offset: 2.04
      - filter_out: nan
      - sliding_window_moving_average:
          window_size: 4
          send_every: 4
    #for the second (redundant) temperature sensor I only monitor for the high temperature alarm
    on_value: 
      then:
        - if:
            condition:
              sensor.in_range:
                id: reef_temp_2
                above: ${temperature_alarm_high}
            then:
            - switch.turn_on: alarm_temperature_high

      
switch:

  #Heater SSR
  - platform: gpio
    id: heater_ssr
    pin: GPIO0
    #I made this internal because the user shouldn't manually manipulate the heater - just the thermostat controller and alarms
    internal: true
    name: "Heater SSR"
  
  #Making the alarms switches means the microcontroller can trigger the switch on and do the appropriate behavior while
  #the user can easily turn it off via Home Assistant 
  
  #High Temperature Alarm  
  - platform: template
    name: "High Temperature Alarm"
    id: alarm_temperature_high
    #if you don't set optimistic to true then the switch automatically turns itself off
    optimistic: true
    on_turn_on:
    - logger.log: "High Temperature Alarm Turned On!"
      #change climate control mode to "OFF"
    - climate.control:
        id: reef_temp_control
        mode: "OFF"
      #not sure if this is 100% necessary but just to be safe turn off the SSR
    - switch.turn_off: heater_ssr
      #this while loop runs for as long as the alarm is left on. 
      #once the user turns the alarm switch off it will exit the while loop
    - while:
        condition:
          switch.is_on: alarm_temperature_high
        then:
        - logger.log: "High Temperature Alarm Still On!"
        - script.execute: triple_siren_short
        - delay: 90s
    #once the user turns the alarm off we turn the thermostat controller back on i.e. "HEAT"
    #the siren script just gives a nice audible feedback that the alarm has been acknowledged/shut off
    on_turn_off:
    - logger.log: "High Temperature Alarm Turned Off!"
    - script.execute: single_siren_short
    - climate.control:
        id: reef_temp_control
        mode: "HEAT"
  
  #Low Temperature Alarm 
  #This alarm doesn't stop the thermostat, it just sets off a siren.
  #One state you might run into is when the system is starting up you may be significantly below the low alarm
  #In that case you still want the thermostat to control, so we don't turn off the thermostat here.
  #For now this has to be acknowledged before turning off (rather than automatically turning off when in range)
  - platform: template
    name: "Low Temperature Alarm"
    id: alarm_temperature_low
    optimistic: true
    on_turn_on:
    - logger.log: "Low Temperature Alarm Turned On!"
    - while:
        condition:
          switch.is_on: alarm_temperature_low
        then:
        - logger.log: "Low Temperature Alarm Still On!"
        - script.execute: triple_siren_short
        - delay: 90s
    on_turn_off:
    - logger.log: "Low Temperature Alarm Turned Off!"
    - script.execute: single_siren_short
    
  #Siren Output  
  - platform: gpio
    pin: GPIO15
    id: siren
    name: "Siren"
    #I made this internal because there's no real usefulness in the user turning it on/off
    #The alarms and scripts manage turning it on/off.
    internal: true

climate:
  - platform: thermostat
    id: reef_temp_control
    #Note that here the same temperature sensor that has all of the alarms configured is used to control the thermostat
    sensor: reef_temp_1
    default_target_temperature_low: ${temperature_setpoint} 
    hysteresis: ${temperature_hysteresis}
    heat_action:
      - switch.turn_on: heater_ssr
    idle_action:
      - switch.turn_off: heater_ssr

script:
  #the single siren is useful as an acknowledge noise. just one simple chirp
  #the scirpt mode has to be queued or else you can't use it properly in the next script
  - id: single_siren_short
    mode: queued
    then:
      - switch.turn_on: siren
      - delay: 50ms
      - switch.turn_off: siren
      - delay: 50ms
      
  #the triple chirp is used as the alarm and always implies something is wrong    
  - id: triple_siren_short
    then:
      - script.execute: single_siren_short
      - script.execute: single_siren_short
      - script.execute: single_siren_short
      
  #this script monitors the frequency that we receive new temperature measurements
  #if a temperature sensor fails we don't want the thermostat to be stuck high and cook the aquarium
  #so the "heartbeat" idea is is that we want a consistent flow of incoming temperature measurements
  #if not we should safely shut down and turn on an alarm.
  #this script is basically always running. everytime we execute the script is restarts.
  #but if we go 2 minutes without restarting it then it will successfully complete and turn on the high alarm
  #so everytime the sensor reads a value it will restart this timer.
  - id: temperature_heartbeat 
    mode: restart 
    then:
      - delay: 2 min
      - switch.turn_on: alarm_temperature_high

About the Author

Leave a Reply

Your email address will not be published. Required fields are marked *

You may also like these

WordPress Appliance - Powered by TurnKey Linux