# WIFI Temperature Monitor



## Chiumanfu (Oct 30, 2014)

Here is a DIY temperature monitor with WIFI connectivity, graphing and email alerts. I needed this for my shrimp tank as it is only 10 gallons and located in a not so well insulated garage. I have a min/max thermometer in the garage and I recorded 7C in the winter and 24C in the summer. I have a 100W Aqueon Pro in the tank now and I'm not sure if it will be adequate during the winter. I feel more confident with this watching the temp to make sure that things don't get out of hand without me noticing.

I used an Arduino Nano and a waterproof DS18B20 sensor. Wifi connectivity is through the SparkFun CC3000 breakout board. The display is a generic 20x4 LCD. I had to use a switching regulator because the onboard Arduino regulator does not have enough guts to power the CC3000. Eventually I will build a permanent PCB and box for this but for now it lives in a pretty sheltered spot on the shelf beside the tank so I'm not in a rush. The LCD displays current temp, max temp, min temp, WIFI status and time(mS) until next Grovestreams update.









I used a service called Grovestreams for data acquisition and alerting. This platform is really powerful and I've been using it for other "InternetOfThings" projects for a while now. It is totally free under a certain threshold. This project should never exceed that threshold unless you have more that 20 running all at once. Here is a shot of the data visualization, which for this project is simply a line graph. You can create dashboards and all sorts of different charts, graphs and gauges with Grovestreams.









*Fritzing diagram*









*Parts List*
Arduino Nano V3.0 - Clone from eBay $7. Make sure it has the 328P processor and FT232RL USB chip.

CC3000 Wifi Breakout Board - Clone from eBay $25. The genuine Sparkfun unit is $35. If you have wired ethernet close by, you can just use a W5100 shield and save more than half this cost.

DS18B20 Waterproof sensor - eBay $2. Beware that the colour code of the wires may not be the same. Mine was Red=VCC Yellow=GND Green=SIG.

20x4 LCD Display - eBay $7. Any display that is HD44780 compatible will work (almost all are). You can use other sizes with code alterations.

Switching Regulator - eBay $2. These are overkill for what we need but I have a pile of them in my junk box so I used it. Any power supply capable of 5V @ 1A will work fine. Check your junk box for old wall adapters and electronics power adapters.

*Arduino Code*

