Jump to content

WIFI Temperature Monitor with Email Alerts


Chiumanfu

Recommended Posts

There is a lot of talk on the forum about cooling our shrimp tanks now that summer is coming fast. I thought you guys might be interested in a project I've been working on.
 
Here is a DIY temperature monitor with WIFI connectivity, graphing and email alerts. I needed this for one of my shrimp tanks 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.
shrimptank_breadboard.jpg
 
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.
shrimptank_graph.jpg
 
Fritzing diagram
shrimptank_fritzing.jpg
 
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 // 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 constantsconst byte temperaturePin = A3;                                  // DS18B20 temp sensor pinLiquidCrystalFast lcd (3, 4, 5, 6, 7, 8, 9);                     // rs,rw,en1,d4,d5,d6,d7 Init LCDOneWire oneWire(temperaturePin);                                 // Init DS18B20 One WireDallasTemperature sensors(&oneWire);                             // Init Dallas Temp libraryDeviceAddress 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 // 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 byteschar ap_ssid[] = "YOURwifiSSID";                                    // SSID of networkchar ap_password[] = "YOURwifiPASSWORD";                                // Password of networkunsigned int ap_security = WLAN_SEC_WPA2;                        // Security of networkunsigned int timeout = 30000;                                    // Timeout in MillisecondsSFE_CC3000 wifi = SFE_CC3000(CC3000_INT, CC3000_EN, CC3000_CS);SFE_CC3000_Client client = SFE_CC3000_Client(wifi); // Grovestream setupchar gsDomain[] = "grovestreams.com";                            // GroveStreams DomainString gsApiKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";        // Grovestreams API KeyString gsComponentID = "Arduino";                                // Grovestreams Component IDconst unsigned long gsUpdateFrequency = 900000;                  // GroveStreams update frequency 15minunsigned long gsLastSuccessfulUploadTime = 0;                    // Timer for interval data streamsunsigned long gsConnectAttemptTime = 0;                          // Timer for reconnectsint gsFailedCounter = 0;                                         // Counter for failed connects // Declare Variablesfloat tempValue;                                                 // Temperature Variablefloat maxTemp = 25;                                              // Max Temperature Holderfloat minTemp = 25;                                              // Min Temperature Holderint 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} 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 // 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");  } 
 
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.
shrimptank_component-stream.jpg
 
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.
shrimptank_value-condition-event.jpg
 
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.
shrimptank_latency-event.jpg
 

Here's an 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.
shrimptank_temp.jpg
 
If you want to build an over-glorified thermometer for yourself and need help, don't hesitate to message me. 
 
If you want to see the exact temperature of my shrimp tank right now. If you see gaps or no data, it's because my wife likes to turn off the WIFI router when no one is using the internet. The CC3000 module doesn't handle losing connectivity very well. As soon as I can run an ethernet cable to the garage, I'm going to swap the CC3000 for a W5100 wired ethernet module.

 

If you want to see how far you can really take it. Look at my Aquaponics setup. And more recent

Link to comment
Share on other sites

My goal for next week is to add a relay to control a fan. It's going to be a hot summer and I want to be prepared.

Also on the roadmap is to add an Atlas Scientific pH probe and TDS probe. I've used the Atlas Scientific stuff on my Aquaponics system and I love it. Little expensive but dead simple and lab grade accuracy.

Link to comment
Share on other sites

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.

 

20150528-2491-WEB.jpg

 

20150528-2492-WEB.jpg

// 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 constantsconst byte temperaturePin = A3;                                  // DS18B20 temp sensor pinconst byte fanRelayPin = A2;LiquidCrystalFast lcd (3, 4, 5, 6, 7, 8, 9);                     // rs,rw,en1,d4,d5,d6,d7 Init LCDOneWire oneWire(temperaturePin);                                 // Init DS18B20 One WireDallasTemperature sensors(&oneWire);                             // Init Dallas Temp libraryDeviceAddress 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 errorconst 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 byteschar ap_ssid[] = "YOURwifiSSID";                                    // SSID of networkchar ap_password[] = "YOURwifiPASSWORD";                                // Password of networkunsigned int ap_security = WLAN_SEC_WPA2;                        // Security of networkunsigned int timeout = 30000;                                    // Timeout in MillisecondsSFE_CC3000 wifi = SFE_CC3000(CC3000_INT, CC3000_EN, CC3000_CS);SFE_CC3000_Client client = SFE_CC3000_Client(wifi); // Grovestream setupchar gsDomain[] = "grovestreams.com";                            // GroveStreams DomainString gsApiKey = "xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";        // Grovestreams API KeyString gsComponentID = "Arduino";                                // Grovestreams Component IDconst unsigned long gsUpdateFrequency = 900000;                  // GroveStreams update frequency 15minunsigned long gsLastSuccessfulUploadTime = 0;                    // Timer for interval data streamsunsigned long gsConnectAttemptTime = 0;                          // Timer for reconnectsint gsFailedCounter = 0;                                         // Counter for failed connects // Declare Variablesfloat tempValue;                                                 // Temperature Variablefloat maxTemp = 25;                                              // Max Temperature Holderfloat minTemp = 25;                                              // Min Temperature Holderint 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");  } 
Link to comment
Share on other sites

  • 3 weeks later...

I've made a lot of changes since the original posting.

 

I added a LED driver/fade controller same as shown here on another tank of mine.

http://www.plantedtank.net/forums/showthread.php?t=779337

 

I ordered an Atlas Scientific pH probe and I may have a lead on a 2nd hand Atlas Scientific TDS probe.

 

I got rid of the CC3000 WIFI board. After extended use, I've come to realize that it is garbage and there is some major bugs that TI is not willing to fix. I am converting the code to use a standard W5100 wired ethernet board and using Powerline ethernet adapters to get the signal back to my router.

 

I had to upgrade the brains to an Arduino Mega 2560.

 

I will post more details when I've ironed out all the bugs.

Link to comment
Share on other sites

  • 3 months later...
  • 4 weeks later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...