Control of aquarium heaters with DS18B20 sensor feedback, SOLVED

***** Solved ***** Thanks ******

I am in a quandry as to how to neatly write code to control 3 aquarium heaters and 5 DS18B20 temp sensors, an UNO R3, and a strip of solenoids.

I could bang out a couple of hundred lines of code to do it, but I'm sure a RSG will guide me to a simpler solution.

First, I'm an old COBOL mainframe programmer with a few weeks of working with the Arduino and it's C language, so don't go too far into the weeds or you'll lose me.

Here's what I want to do:

Two DS18B20's are dedicated to Aquarium temperature.
If they are within 0.5 degrees of each other, the average is taken as the desiredTemp and it is displayed on the LCD display.
If they are more divergent, an alarm message is displayed on the LCD display.
If one of the temp sensors falls out of the safety range (probably 75-85 degrees) that sensor is taken out of service along with the ever-popular display of nasty message.

The Aquarium temperature is used to:
Control 3 aquarium heaters which have a water-proofed DS18B20 attached to each one.
When heat needed turn on a heater,
If needed, turn on another heater.
If needed, turn on the last heater. (There could be more some day if I stop spending all my money on Arduino-related stuff).
When desiredTemp reached, turn all them off.
When a heater is turned on and a minute has passed, check it's attached DS18B20 to see if it has warmed up.
If it hasn't warmed up, display an alarm message on the LCD display and take it out of service.
Rotate through the heaters so that they are turned on in rotating sequence. (1,2,3 then 2,3,1 etc). This ensures that each heater is regularly tested for functionality.

I don't expect anyone to write the code for me.
I already have a sketch set up that reads the DS18B20's, turns on and off the solenoids, displays everything on the I2C-LCD, monitors water levels with float switches, turns pumps and RO/DI water solenoid on and off based on the float positions, and brushes the dog.
What I would appreciate some suggestions for how to structure the array of temp sensors in concert with the array of heaters (switch-case, array,...........).
My only idea so far is some huge nested if-else statements.

Thanks, John

I don't expect anyone to write the code for me.

Well, not that new , evidently...

I've been working on the Aquarium Maintenance sketch for 3 or so weeks. I work on it until I bang up against something I don't know. Then I bounce off to learn all about LCD's. Then I go after I2C. Just recently DS18B20 and 1-wire. When I get through this, I just got a YourDuino collision-avoidance robot that I want to play with. Then there is CNC............

I have all the water level stuff working.

Now I'm trying to get the heater stuff working.

You read my mind. Almost...