```
// Aquarium Temp Monitor by Chiu Fang
// No rights reserved. If you use this code in your project, buy me a beer.

// Include Libraries
#include <OneWire.h>                                             // For DS18B20 Temperature Sensor
#include <DallasTemperature.h>                                   // For DS18B20 Temperature Sensor
#include <LiquidCrystalFast.h>                                   // For LCD Display
#include <SPI.h>                                                 // For CC3000 WIFI
#include <SFE_CC3000.h>                                          // For CC3000 WIFI
#include <SFE_CC3000_Client.h>                                   // For CC3000 WIFI

// Set constants
const byte temperaturePin = A3;                                  // DS18B20 temp sensor pin
LiquidCrystalFast lcd (3, 4, 5, 6, 7, 8, 9);                     // rs,rw,en1,d4,d5,d6,d7 Init LCD
OneWire oneWire(temperaturePin);                                 // Init DS18B20 One Wire
DallasTemperature sensors(&oneWire);                             // Init Dallas Temp library
DeviceAddress temp = { 0x28, 0x2A, 0xC1, 0xD2, 0x05, 0x00, 0x00, 0xBB };     // DS18B20 ID#
const float calibration = 0.6;                                   // Adjust for DS18B20 accuracy error

// CC3000 Setup
#define CC3000_INT      2                                        // Needs to be an interrupt pin (D2/D3)
#define CC3000_EN       A0                                       // Can be any digital pin
#define CC3000_CS       10                                       // Preferred is pin 10 on Uno
#define IP_ADDR_LEN     4                                        // Length of IP address in bytes
char ap_ssid[] = "YOURwifiSSID";                                    // SSID of network
char ap_password[] = "YOURwifiPASSWORD";                                // Password of network
unsigned int ap_security = WLAN_SEC_WPA2;                        // Security of network
unsigned int timeout = 30000;                                    // Timeout in Milliseconds
SFE_CC3000 wifi = SFE_CC3000(CC3000_INT, CC3000_EN, CC3000_CS);
SFE_CC3000_Client client = SFE_CC3000_Client(wifi);

// Grovestream setup
char gsDomain[] = "grovestreams.com";                            // GroveStreams Domain
String gsApiKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";        // Grovestreams API Key
String gsComponentID = "Arduino";                                // Grovestreams Component ID
const unsigned long gsUpdateFrequency = 900000;                  // GroveStreams update frequency 15min
unsigned long gsLastSuccessfulUploadTime = 0;                    // Timer for interval data streams
unsigned long gsConnectAttemptTime = 0;                          // Timer for reconnects
boolean gsLastConnected = false;                                 // Flag for connection status
int gsFailedCounter = 0;                                         // Counter for failed connects

// Declare Variables
float tempValue;                                                 // Temperature Variable
float maxTemp = 25;                                              // Max Temperature Holder
float minTemp = 25;                                              // Min Temperature Holder
int i;                                                           // Scratchpad

void setup () {
  Serial.begin(115200);                                          // For debug
  lcd.begin(20, 4);                                              // Config LCD
  sensors.begin();                                               // Init DS18B20 sensors
  sensors.setResolution(temp, 12);                               // Set DS18B20 resolution
  sensors.requestTemperatures();                                 // Read temp from DS18B20 sensors
  tempValue = sensors.getTempC(temp) + calibration;              // Apply calibration value
  minTemp = tempValue;                                           // Preset min marker
  maxTemp = tempValue;                                           // Preset max marker
  wifiConnect();                                                 // Connect WIFI
}

void loop () {
// Get temperature from DS18B20
  sensors.requestTemperatures();                                 // Read temp from DS18B20 sensors
  tempValue = sensors.getTempC(temp) + calibration;              // Apply calibration value
  if (tempValue < minTemp && tempValue > -100) {                 // Check for Min temp
    minTemp = tempValue;
  }
  if (tempValue > maxTemp) {                                     // Check for Max temp
    maxTemp = tempValue;
  }
  lcd.setCursor(0, 0);                                           // Display info on LCD screen
  lcd.print(F("Temp:     "));
  lcd.setCursor(5, 0);
  lcd.print(tempValue, 3);
  lcd.setCursor(0, 1);
  lcd.print(F("Max :     "));
  lcd.setCursor(5, 1);
  lcd.print(maxTemp, 3);
  lcd.setCursor(0, 2);
  lcd.print(F("Min :     "));
  lcd.setCursor(5, 2);
  lcd.print(minTemp, 3);

// Create strings from floats for Grovestream string
  char temp1[7] = {0};                                           // Initialize buffer to nulls
  dtostrf(tempValue, 7, 3, temp1);                               // Convert float to string
  String tempValueS(temp1);                                      // Save string
  tempValueS.trim();                                             // Trim white space

// Send data to Grovestream
  while(client.available()) {                                    // Send host response to serial monitor
    char c = client.read();
    Serial.print(c);
  }
  
  if(!client.connected() && gsLastConnected) {                   // Disconnect from GroveStreams
    Serial.println(F("...disconnected"));
    client.close();
  }

  if(!client.connected() && (millis() - gsLastSuccessfulUploadTime > gsUpdateFrequency)) { // 15 minutes
    gsConnectAttemptTime = millis();                             // Set the interval timer
    if (client.connect(gsDomain, 80)) {                          // Connect to grovestream server
      String url = "PUT /api/feed?compId=" + gsComponentID;      // Construct the string
      url += "&api_key=" + gsApiKey;
      url += "&t1=" + tempValueS;
      url += " HTTP/1.1";
      client.println(url);                                       //Send the string
      client.println("Host: " + String(gsDomain));
      client.println(F("Connection: close"));
      client.println(F("Content-Type: application/json"));
      client.println();
      Serial.println(url);                                       // Print the string for debug

      if (client.connected()) {                                  // Confirm status
        gsLastSuccessfulUploadTime = gsConnectAttemptTime;
        gsFailedCounter = 0;
        lcd.setCursor(0, 3);
        lcd.print(F("WIFI OK   "));
      } else {
        gsFailedCounter++;                                       // Connection failed. Increase failed counter
        Serial.println("Connection to GroveStreams failed ("+String(gsFailedCounter, DEC)+")");
        lcd.setCursor(0, 3);
        lcd.print(F("WIFI ERROR          "));
        delay(5000);
      }
    } else {
      gsFailedCounter++;                                         // Connection failed. Increase failed counter
      Serial.println("Connection to GroveStreams Failed ("+String(gsFailedCounter, DEC)+")");
      lcd.setCursor(0, 3);
      lcd.print(F("WIFI ERROR          "));
      delay(5000);
    }
  }
  
  if (gsFailedCounter > 10 ) {                                   // Check if CC3000 needs to be restarted
    client.close();                                              // Stop client
    wifi.disconnect();                                           // Disconnect socket
    delay(5000);
    wifiConnect();                                               // Restart CC3000
  }

  gsLastConnected = client.connected();                          // Set last connected flag
  
  lcd.setCursor(14, 3);                                          // Display countdown to next update
  lcd.print(F("      "));
  lcd.setCursor(14, 3);
  lcd.print(gsUpdateFrequency - (millis() - gsLastSuccessfulUploadTime));
        
// Debug info
  Serial.print(F("Ram:"));
  Serial.print(freeRam());
  Serial.print(F(","));
  Serial.print(F("Millis:"));
  Serial.print(millis());
  Serial.println(F(","));
}

void wifiConnect() {                                             // WIFI Connect routine
  ConnectionInfo connection_info;
  if ( wifi.init() ) {                                           // Initialize CC3000 SPI
    Serial.println(F("CC3000 initialization complete"));
  } else {
    Serial.println(F("Something went wrong during CC3000 init!"));
    lcd.setCursor(0, 3);
    lcd.print(F("WIFI NO CC"));
  }
  Serial.print(F("Connecting to SSID: "));                       // Connect using DHCP
  Serial.println(ap_ssid);
  if(!wifi.connect(ap_ssid, ap_security, ap_password, timeout)) {
    Serial.println(F("Error: Could not connect to AP"));
    lcd.setCursor(0, 3);
    lcd.print(F("WIFI NO AP"));
  }
  if ( !wifi.getConnectionInfo(connection_info) ) {              // Gather connection details
    Serial.println(F("Error: Could not obtain connection details"));
    lcd.setCursor(0, 3);
    lcd.print(F("WIFI NO RX"));
  } else {
    Serial.print(F("IP Address: "));
    for (i = 0; i < IP_ADDR_LEN; i++) {
      Serial.print(connection_info.ip_address[i]);
      if ( i < IP_ADDR_LEN - 1 ) {
        Serial.print(".");
      }
    }
    Serial.println();
    lcd.setCursor(0, 3);
    lcd.print(F("WIFI OK   "));
  }
}

int freeRam() {                                                  // RAM monitor routine
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
```
*Grovestreams Configuration*
Grovestreams is actually quite simple to configure considering the flexibility and power of the platform. I will not dive into all the nitty gritty details here but I will show you the config screens for the temp sensor and alarms. To start you have to sign up for Grovestreams and get your API Key. Enter it into the code where shown.

