Simple Aquarium Temperature Sensor

It has been a while that I wanted to leverage the power of ESP-8266 NodeMCU to monitor my aquarium. My first experiment is to read the water temperature every x interval and upload it to ThingSpeak.

For this experiment, I need:

  • ESP-8266 NodeMCU;
  • DS18B20 waterproof sensors, which I bought from Amazon;
  • 4.7kΩ resistor.

The setup is simple. I used Digital I/O pin 2 for my sensor bus. DS18B20 sensors can be connected in parallel and our microcontroller will take care to address each individual sensor to read its temperature measurement. This is all handled by Dallas Temperature and 1-Wire third-party libraries. The figure below shows my setup.

ESP-8266 setup – Circuit 1

Caution: The ESP-8266 chip operates at 3.3V. Connecting it to a 5V supply will damage it. A 5V supply can be used via the micro-USB or Vin pin on the board. Similarly, the output voltage from the board are 0V for LOW and 3V to represent the HIGH logic value.

In my circuit above, Circuit 1, I used the transistor type of DS18B20. In my actual setup, I used the waterproof sensors depicted in the photo below.

Waterproof DS18B20 and terminal description

Once I had all the components in place, next I created a channel in ThingSpeak. Follow the instructions here to create your channel. With the channel created, you need the following two pieces of information: (1) the channel ID, which you can get from the channel page (see screen shot 1), and (2) the Write API key (see screen shot 2).

Channel ID – Screen Shot 1

Write API Key – Screen Shot 2

To communicate with ThingSpeak, I used the ThingSpeak Arduino library by MathWorks. This facilitates the calls to upload data to my channel. An alternative is to manually craft the http PUT url, but this is more prone errors. I based my code on Miles Burton’s Tester example. Here is my code, which can be downloaded from gitub.

  1. /*  
  2.  *  The aim of this program is to read temperature data from DS18B20 sensors and 
  3.  *  upload this data to https://www.thingspeak.com/ by using ESP8266 microcontroller.
  4.  *   
  5.  *  For information about DS18B20 refer to https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf
  6.  *  
  7.  *  In order to upload data to ThingSpeak, you need to create a free account in the said service. Once
  8.  *  you create an account, you need to create a channel and obtain a Write API key and Channel ID.
  9.  *  
  10.  *  The code below builds on the Tester.ino example by Dallas Temperature sensor library:
  11.  *     https://github.com/milesburton/Arduino-Temperature-Control-Library 
  12.  *  
  13.  *  Libraries needed to compile this code are
  14.  *  DallasTemperature.h - Dallas Temperature sensor  library by Miles Burton
  15.  *  OneWire.h - Library for Dallas/Maxim 1-Wire Chips
  16.  *  ESP8266Wifi.h - Built in to ESP8266/Arduino integration.
  17.  *  ThingSpeak.h - Offical ThinkSpeak library by Mathworks:
  18.  *      https://github.com/mathworks/thingspeak-arduino
  19.  *  
  20.  */
  21.  
  22. #include <DallasTemperature.h>
  23. #include <OneWire.h>
  24. #include <ThingSpeak.h>
  25. #include <ESP8266WiFi.h>
  26.  
  27. /***************************
  28.  * DS18B20 Settings
  29.  **************************/
  30. #define ONE_WIRE_BUS D2          // Digital I/O pin to which the DS18B20 is connected to.  This will act as our data bus.
  31. #define TEMPERATURE_PRECISION 11 // Set sensor precision to 11-bits.  Check the manufacturer's specifications for supported values.
  32.  
  33. /***************************
  34.  * WIFI Settings
  35.  **************************/
  36. const char *ssid = "YOUR WIFI NETWORK SSID";    // SSID of wireless network
  37. const char *password = "YOUR WIFI PASSWORD";    // Password for wireless network
  38.  
  39. /***************************
  40.  * ThingSpeak Settings
  41.  **************************/ 
  42. unsigned long channelNumber = 9999999;          // Thingspeak channel ID
  43. const char *writeAPIKey = "WRITE API KEY";      // Write API key
  44. int fieldStart = 1;                             // The field number to where we shall start the data upload to the channel
  45. int uploadIntervalSeconds = 60;                 // Data reading interval
  46.  
  47. /***************************
  48.  * Serial Monitor Settings
  49.  **************************/ 
  50. const int baudrate = 115200;
  51.  
  52. int status = WL_IDLE_STATUS;
  53. WiFiClient wifiClient;
  54. OneWire oneWire(ONE_WIRE_BUS);
  55. DallasTemperature sensors(&oneWire);
  56. int numberOfDevices;                           // Number of temperature devices found
  57. DeviceAddress tempDeviceAddress;               // We'll use this variable to store a found device address
  58.  
  59. void setup()
  60. {
  61.   Serial.begin(baudrate);
  62.   delay(10);
  63.  
  64.   Serial.println();
  65.   Serial.println();
  66.   Serial.print("Connecting to WiFi with ssid: " + String(ssid));  
  67.  
  68.   WiFi.begin(ssid, password);
  69.  
  70.   while (WiFi.status() != WL_CONNECTED)
  71.   {
  72.     delay(500);
  73.     Serial.print(".");
  74.   }
  75.  
  76.   Serial.println("");
  77.   Serial.println("WiFi connected");
  78.   Serial.print("IP address: " + WiFi.localIP());  
  79.  
  80.   ThingSpeak.begin(wifiClient);
  81.   sensors.begin();
  82.  
  83.   // Get a count of devices on the bus
  84.   Serial.println("Locating devices...");
  85.   numberOfDevices = sensors.getDeviceCount();
  86.   Serial.print("Found " + String(numberOfDevices, DEC) + " devices.");
  87.  
  88.   // Report parasite power requirements
  89.   Serial.print("Parasite power is: ");
  90.   if (sensors.isParasitePowerMode())
  91.     Serial.println("ON");
  92.   else
  93.     Serial.println("OFF");
  94.  
  95.   // Loop through each device, print out address
  96.   for (int i = 0; i < numberOfDevices; i++)
  97.   {
  98.     // Search the wire for address
  99.     if (sensors.getAddress(tempDeviceAddress, i))
  100.     {
  101.       Serial.print("Found device at " + String(i, DEC) + " with address: ");      
  102.       printAddress(tempDeviceAddress);
  103.       Serial.println();
  104.  
  105.       Serial.println("Setting resolution to " + String(TEMPERATURE_PRECISION, DEC));      
  106.  
  107.       // Set the resolution to TEMPERATURE_PRECISION bit (Each Dallas/Maxim device is capable of several different resolutions)
  108.       sensors.setResolution(tempDeviceAddress, TEMPERATURE_PRECISION);
  109.  
  110.       Serial.print("Resolution actually set to: ");
  111.       Serial.print(sensors.getResolution(tempDeviceAddress), DEC);
  112.       Serial.println();
  113.     }
  114.     else
  115.     {
  116.       Serial.println("Found ghost device at " + String(i, DEC) + " but could not detect address. Check power and cabling");            
  117.     }
  118.   }
  119. }
  120.  
  121. // Function to print a device address
  122. void printAddress(DeviceAddress deviceAddress)
  123. {
  124.   for (uint8_t i = 0; i < 8; i++)
  125.   {
  126.     if (deviceAddress[i] < 16)
  127.       Serial.print("0");
  128.  
  129.     Serial.print(deviceAddress[i], HEX);
  130.   }
  131. }
  132.  
  133. void loop()
  134. {
  135.   Serial.println("Requesting temperatures...");
  136.   sensors.requestTemperatures();
  137.   Serial.println("Done");
  138.  
  139.   // Iterate through each sensor and send readings to ThinkSpeak
  140.   for (int i = 0; i < numberOfDevices; i++)
  141.   {
  142.     // Search the wire for address
  143.     if (sensors.getAddress(tempDeviceAddress, i))
  144.     {
  145.       // Change this line to use getTempFByIndex if you need to read your temperateure in Fahrenheit
  146.       float temp = sensors.getTempCByIndex(i);
  147.       ThingSpeak.setField(i + fieldStart, temp);
  148.       Serial.println("Sensor #" + String(i, DEC));
  149.       Serial.println("Temperature: " + String(temp) + "°C");      
  150.     }
  151.     else
  152.     {
  153.       Serial.println("Found ghost device at " + String(i, DEC) + " but could not detect address. Check power and cabling");      
  154.     }
  155.   }
  156.  
  157.   Serial.println("Uploading data to thingspeak ");
  158.   ThingSpeak.writeFields(channelNumber, writeAPIKey); 
  159.  
  160.   delay(uploadIntervalSeconds * 1000);
  161. }

