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