Create a component called "Arduino". Add a new stream and configure it as shown.









You can now start up the Arduino and you'll see new data after refreshing the screen in 15 minute intervals.

Now let create an alarm event that sends you an email when the temp drifts high or low. Create a new Event. Type is "Condition Event". Configure as shown. This tells Grovestreams to email you when the temp is above 24C or below 20C.









But what if your internet goes down. Let's create another alert that sends you an email when Grovestreams doesn't hear anything from the Arduino for more than 2 hours. Create a new Event. Type is "Latency Event". Configure as shown.









If you want to build one for yourself and need help, don't hesitate to message me.


----------



## mikebike (Sep 8, 2010)

Thank you for the informative post.


----------



## MEDHBSI (Sep 4, 2011)

Wow that is high tech! Very cool too. Thanks for the interesting read


----------



## TomC (Apr 21, 2010)

Very cool. You should make a few and sell them.


----------



## effox (Apr 21, 2010)

I don't know if it'd be profitable to sell them for a reasonable price, but it's definitely a cool project.

I still haven't figured out a project to use an arduino with.


----------



## Chiumanfu (Oct 30, 2014)

Yeah I don't think people would be willing to pay even the cost for parts for what is ultimately an over-glorified thermometer.

On the other hand, if you want to see the exact temperature of my shrimp tank right now...