The code has a lot of comments to make it easier to understand. It is split into three main sections. The first section defines the libraries we need and the parameters to use to initialize our objects. Make sure that you change these values as instructed below before you upload the code to your module. The next section is the setup() method. Here we instantiate our objects. First we connect to our WiFi, and then discover the sensors connected to our bus. Our bus is configured to the digital i/o pin 2 in line 30. I am also using an 11-bit temperature precision, set in line 31. Note that the more precise your measurement is, the more time is required to compute it; refer to the DS18B20 datasheet here. Lastly, the loop() method. Every uploadIntervalSeconds we get the data from our sensors and upload them to ThingSpeak. For experimental purposes, you can comment out line 158 to do not upload your data. You can also troubleshoot and see what is going on through the Serial Monitor. If you want your temperature measurements in Fahrenheit, change the method call in line 146 to read getTempFByIndex() instead of getTempCByIndex().

Before you upload your code to the NodeMCU module, make sure that you change the following:

  • Your WiFi SSID and password in lines 36 and 37;
  • Lines 42 and 43 must reflect your ThingSpeak channel ID and Write API Key;
  • Change the value in seconds in line 45 if you want more coarse sampling rate. Currently it is set to 1 minute (60 seconds).
  • You may also change the value in line 44 if you already have channel fields defined and the values you are going to submit must be after these. Leave it set to 1 if the values you are going to upload are the only ones in your channel.

Here is a screenshot of my aquarium temperature readings:

Aquarium water temperature for the last 60 minutes

My next step is to host a web server on my NodeMCU module and get real-time temperature by browsing to the server other than logging in ThingSpeak. I may also add some alarms if the water temperature goes out of a set range. But let’s leave this for another time. As Clounce always say, enjoy coding and have fun!