What is the current state of this project ?
(ie: still conceptual stage (nothing wired up or powered up, and possibly don't have any parts yet.)
any code yet ?
Have you used the DS18B20s yet ? (indivually or bussed ?)
Are we supposed to guess the electrical specs for the heaters (voltage, wattage , etc...) ?
Have you waterproofed the DS18B20's yet ? (do you have a plan on how to do that ?)

I could bang out a couple of hundred lines of code to do it, but I'm sure a RSG will guide me to a simpler solution.

Are we supposed to know what RSG is ? (is that like some secret name for the forum?)

If it hasn't warmed up, display an alarm message on the LCD display and take it out of service.

Well at least we know your LCD is working, don't we. (referring to "CASE CLOSED". you know what I mean)
Have you translated this post into a formal algorithm diagram ?
We know you know how to do that , right ?

First, I'm an old COBOL mainframe programmer with a few weeks of working with the Arduino and it's C language, so don't go too far into the weeds or you'll lose me.

If they are within 0.5 degrees of each other, the average is taken as the desiredTemp and it is displayed on the LCD display.

Do you know what that translates to in analogcounts on the arduino ?
Do you have a schematic yet ? (we know you know how to that right?)

raschemmel, I will try to answer all your questions.

RSG - army talk for Real Smart Guy.

I have written a 386 line sketch (1/3 comments in case I forget what I was trying to do). I have all the routines working to maintain the various water levels, based on the various float statuses.
All tested and running. I bought another Uno, the old one is in production now, maintaining water levels.

Here is the front part of the sketch:

  Aquarium Water Maintenance Program
 Keeps RO/DI water tub full and sump full and prevents aquarium overflow.
 Displays status of water levels.
 Operates 3 heaters to keep Aquarium at desired temperature.
 Displays current temperature and status of heaters.
// * * * * * * * * *  Global Declarations  * * * * * * * * * * * 

#include <Time.h>  
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <DallasTemperature.h>

/*-----( Declare Constants and Pin Numbers )-----*/
#define ONE_WIRE_BUS_PIN 13

/*-----( Declare objects )-----*/
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS_PIN);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

// Set the LCD I2C address, pinouts, and backlight
LiquidCrystal_I2C lcd(0x20, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

        // *********** Output solenoid array **********
int pumpSumpToAquarium     = 2;  // Pump to fill Aquarium from the Sump
int solenoidRoDi           = 3;  // Solenoid activates RO/DI filter to refill Freshwater Tub
int pumpFwToSump           = 4;  // Pump to refill the Sump from the Freshwater Tub
int heaterA                = 5;  // Water heaters; bypass thermostats; control directly
int heaterB                = 6;
int heaterC                = 7;
       //  ************ Input sensors *************
int switchAqOverflow       = 8;  // Float switch to indicate overflow situation in Aquarium

int switchLowerSump        = 9;  // Float switch to indicate Sump water level is low
int switchUpperSump        = 10; // Float switch to indicate Sump water level is full

int switchLowerFwTub       = 11; // Float switch to indicate Freshwater level is low
int switchUpperFwTub       = 12; // Float switch to indicate Freshwater level is full
//  #define ONE_WIRE_BUS_PIN 13; // Pin 13 is used by the 1-wire buss (see above)
        // ********* Variables for storing Machine State ********
        // ***** For Sump ******
  long timeSumpFillStarted     = 0;
  long minSumpFillHoldOff      = 5;        // (8 * 60 * 60) = 28800;  // wait 8 hours
        // ***** For Aquarium Overflow *****
  long timeAqOverflowStarted   = 0;
  long minAquariumShutdown     = 5;       //  (5 * 60) = 300;         // wait 5 minutes
  int stateAqOverflow;
  int stateLowerSump;
  int stateUpperSump;
  int stateLowerFwTub;
  int stateUpperFwTub;
  int statePumpSumpToAquarium;
  int stateSolenoidRoDi;
  int statePumpFwToSump;
  int stateHeaterA;
  int stateHeaterB;
  int stateHeaterC;
  long  timeHeaterAOn;
  long  timeHeaterAOff;
  long  timeHeaterBOn;
  long  timeHeaterBOff;
  long  timeHeaterCOn;
  long  timeHeaterCOff;
  float tempSensorA;
  float tempSensorB;
  float tempSensorC;
  float tempSensorD;
  float tempSensorE;
  float maxTempDeviationAB = 0.5;   // Temperature Sensors A and B should never deviate more than this.
  float maxAquariumTemp    = 85;    // Aquarium temperature should never exceed this
  float minAquariumTemp    = 75;    // Aquarium temperature should never fall below this
  float averageSensorAB;            // Average the 2 sensors or take the most reasonable one
  float tempDifferenceAB;
void setup() 
  time_t pctime = (1262347200) ;  // this sets the clock to some arbitrary date, I forget what date.
  setTime(pctime);                // it doesn't make any difference; I'm just looking for elapsed time
  pinMode(switchAqOverflow,       INPUT_PULLUP);
  pinMode(switchLowerFwTub,       INPUT_PULLUP);
  pinMode(switchUpperFwTub,       INPUT_PULLUP);
  pinMode(switchLowerSump,        INPUT_PULLUP);
  pinMode(switchLowerSump,        INPUT_PULLUP);
  pinMode(pumpSumpToAquarium,     OUTPUT);
  pinMode(solenoidRoDi,           OUTPUT);
  pinMode(pumpFwToSump,           OUTPUT);

  lcd.begin(20,4);               // initialize the lcd 
  lcd.noBacklight();             // turns backlight off
  lcd.backlight();               // turns backlight on
  func_InitDisplay();            // set up the display
// delay(2000);
     // Initialize the Temperature measurement library

void loop()

Every thing is working except " func_CheckHeaters();".

The 8-relay solenoid strip turns pumps, water solenoids, and heaters on and off directly from the Aurduino pins.
The float switches are water-proof reed switches with a magnetic float that gives HIGH when floating LOW when not.
They are attached directly to the Uno.

In my test program, I am reading the 5 temp probes and displaying them in degrees F on the LCD.
I compare the 2 Aquarium probes and determine deviation and take appropriate action with message to LCD for the time being.

Everything is on breadboard. (The production Uno is screwed to a piece of plywood alongside similarly mounted breadboard and LCD. The whole thing is mounted to the wall above the aquarium sump in the basement and powered from a 9v wall wart power supply).

Three 300 watt heaters 110V. I was tempted to say they are connected directly to +5 and Gnd on the Arduino, but I’ll show restraint. They are connected to the same 8-relay solenoid array opto-isolated. The relays easily handle the load. I’ve done it before.

No water-proofing yet. I plan to solder wire to their pins, heatshrinking each, goop it all up with epoxy and heat shrink it all into a little blob on a string (3 strings, actually).

Do you know what that translates to in analogcounts on the arduino ?

Nothing I’m doing is time-critical, so I don’t care how hard the Uno works.

Schematic? Schematic? We don’t need no stinkin’ schematic. It couldn’t be simpler. I ain’t making a phase-coupled warp drive.

I restate:

“My problem is that I just can’t come up with a decent algorithm for accomplishing what I want to do with the temp sensors and heaters.”

I know how to work the sensors and the heaters. I just don’t want to write a bunch of spaghetti code if I can get some guidance to help me avoid it.

When I finish this sketch with all the bells and whistles I will post it where ever youse guys think would be best. It should be pretty cool. Functionally, not necessarily esoterically.


Here’s what I got so far:

void func_ReadTemperatures()
   tempSensorA = (sensors.getTempFByIndex(0));  // Read all the temperature sensors
   tempSensorB = (sensors.getTempFByIndex(1));
   tempSensorC = (sensors.getTempFByIndex(2));
   tempSensorD = (sensors.getTempFByIndex(3));
   tempSensorE = (sensors.getTempFByIndex(4));
    tempDifferenceAB = abs(tempSensorA - tempSensorB);  // Look for deviation between sensors

   if (tempDifferenceAB > (maxTempDeviationAB))   // Report deviation
      func_BadAquariumSensor();                  // If minor, average the 2
   if (tempDifferenceAB > 5)                       // If major, determine the most likely one
   {   func_SoundAlarm();
       if (tempSensorA > maxAquariumTemp ||
          tempSensorA < minAquariumTemp)
          averageSensorAB = tempSensorB;
          averageSensorAB = tempSensorA;
      averageSensorAB = (tempSensorA + tempSensorB) / 2;

void func_CheckHeaters()

void func_SoundAlarm()

void func_BadAquariumSensor()

It would be more expedient to just draw an algorithm . It is easier to see logic errors. I can't troubleshoot your code because it is not as easy to follow as an algorithm. If you can't do that then I guess you'll have to wait for a RSG.

Are the solenoids valve switches for turning on and off water valves in a pump line ? (that would make sense , but not for a heater)

I haven't looked into the code in detail, but from your description it seems to me you need to address the problem in several layers.

At the bottom layer you have a bunch of temp sensors which can be validated against each other. If the validation fails you don't know what the temperature is, so the interface to this bottom layer should indicate whether the temperature is known, and if so what it is.

At the next layer you have some zone temperatures which may or may not be known, and you need to decide what to do when they're known and when they're unknown.

Presumably in the 'known' case you'll execute some sort of feedback algorithm to apply heat as required to reach the target temperature.

What you do in the 'unknown' case I have no idea.

It seems to me that the most likely failure modes are that you can't read a given temperature i.e. that an error occurs when you try to read it, or it returns a value which is outside the credible range. I'm skeptical about your plans to sanity-check sensors against each other when they both return a credible value, because I don't see any way to resolve the problem and by choosing to ignore both sensors could be just as harmful as using an invalid sensor. So I suggest you need to either provide three sensors (so you can use a majority voting system to detect a bad sensor) or validate each sensor in isolation and where you have redundant sensors just average the readings from the ones that appear valid.

Doooohhhh. Three sensors for the aquarium. Why didn't I think of that? They cost less than a buck each.

That makes the decision structure ever so much easier. Odd how I get my head stuck on an idea and don't consider other possibilities, regardless of difficulties.

Thanks, John

If you want redundancy for the heater drivers there is a way to do that. It would be nice to have a schematic.

Never mind.

I applied brute force and it's done. 518 lines. Not elegant, but it will work.

I have to flesh out the error handling functions (error messages, screen blinking, sound buzzer alarm, etc.).
I'm not happy with the display formatting. It could be better.

I hope to have it all tested by Monday or so. Maybe in production.

I used up all the pins except A0-A3 and D0-D1.

The buzzer will require a PWM pin, which means something will have to be relocated to A0.

I don't want to deal with shift registers just yet.

I have plans for a future expansion to do automatic periodic water changes which will require 3 more float switches and 4 additional pumps. That will bring in the shift register phase.

Thanks, John

If you want redundancy for the heater drivers there is a way to do that. It would be nice to have a schematic.

I'm now an ad hoc kinda guy. I made a living filling out charts and documentation and a lot of other stuff that made year long projects out of systems I wrote, tested, and put into production in a week or so. I seldom followed the planning documents. I just worked it out in my head and hauled off and did it. Now I think it over, work it out mentally for a while, type up a narrative, and go do it. I just had a mental block on the 5 sensors and 3 heaters. I kept thinking there might be a elegant/easy way to do it.

Then I gave that up and banged it out.

Thanks one and all for the inspiration.


If you want redundancy for the heater drivers there is a way to do that. It would be nice to have a schematic.

What I meant is that I designed a redundant system for some lights that ran off of 12 volts driven by power transistors. By using
12V CMOS I could use an EXCLUSIVE OR GATE to sense the voltage across the collector and emitter of the power transistor. If it was ON, then the voltage drop was very low and was read as a LOGIC LOW. If it was OFF , (as in not working) the voltage drop was almost 12V (logic HIGH) . I compared the signal going to the power transistor to voltage drop across it and could tell if is matched
the signal it was getting. (ON=ON, OFF=OFF) was working, (ON=OFF , OFF=ON ) WAS BAD. The same thing can be done with

Too much electronics for me.

The heaters are on the other side of opto isolated relays.

The temp sensors will work well enough.

As for redundancy, I think I will check the sensors attached (zip ties) to the heaters when they have been off for a while and compare those to the aquarium sensors. They should be spot on after a few minutes. I have 10 of them running on a breadboard and constantly displaying their values. They don’t vary among themselves by more than 0.2 or so unless a breeze wafts over them. They are stacked so tightly that the breeze can get to the ones on the outside, but not so much on the inner ones. If they were in water, i suspect they would be even closer. In 12 bit mode, the resolve down to 0.0625C.

I just discovered the Mega 2560. No shift register needed and only costs 6 bucks more than UNO. :smiley:


Also has 128k of memory compared to the UNO's 32k. After looking at your sketch, I think that's just the ticket for you.

Then I gave that up and banged it out.

There's really no substitute for learning by doing, and I'm in favour of just getting something working without trying to make it perfect. For the sort of simple project that is suitable to be implemented on an Arduino, it's actually quicker IMO to treat the first couple of versions as disposable prototypes and just stop evolving when it gets good enough to not be worth your time re-writing.

Only a measley 128K? How will I ever deal with that?

Big code is a little faster, but huge memory hog.

It isn’t hard to follow the logic like nice, tight, array, indexing elegant code.

The State Machine code that came with the YourDuino Robot Kit has me mystified. I want to modify the program, but I can’t figure out how it’s doing what it’s doing.

I’ve been looking for a forum on the Robot Kit, but haven’t found one yet.

PeterH ***********

Memory is cheap. Nothing I’m doing is time-critical. I don’t care if the Uno has to work harder; it wouldn’t be doing anything useful otherwise. Brute force code is easier to understand than the elegant stuff. And it’s done (mostly).