The geek factor might be worth the price of admission.


----------



## mikebike (Sep 8, 2010)

a couple of years ago I picked up a digital temperature gun at Canadian Tire sale for $19.95
it is great I just point and shoot instant digital tem read out.

I find it a handy too for checking microwaved food/oven tems as well as aquarium use


----------



## Chiumanfu (Oct 30, 2014)

Typically those IR thermometer guns are +-1.5C accuracy. A 3 degree spread is pretty big when you're talking about sensitive inverts.


----------



## mikebike (Sep 8, 2010)

I found mine was 1 degree F low on the measurement through the glass.
I also have thermometers in the tanks to compare with if I suspect the reading are off.

Yes 3 degrees can be a lot.


----------



## Chiumanfu (Oct 30, 2014)

Interesting graph comparing Aqueon Pro 100 to the Eheim Jager 100. I would have preferred to use the Aqueon as it is shorter and fits the tank better but the Eheim does a way better job of regulating temp.


----------



## Chiumanfu (Oct 30, 2014)

Added a fan relay and a single fan for testing. I will find a way to mount the fan more securely soon. Only 10 lines of code including declarations. The trigger temperature is hardcoded for now with variables fanTempHi and fanTempLo. The fan turns on at 23.5C and turns off at 22.85C. This gives a good amount of hysteresis so the fan in not toggling on and off around a single trigger point.




















```
// Aquarium Temp Monitor by Chiu Fang
 
// Include Libraries
#include <OneWire.h>                                             // For DS18B20 Temperature Sensor
#include <DallasTemperature.h>                                   // For DS18B20 Temperature Sensor
#include <LiquidCrystalFast.h>                                   // For LCD Display
#include <SPI.h>                                                 // For CC3000 WIFI
#include <SFE_CC3000.h>                                          // For CC3000 WIFI
#include <SFE_CC3000_Client.h>                                   // For CC3000 WIFI
// #include <avr/wdt.h>
 
// Set constants
const byte temperaturePin = A3;                                  // DS18B20 temp sensor pin
const byte fanRelayPin = A2;
LiquidCrystalFast lcd (3, 4, 5, 6, 7, 8, 9);                     // rs,rw,en1,d4,d5,d6,d7 Init LCD
OneWire oneWire(temperaturePin);                                 // Init DS18B20 One Wire
DallasTemperature sensors(&oneWire);                             // Init Dallas Temp library
DeviceAddress temp = { 0x28, 0x2A, 0xC1, 0xD2, 0x05, 0x00, 0x00, 0xBB };     // DS18B20 ID#
//DeviceAddress temp = { 0x28, 0x50, 0x07, 0xD3, 0x05, 0x00, 0x00, 0xDD };
 
const float calibration = 0.6;                                   // Adjust for DS18B20 accuracy error
const float fanTempHi = 23.5;
const float fanTempLo = 22.85;
 
// CC3000 Setup
#define CC3000_INT      2                                        // Needs to be an interrupt pin (D2/D3)
#define CC3000_EN       A0                                       // Can be any digital pin
#define CC3000_CS       10                                       // Preferred is pin 10 on Uno
#define IP_ADDR_LEN     4                                        // Length of IP address in bytes
char ap_ssid[] = "YOURwifiSSID";                                    // SSID of network
char ap_password[] = "YOURwifiPASSWORD";                                // Password of network
unsigned int ap_security = WLAN_SEC_WPA2;                        // Security of network
unsigned int timeout = 30000;                                    // Timeout in Milliseconds
SFE_CC3000 wifi = SFE_CC3000(CC3000_INT, CC3000_EN, CC3000_CS);
SFE_CC3000_Client client = SFE_CC3000_Client(wifi);
 
// Grovestream setup
char gsDomain[] = "grovestreams.com";                            // GroveStreams Domain
String gsApiKey = "xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";        // Grovestreams API Key
String gsComponentID = "Arduino";                                // Grovestreams Component ID
const unsigned long gsUpdateFrequency = 900000;                  // GroveStreams update frequency 15min
unsigned long gsLastSuccessfulUploadTime = 0;                    // Timer for interval data streams
unsigned long gsConnectAttemptTime = 0;                          // Timer for reconnects
int gsFailedCounter = 0;                                         // Counter for failed connects
 
// Declare Variables
float tempValue;                                                 // Temperature Variable
float maxTemp = 25;                                              // Max Temperature Holder
float minTemp = 25;                                              // Min Temperature Holder
int i;                                                           // Scratchpad
 
void setup () {
  Serial.begin(115200);                                          // For debug
  lcd.begin(20, 4);                                              // Config LCD
  sensors.begin();                                               // Init DS18B20 sensors
  sensors.setResolution(temp, 12);                               // Set DS18B20 resolution
  sensors.requestTemperatures();                                 // Read temp from DS18B20 sensors
  tempValue = sensors.getTempC(temp) + calibration;              // Apply calibration value
  minTemp = tempValue;                                           // Preset min marker
  maxTemp = tempValue;                                           // Preset max marker
  digitalWrite(fanRelayPin, HIGH);
  pinMode(fanRelayPin, OUTPUT);
}
 
void loop () {
// Get temperature from DS18B20
  sensors.requestTemperatures();                                 // Read temp from DS18B20 sensors
  tempValue = sensors.getTempC(temp) + calibration;              // Apply calibration value
  if (tempValue < minTemp && tempValue > -100) {                 // Check for Min temp
    minTemp = tempValue;
  }
  if (tempValue > maxTemp) {                                     // Check for Max temp
    maxTemp = tempValue;
  }
  lcd.setCursor(0, 0);                                           // Display info on LCD screen
  lcd.print(F("Temp:     "));
  lcd.setCursor(5, 0);
  lcd.print(tempValue, 3);
  lcd.setCursor(0, 1);
  lcd.print(F("Max :     "));
  lcd.setCursor(5, 1);
  lcd.print(maxTemp, 3);
  lcd.setCursor(0, 2);
  lcd.print(F("Min :     "));
  lcd.setCursor(5, 2);
  lcd.print(minTemp, 3);
  
// Control fan relay
  if (tempValue >= fanTempHi){
    digitalWrite(fanRelayPin, LOW); // turn fan on if tank temperature is high
  } else if (tempValue <= fanTempLo){
    digitalWrite(fanRelayPin, HIGH); // turn fan off if tank temperature drops
  }
 
// Create strings from floats for Grovestream string
  char temp1[7] = {0};                                           // Initialize buffer to nulls
  dtostrf(tempValue, 7, 3, temp1);                               // Convert float to string
  String tempValueS(temp1);                                      // Save string
  tempValueS.trim();                                             // Trim white space
 
// Start sending data
  if(millis() - gsLastSuccessfulUploadTime > gsUpdateFrequency) { // 15 minutes
    Serial.println(F("Starting Upload"));
    gsConnectAttemptTime = millis();                             // Set the interval timer
    Serial.println(F("Wifi Connecting"));
    wifiConnect();                                                 // Connect WIFI
    Serial.println(F("Client Connecting"));
    if (client.connect(gsDomain, 80)) {                          // Connect to grovestream server
      Serial.println(F("Client Connect Successful"));
      String url = "PUT /api/feed?compId=" + gsComponentID;      // Construct the string
      url += "&api_key=" + gsApiKey;
      url += "&t=" + tempValueS;
      url += " HTTP/1.1";
      client.println(url);                                       //Send the string
      Serial.println(url);                                       // Print the string for debug
      client.println("Host: " + String(gsDomain));
      client.println(F("Connection: close"));
      client.println(F("Content-Type: application/json"));
      client.println();
      gsLastSuccessfulUploadTime = gsConnectAttemptTime;
      delay(10000);
      Serial.println(F("Reading Response"));
      while(client.available()) {                                    // Send host response to serial monitor
        char c = client.read();
        Serial.print(c);
      }
      Serial.println(F("Closing Client"));
      client.close();                                              // Stop client
      Serial.println(F("Flushing Buffer"));
      client.flush();
      Serial.println(F("Wifi Disconnecting"));
      wifi.disconnect();
      lcd.setCursor(0, 3);
      lcd.print(F("WIFI SENT "));
    } else {
      Serial.println(F("Client Connect Failed - Closing Socket"));
      client.close();                                              // Stop client
      client.flush();
      wifi.disconnect();
      gsFailedCounter++;
      lcd.setCursor(0, 3);
      lcd.print(F("WIFI FAIL "));
    }
  }
  
  if (gsFailedCounter > 2 ) {
    Serial.println(F("Connection Retries Exceeded - Retrying in 15 minutes"));
    gsLastSuccessfulUploadTime = gsConnectAttemptTime;
    gsFailedCounter = 0;
    delay(10);
    software_Reset();
  }
  
  lcd.setCursor(14, 3);                                          // Display countdown to next update
  lcd.print(F("      "));
  lcd.setCursor(14, 3);
  lcd.print(millis() - gsLastSuccessfulUploadTime);
        
// Debug info
  Serial.print(F("Ram:"));
  Serial.print(freeRam());
  Serial.print(F(","));
  Serial.print(F("Millis:"));
  Serial.print(millis());
  Serial.println(F(","));
}
 
void wifiConnect() {                                             // WIFI Connect routine
  ConnectionInfo connection_info;
  if ( wifi.init() ) {                                           // Initialize CC3000 SPI
    Serial.println(F("CC3000 initialization complete"));
  } else {
    Serial.println(F("Something went wrong during CC3000 init!"));
    lcd.setCursor(0, 3);
    lcd.print(F("WIFI NO CC"));
  }
  Serial.print(F("Connecting to SSID: "));                       // Connect using DHCP
  Serial.println(ap_ssid);
  if(!wifi.connect(ap_ssid, ap_security, ap_password, timeout)) {
    Serial.println(F("Error: Could not connect to AP"));
    lcd.setCursor(0, 3);
    lcd.print(F("WIFI NO AP"));
  }
  if ( !wifi.getConnectionInfo(connection_info) ) {              // Gather connection details
    Serial.println(F("Error: Could not obtain connection details"));
    lcd.setCursor(0, 3);
    lcd.print(F("WIFI NO RX"));
  } else {
    Serial.print(F("IP Address: "));
    for (i = 0; i < IP_ADDR_LEN; i++) {
      Serial.print(connection_info.ip_address[i]);
      if ( i < IP_ADDR_LEN - 1 ) {
        Serial.print(".");
      }
    }
    Serial.println();
    lcd.setCursor(0, 3);
    lcd.print(F("WIFI CONN "));
  }
}
 
int freeRam() {                                                  // RAM monitor routine
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
 
void software_Reset()
// Restarts program from beginning but 
// does not reset the peripherals and registers
{
  asm volatile ("  jmp 0");  
}
```


----------

