How I Monitor Home Temperature

We live in an old house. Real old. It has no A/C, and the rooms all seem to have a temperature range that’s less than desirable. What’s worse, the temperatures you go to bed with are seldom what you wake up with. That’s not too odd, really, but when you are trying to cool a room down without A/C using fans and (hopefully) open windows it gets tricky. So, what can be done about this? Nothing. Truthfully nothing. But I can monitor the temperatures. That’s where this article takes off. It’s my continuous journey into microcontrollers and C/C++ programming, and some new interfaces along the way.

A while back I wrote an article on how to trigger LED’s like the famous front-end of KITT.  I used an NE555 and CD4017 for that.  It worked, but then I redesigned it with an Arduino, thus beginning my infatuation with this little device.

So how do I monitor my home temperatures?  Maxim IC (formerly Dallas Semiconductor) DS18B20 digital temperature sensors.  I chose the TO-92 packages since they’re easier to work up than the SO version.  You can pick these up on eBay for cheap or if you’re clever you can get samples for no cost.  You can figure the latter option out from here, you’re smart.

I don’t have a home phone, but the house is wired for one to each room.  That helps, because home phone wiring at layer 1 is just a long two-wire bus.  What makes this perfect is you can drop multiple DS18B20’s on one pair of wire.  Each sensor has a 64-bit serial burned into it, so each one can be uniquely identified by the Arduino when polling.  I bought a handful of perf-boards from the local electronics supply shop for $0.49 each.  From another shop I bought PCB mount RJ-25C jacks.  I could have used RJ-11 but I wanted more pairs to work with.

What You’ll Need


  • Arduino
  • Small Perf Board(s)
  • DS18B20 Sensor(s)
  • 5k Potentiometer (take my advice)
  • Male header strip (straight or 90°)
  • Female breadboard jumper wire
  • Connect wire
  • Soldering iron
  • Solder

Some Telecom Nerdery For The Uninitiated


RJ-11 = one pair, RJ-14 = two pairs, RJ-25 = three pairs. Also if you choose to use RJ-45, be mindful that standard phone cords reverse the wiring 180° and patch cords are straight-through. If you wire the sensor backward, congratulations, you just made a tiny white smoke generator.


The rest is mostly procedural.  Solder the RJ-25 to the PCB.  The pins number from left to right when looking into the jack, 1 through 6.  Solder the header strip to the PCB.  You’ll likely be using the center pair (white/blue+blue/white, sometimes red/green in older homes) so wire up the headers so that one header connects to pin 3 and another header connects to pin 4.  This is important.  Work out your polarity as you go or prepare for some more white smoke.  My board images are here and here.  Ignore the unlabeled pins – those are for other, unrelated sensors.  One thing at a time here.

So far, your Arduino will jumper onto the header pins labeled 1, 2 & 3 with +5Vdc, digital PIN and ground, respectively.  That gets you connected to the 5K pot and a DS18B20 sensor.  Technically you could stop here because the Arduino has access to the one sensor and the 5K pot is there to act as the pullup resistor.  Incidentally, in the balloon at the top, when I said “take my advice”, well, here’s why.  Almost all write-ups call for a 4k7 resistor.  That’s fine until you begin dropping more DS18B20 sensors on the same bus.  Others say use a 3k3 resistor.  I’m not going to play musical resistors and solder it up just to find out I need to change it later.  The 5K pot IS a resistor, and a variable resistor at that.  So as you add more sensors, if you wired it up the way I illustrated, you just dial the 5K pot counter-clockwise and like magic, you have a weaker pullup resistor. I’ve had to drop the resistance on mine twice as I’ve added sensors. Like I said, take my advice.

OK, now if you have more sensors and your house is all wired up, don’t stop yet.  Let’s crank out some more perf boards.  Only this time you just need to have the RJ-25 and a sensor.  The 5K pot only goes at the Arduino point in the bus and no headers are needed since this perf board will strictly be receiving the connection from the wall, not injecting it into the wall like the core board..  The way we left it, you can take a standard phone cord and connect one end into the wall plate, where a phone would normally connect, and since we didn’t use RJ-45’s and patch cords, that jack is all ready to go.  The other end of the cord goes on the perf board jack.

 Remember when I brought up how phone cords are 180° reversing? Well, you just connected it to the wall backward, sort of. It’s how telecom works. For residential phone service the carrier delivers it reversed. The cord you put between the wall and the phone reverses it another 180° and, OMG, now it’s straight. Your Arduino is acting as the carrier now and is sending the signal into the wall reversed, and the perf boards at the other outlets will connect it back straight. You’re now smarter about Telecom than some people I’ve worked with.

So at the other outlets you just connect the wall jack to the RJ-25 on the perf board. Done. Here’s what my remote perf boards look like here and here.

ds181b20.network

Now for some code. The magic begins here with your Arduino and an additional library. Download the OneWire library here.  Add it to your IDE in the libraries directory.  Restart the IDE if you had it open when you copied it into the directory.  In the IDE, click File >> Examples >> Onewire >> Dallas DS18x20.  This is the automatic serial ROM discovery sketch so you can identify the ID’s of each sensor.  All you need to do is change the Arduino PIN reference if you’re not connecting the bus to PIN 10 and the serial baud rate if you’re not using 9600.  To the right are my settings in the sketch and the results of the sketch.

Tip of the centuryConnect the sensors one at a time so you know which is which. This should be self-explanatory.

How you go about incorporating the serial numbers into your sketch is up you. There are a few generally “accepted” ways to do it. I can’t say which way is better than another, although I think identifying them once, assigning the ID to a var and then referencing that var for the duration is the best way to do it. Since I’m still adding sensors to the home I have my sketch searching in a loop. One-by-one they’re discovered and asked to convert their reading and store it in the sensors scratchpad. On the next run through the loop we’ll retrieve it. This, as you may have noticed is different from how the discovery sketch goes about business, and there’s a reason. The discovery sketch is all about, well, discovery. It isn’t meant for production. If you were to use that method in production it would induce a 750ms delay at each sensor reading. That’s how long it takes to convert the reading and store it in scratchpad at 12-bit resolution. I know, 750ms is nothing. True, until you have 7 sensors. It adds up, especially when there are other time sensitive things occurring that we should be watching for. That’s why I separated the discover and convert process from the fetch process. My sketch requests the temp be converted and stored in scratchpad, then moves on for another 750ms. Then, after the 750ms has lapsed I go back read the temperature, store it in a var and move on to another sensor. Rinse and repeat, forever. This way I can process other code during the waiting period.

Wondering what my code looks like? Of course. Is there room for optimization, oh yeah, you bet. At this point since it’s still in a “work in progress” state I’ll tidy it up later.

My Code

#include <CountingStream.h>
#include <Xively.h>
#include <XivelyClient.h>
#include <XivelyDatastream.h>
#include <XivelyFeed.h>
#include <b64.h>
#include <HttpClient.h>
#include <SPI.h>
#include <Ethernet.h>
#include <Dns.h>
#include <SD.h>
#include <OneWire.h>
#include <stdlib.h>
#include <Time.h>
#include <EthernetUdp.h>
// size of buffer used to capture HTTP requests
//#define REQ_BUF_SZ   60
#define DS18S20_ID 0x10
#define DS18B20_ID 0x28
// Your Xively key to let you upload data
char xivelyKey[] = "XXXXXXXX";
//your xively feed ID
#define xivelyFeed XXXXXXXX
//datastreams
char owSensor0[] = "Downstairs";
char owSensor1[] = "Master_Bedroom";
char owSensor2[] = "Front_Room";
char owSensor3[] = "Garage";
char owSensor4[] = "Back_Porch";
char owSensor5[] = "Zoe-s_Room";
char owSensor7[] = "Black_Refrigerator";
/*
char owSensor6[] = "Unassigned 1";
char owSensor8[] = "Unassigned 3";
*/
char irDetectorPin24[] = "Power_Usage";
// Define the strings for our datastream IDs
XivelyDatastream datastreams[] = {
XivelyDatastream(owSensor0, strlen(owSensor0), DATASTREAM_FLOAT),
XivelyDatastream(owSensor1, strlen(owSensor1), DATASTREAM_FLOAT),
XivelyDatastream(owSensor2, strlen(owSensor2), DATASTREAM_FLOAT),
XivelyDatastream(owSensor3, strlen(owSensor3), DATASTREAM_FLOAT),
XivelyDatastream(owSensor4, strlen(owSensor4), DATASTREAM_FLOAT),
XivelyDatastream(owSensor5, strlen(owSensor5), DATASTREAM_FLOAT),
XivelyDatastream(irDetectorPin24, strlen(irDetectorPin24), DATASTREAM_FLOAT),
XivelyDatastream(owSensor7, strlen(owSensor7), DATASTREAM_FLOAT)
};
// Finally, wrap the datastreams into a feed
XivelyFeed feed(xivelyFeed, datastreams, 8 /* number of datastreams */);
// MAC address from Ethernet shield sticker under board
byte mac[] = { 0x00, 0x10, 0x36, 0x00, 0x25, 0x60 };
char thingSpeakServer[] = {"api.thingspeak.com"}; //ThingSpeak Server
//EthernetServer server(2560);  // create a server at port 80
//File webFile;               // the web page file on the SD card
//char HTTP_req[REQ_BUF_SZ] = {0}; // buffered HTTP request stored as null terminated string
//char req_index = 0;              // index into HTTP_req buffer
//boolean LED_state[4] = {0}; // stores the states of the LEDs
OneWire  ds(22); //set up the OneWire call
float ow0;
float ow1;
float ow2;
float ow3;
float ow4;
float ow5;
float ow6;
float ow7;
float ow8;
char charBuff[10];
String owA = "";
String owB = "";
String owC = "";
String owD = "";
String owE = "";
String owF = "";
String owG = "";
String owH = "";
String owI = "";
byte i;
int j;
byte present = 0;
byte type_s;
byte data[12];
byte addr[8];
float celsius, fahrenheit;
float temp;
unsigned long previousOwLoop = 0;
long unlockTime = 1800000;
byte dataReadyToPoll = 0;
unsigned long lockedAt22a = 0;
unsigned long lockedAt22b = 0;
unsigned long lockedAt22c = 0;
unsigned long lockedAt22d = 0;
unsigned long lockedAt22e = 0;
unsigned long lockedAt22f = 0;
unsigned long lockedAt22g = 0;
unsigned long lockedAt22h = 0;
unsigned long lockedAt22i = 0;
boolean locked22a = false;
boolean locked22b = false;
boolean locked22c = false;
boolean locked22d = false;
boolean locked22e = false;
boolean locked22f = false;
boolean locked22g = false;
boolean locked22h = false;
boolean locked22i = false;
float currentTemp;
String tempLocName;
volatile unsigned long wattHourPulses = 0;
byte oldPulsesPer60s = 0;
float kWh;
byte owSensorId;
char smtpServer[] = {"smtp.XXXXXXXX.net"};
EthernetClient email;
EthernetClient Xively;
unsigned long previousMillis = 0;
long interval1 = 60000;
//NTP
IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov
EthernetUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets
int timeZone = -7;  // set this shit later
//******************************BEGIN SDG&E POLL FUNCTION
void powerPoll() {
wattHourPulses++;
}
//******************************END SDG&E POLL FUNCTION
void setup() {
Serial.begin(115200);
delay(2000);
pinMode(10,OUTPUT);
digitalWrite(10,LOW);
pinMode(53, OUTPUT); //Ethernet
pinMode(4,OUTPUT);
digitalWrite(4,HIGH);
Serial.print("SPI Bus initialized");
Serial.println();
Serial.print("Waiting for DHCP...");
Serial.println();
int i = 0;
int DHCP = 0;
DHCP = Ethernet.begin(mac);
//Try to get dhcp settings 30 times before giving up
while( DHCP == 0 && i < 30){
delay(1000);
DHCP = Ethernet.begin(mac);
i++;
}
if(!DHCP){
Serial.println("DHCP FAILED");
for(;;); //Infinite loop because DHCP Failed
}
Serial.println("DHCP Success");
Serial.print("Ethernet initialized");
Serial.println();
Serial.print("IP number assigned by DHCP is ");
Serial.println(Ethernet.localIP());
Serial.println();
Udp.begin(localPort);
Serial.println("Waiting for NTP sync");
setSyncProvider(getNtpTime);
Serial.println();
//pinMode(2, INPUT);
pinMode(3, INPUT);
pinMode(5, INPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(22,OUTPUT);        //OneWire Bus
pinMode(24, INPUT_PULLUP); //IR power meter pulse detect
pinMode(26, OUTPUT);       //IR LED confirmation flicker
Serial.println();
Serial.print("Arduino PINS initialized");
Serial.println();
Serial.println();
Serial.print("GO");
Serial.println();
Serial.println();
delay(1000);
Serial.println();
Serial.println("1-Wire ready.....");
attachInterrupt (0, powerPoll, RISING);  // to catch the change from LOW to HIGH
}
time_t prevDisplay = 0; // when the digital clock was displayed NTP STUFF, HERE FOR SOME REASON
void loop() {
//******************************BEGIN 60 SECOND UPDATE
//check the 60 second timers to see if we need to update the web server
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > interval1) {
previousMillis = currentMillis;
Serial.print(millis());
Serial.println("Fetching kWh for last 60 seconds");
getPowerNow();
Serial.println("Posting data to Xively.com");
xivelyPost();
Serial.println("Summary of temperature data sent");
sendThingSpeakData();
Serial.print("Free bytes of sRAM = ");
Serial.println(freeRam());
Serial.println(millis());
}  //******************************END 60 SECOND UPDATE
oneWirePoll();
int timeZone = -7;  // reread it so we can do come math
if (timeStatus() != timeNotSet) { //makes the serial mon display time forever
if (now() != prevDisplay) { //update the display only if time has changed
prevDisplay = now();
digitalClockDisplay();
}
}
if (wattHourPulses > oldPulsesPer60s) {
Serial.print("Meter Pulse Registered >> wattHourPulses = ");
Serial.println(wattHourPulses);
oldPulsesPer60s = wattHourPulses;
for (int i=0;i<512;i++) {
digitalWrite(26,HIGH);
}
digitalWrite(26,LOW);
}
} // END OF MAIN LOOP
//******************************BEGIN SDG&E POLL FUNCTION
void getPowerNow() {
kWh = ((wattHourPulses * 60) * .001);  //wH pulses/sec * 60 minutes * wH:kWh ratio
datastreams[6].setFloat(kWh); //set Xively
Serial.print("Current kWh = ");
Serial.println(kWh);
Serial.println();
cli();
wattHourPulses = 0;
sei();
oldPulsesPer60s = 0;
}
//******************************END SDG&E POLL FUNCTION
//******************************BEGIN THINGSPEAK FUNCTION
void sendThingSpeakData() {
EthernetClient thingSpeak;
if (thingSpeak.available()) {
char c = thingSpeak.read();
Serial.print(c);
}
Serial.println();
Serial.println("Initiating connection to ThingSpeak.com");
Serial.println("Connecting...");
delay(1000); //This one keeps it from hanging
char charBuff[10];
String owA = "";
String owB = "";
String owC = "";
String owD = "";
String owE = "";
String owF = "";
String owG = "";
String owH = "";
String owI = "";
memset(charBuff, '\0', 10);
dtostrf(ow0, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owA += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow1, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owB += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow2, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owC += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow3, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owD += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow4, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owE += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow5, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owF += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow6, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owG += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow7, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owH += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow8, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owI += charBuff[i];
}
if (thingSpeak.connect(thingSpeakServer, 80)) {
Serial.println("Connected!");
delay(1000);
String apiKeyArray[5] = {"VVVVVVVVVV"}; //,"WWWWWWWWWW","XXXXXXXXXX","YYYYYYYYYY","ZZZZZZZZZZ"};
String channelName[5] = {"channel VVVV"}; //,"channel WWWW","channel XXXX","channel YYYY","channel ZZZZ"};
//update the all ThingSpeak channels in a loop
//loop through the different API keys to make 5 unique URL's
for (byte n=0;n<1;n++) { //set the 2nd operator to number of channels + 1
thingSpeak.print("GET /update?key="+apiKeyArray[n]);
thingSpeak.print("&field1=");
thingSpeak.print(owA);
thingSpeak.print("&field2=");
thingSpeak.print(owB);
thingSpeak.print("&field3=");
thingSpeak.print(owC);
thingSpeak.print("&field4=");
thingSpeak.print(owD);
thingSpeak.print("&field5=");
thingSpeak.print(owE);
thingSpeak.print("&field6=");
thingSpeak.print(owF);
thingSpeak.print("&field7=");
thingSpeak.print(kWh);
thingSpeak.println(" HTTP/1.1");
thingSpeak.println("Host: api.thingspeak.com");
thingSpeak.println();
Serial.print("Updated ");
Serial.print(channelName[n]);
Serial.print(" channel using API key: ");
Serial.println(apiKeyArray[n]);
delay(450);
}
}
else {
Serial.println("Connection to ThingSpeak.com failed");
}
thingSpeak.stop();
Serial.print("Connection to ThingSpeak.com closed");
Serial.println();
while(thingSpeak.status() != 0) {
delay(5);
}
Serial.println();
Serial.println("||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
Serial.println("|  Temp  | Friendly Name  | Sensor | Alias1 | Alias2 | Alarm |");
Serial.println("||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
Serial.println("| "+owA+"  | Downstairs     |  ow0   |  owA   |  0x04  |   "+locked22a+"   |");
Serial.println("| "+owB+"  | Master Bedroom |  ow1   |  owB   |  0xF7  |   "+locked22b+"   |");
Serial.println("| "+owC+"  | Front Room     |  ow2   |  owC   |  0x84  |   "+locked22c+"   |");
Serial.println("| "+owD+"  | Garage         |  ow3   |  owD   |  0x81  |   "+locked22d+"   |");
Serial.println("| "+owE+"  | Back Porch     |  ow4   |  owE   |  0xDC  |   "+locked22e+"   |");
Serial.println("| "+owF+"  | Zoe's Room     |  ow5   |  owF   |  0x61  |   "+locked22f+"   |");
Serial.println("| "+owG+"  | Unassigned 1   |  ow6   |  owG   |  0xDA  |   "+locked22g+"   |");
Serial.println("| "+owH+"  | Black Refer    |  ow7   |  owH   |  0x0D  |   "+locked22h+"   |");
Serial.println("| "+owI+"  | Unassigned 3   |  ow8   |  owI   |  0xF4  |   "+locked22i+"   |");
Serial.println("||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
Serial.println();
} //******************************END THINGSPEAK FUNCTION
//******************************BEGIN DS18B20 FUNCTION
void oneWirePoll() {
if (millis() - previousOwLoop > 4163) {
previousOwLoop = millis();
dataReadyToPoll = 1;
//find a device
if (!ds.search(addr)) {
ds.reset_search();
return;
}
if (OneWire::crc8( addr, 7) != addr[7]) {
return;
}
if (addr[0] != DS18S20_ID && addr[0] != DS18B20_ID) {
return;
}
ds.reset();
ds.select(addr);
// Start conversion
ds.write(0x44, 1);
}
if ((millis() - previousOwLoop > 750) && (dataReadyToPoll == 1)) {
dataReadyToPoll = 0;
present = ds.reset();
ds.select(addr);
ds.write(0xBE);         // Read Scratchpad
// the eighth ROM byte indicates ROM CRC
switch (addr[7]) {
case 0x04: //0x28, 0xDD, 0x9C, 0xDD, 0x04, 0x00, 0x00, 0x04
tempLocName = "Downstairs";
owSensorId = 0;
break;
case 0xF7: //0x28, 0x19, 0x3E, 0xDD, 0x04, 0x00, 0x00, 0xF7
tempLocName = "Master Bedroom";
owSensorId = 1;
break;
case 0x84: //0x28, 0x32, 0xFA, 0xDD, 0x04, 0x00, 0x00, 0x84
tempLocName = "Front Room";
owSensorId = 2;
break;
case 0x81: //0x28, 0x77, 0xCE, 0xDD, 0x04, 0x00, 0x00, 0x81
tempLocName = "Garage";
owSensorId = 3;
break;
case 0xDC: //0x28, 0x0D, 0x7F, 0xDE, 0x04, 0x00, 0x00, 0xDC
tempLocName = "Back Porch";
owSensorId = 4;
break;
case 0x61: //0x28, 0x36, 0x17, 0x10, 0x05, 0x00, 0x00, 0x61
tempLocName = "Zoe's Room";
owSensorId = 5;
break;
case 0xDA: //0x28, 0xAF, 0x11, 0x10, 0x05, 0x00, 0x00, 0xDA
tempLocName = "Unassigned 1";
owSensorId = 6;
break;
case 0x0D: //0x28, 0x0C, 0x14, 0x10, 0x05, 0x00, 0x00, 0x0D
tempLocName = "Black Refrigerator";
owSensorId = 7;
break;
case 0xF4: //0x28, 0x50, 0xF1, 0x0F, 0x05, 0x00, 0x00, 0xF4
tempLocName = "Unassigned 3";
owSensorId = 8;
break;
default:
Serial.println();
Serial.print("Can not determine bit 8 from the ROM");
Serial.println();
Serial.print("I must have found a new sensor on the bus");
Serial.println();
return;
}
// the first ROM byte indicates which chip
switch (addr[0]) {
case 0x10:
//Serial.println("  Chip = DS18S20");  // or old DS1820
type_s = 1;
break;
case 0x28:
//Serial.println("  Chip = DS18B20");
type_s = 0;
break;
case 0x22:
//Serial.println("  Chip = DS1822");
type_s = 0;
break;
default:
//Serial.println("Device is not a DS18x20 family device.");
return;
}
//Serial.print("  Data = ");
//Serial.print(present, HEX);
//Serial.print(" ");
for ( i = 0; i < 9; i++) {           // we need 9 bytes
data[i] = ds.read();
//Serial.print(data[i], HEX);
//Serial.print(" ");
}
//Serial.print(" CRC=");
//Serial.print(OneWire::crc8(data, 8), HEX);
//Serial.println();
// Convert the data to actual temperature
// because the result is a 16 bit signed integer, it should
// be stored to an "int16_t" type, which is always 16 bits
// even when compiled on a 32 bit processor.
int16_t raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// "count remain" gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
}
}
else {
byte cfg = (data[4] & 0x60);
// at lower res, the low bits are undefined, so let's zero them
if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
//// default is 12 bit resolution, 750 ms conversion time
}
celsius = (float)raw / 16.0;
fahrenheit = celsius * 1.8 + 32.0;
//Serial.print(celsius);
//Serial.print(" Celsius, ");
Serial.print(tempLocName);
Serial.print(" is currently:  ");
Serial.print(fahrenheit);
Serial.println("F");
if (owSensorId == 0) { //Downstairs
ow0 = fahrenheit;
datastreams[0].setFloat(ow0);
if (millis() - lockedAt22a > unlockTime) { // see if we can unlock the timer
locked22a = false;
}
if (locked22a == false && (ow0 < 50.0 || ow0 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow0;
sendEmail(tempLocName, currentTemp);
locked22a = true;
lockedAt22a = millis();
}
}
}
else if (owSensorId == 1) { //Master Bedroom
ow1 = fahrenheit;
datastreams[1].setFloat(ow1);
if (millis() - lockedAt22b > unlockTime) { // see if we can unlock the timer
locked22b = false;
}
if (locked22b == false && (ow1 < 50.0 || ow1 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow1;
sendEmail(tempLocName, currentTemp);
locked22b = true;
lockedAt22b = millis();
}
}
}
else if (owSensorId == 2) { //Front Room
ow2 = fahrenheit;
datastreams[2].setFloat(ow2);
if (millis() - lockedAt22c > unlockTime) { // see if we can unlock the timer
locked22c = false;
}
if (locked22c == false && (ow2 < 50.0 || ow2 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow2;
sendEmail(tempLocName, currentTemp);
locked22c = true;
lockedAt22c = millis();
}
}
}
else if (owSensorId == 3) { //Garage
ow3 = fahrenheit;
datastreams[3].setFloat(ow3);
if (millis() - lockedAt22d > unlockTime) { // see if we can unlock the timer
locked22d = false;
}
if (locked22d == false && (ow3 < 50.0 || ow3 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow3;
sendEmail(tempLocName, currentTemp);
locked22d = true;
lockedAt22d = millis();
}
}
}
else if (owSensorId == 4) { //Back Porch
ow4 = fahrenheit;
datastreams[4].setFloat(ow4);
if (millis() - lockedAt22e > unlockTime) { // see if we can unlock the timer
locked22e = false;
}
if (locked22e == false && (ow4 < 50.0 || ow4 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow4;
sendEmail(tempLocName, currentTemp);
locked22e = true;
lockedAt22e = millis();
}
}
}
else if (owSensorId == 5) { //Zoe's room
ow5 = fahrenheit;
datastreams[5].setFloat(ow5);
if (millis() - lockedAt22f > unlockTime) { // see if we can unlock the timer
locked22f = false;
}
if (locked22f == false && (ow5 < 50.0 || ow5 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow5;
sendEmail(tempLocName, currentTemp);
locked22f = true;
lockedAt22f = millis();
}
}
}
else if (owSensorId == 6) { //Unassigned 1
ow6 = fahrenheit;
//datastreams[6].setFloat(ow6);
if (millis() - lockedAt22g > unlockTime) { // see if we can unlock the timer
locked22g = false;
}
if (locked22g == false && (ow6 < 50.0 || ow6 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow6;
sendEmail(tempLocName, currentTemp);
locked22g = true;
lockedAt22g = millis();
}
}
}
else if (owSensorId == 7) { //Black Refrigerator
ow7 = fahrenheit;
datastreams[7].setFloat(ow7);
if (millis() - lockedAt22h > unlockTime) { // see if we can unlock the timer
locked22h = false;
}
if (locked22h == false && (ow7 < 32.0 || ow7 > 50.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow7;
sendEmail(tempLocName, currentTemp);
locked22h = true;
lockedAt22h = millis();
}
}
}
else if (owSensorId == 8) { //Zoe's room
ow8 = fahrenheit;
if (millis() - lockedAt22i > unlockTime) { // see if we can unlock the timer
locked22i = false;
}
if (locked22i == false && (ow8 < 50.0 || ow8 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow8;
sendEmail(tempLocName, currentTemp);
locked22i = true;
lockedAt22i = millis();
}
}
}
}
}
//******************************END DS18B20 FUNCTION
//************************************BEGIN XIVELY UPLOAD
void xivelyPost() {
XivelyClient xivelyclient(Xively);
int ret = xivelyclient.put(feed, xivelyKey);  // Send to Xively
if(ret == 200){                           // If success, blink LED
digitalWrite(26, HIGH);
delay(100);
digitalWrite(26, LOW);
}
}
//************************************END XIVELY UPLOAD
//**************************BEGIN NTP FUNCTIONS
void digitalClockDisplay(){
// digital clock display of the time
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(month());
Serial.print("/");
Serial.print(day());
Serial.print("/");
Serial.print(year());
Serial.println();
if (((month() == 11) && (day() >= 3) || (month() == 12) || (month() > 0) && (month() < 3) || (month() == 3) && (day() >= 10))) {
timeZone = timeZone++;
}
}
void printDigits(int digits){
// utility for digital clock display: prints preceding colon and leading 0
Serial.print(":");
if(digits < 10)
Serial.print('0');
Serial.print(digits);
}
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime() {
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println("Transmit NTP Request");
sendNTPpacket(timeServer);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
Serial.println("No NTP Response");
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address) {
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011;   // LI, Version, Mode
packetBuffer[1] = 0;     // Stratum, or type of clock
packetBuffer[2] = 6;     // Polling Interval
packetBuffer[3] = 0xEC;  // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12]  = 49;
packetBuffer[13]  = 0x4E;
packetBuffer[14]  = 49;
packetBuffer[15]  = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
} //***************************END NTP FUNCTIONS
//****************************BEGIN SMTP FUNCTIONS
byte sendEmail(String tempLocName, int currentTemp) {
byte thisByte = 0;
byte respCode;
if(email.connect(smtpServer,25)) {
Serial.println(F("Connected to smtp.XXXXXXXX.net"));
}
else {
Serial.println(F("Connection to smtp.XXXXXXXX.net failed"));
return 0;
}
if(!eRcv()) return 0;
Serial.println(F("Sending helo"));
// change to your public ip
email.println(F("helo live.XXXXXXXX.com"));
if(!eRcv()) return 0;
Serial.println(F("Sending from YYYYYYYYY@XXXXXXXX.net"));
// change to your email address (sender)
email.println(F("MAIL From: YYYYYYYYY@XXXXXXXX.net"));
if(!eRcv()) return 0;
// change to recipient address
Serial.println(F("Sending to: user@XXXXXXXX.com"));
email.println(F("RCPT To: user@XXXXXXXX.com"));
if(!eRcv()) return 0;
Serial.println(F("Sending DATA"));
email.println(F("DATA"));
if(!eRcv()) return 0;
Serial.println(F("Sending email"));
// change to recipient address
email.println(F("To: J. User <user@XXXXXXXX.com>"));
// change to your address
email.println(F("From: The WatchDog <YYYYYYYYY@XXXXXXXX.net>"));
email.println("Subject: " + tempLocName + " is: " + currentTemp + "°F\r\n");
email.println("The " + tempLocName + " is currently " + currentTemp + "°F.\r\n");
//email.println("Click for graphs: https://www.thingspeak.com/channels/XXXXXXX#privateview");
email.println(F("."));
if(!eRcv()) return 0;
Serial.println(F("Sending QUIT"));
email.println(F("QUIT"));
if(!eRcv()) return 0;
email.stop();
Serial.println(F("Disconnected from smtp.XXXXXXXX.net"));
return 1;
}
byte eRcv() {
byte respCode;
byte thisByte;
int loopCount = 0;
while(!email.available()) {
delay(1);
loopCount++;
// if nothing received for 10 seconds, timeout
if(loopCount > 10000) {
email.stop();
Serial.println(F("\r\nTimeout"));
return 0;
}
}
respCode = email.peek();
while(email.available()) {
thisByte = email.read();
Serial.write(thisByte);
}
if(respCode >= '4') {
efail();
return 0;
}
return 1;
}
void efail() {
byte thisByte = 0;
int loopCount = 0;
email.println(F("QUIT"));
while(!email.available()) {
delay(1);
loopCount++;
// if nothing received for 10 seconds, timeout
if(loopCount > 10000) {
email.stop();
Serial.println(F("\r\nTimeout"));
return;
}
}
while(email.available()) {
thisByte = email.read();
Serial.write(thisByte);
}
email.stop();
Serial.println(F("Disconnected from smtp.XXXXXXXX.net"));
}  //****************************END SMTP FUNCTIONS
int freeRam () { //check free RAM
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

So now what? Well, you’re polling the sensors and that’s great. The serial monitor is spitting out temperature readings faster than you can read them and you can’t just sit there watching it all day, can you? Actually you can. I did. Don’t do it yourself. You need to pump the collected data to an Internet of Things aggregator like Open.Sen.Se, KSDuino, ThingSpeak or Xively (formerly Cosm, formerly Pachube. Long story there.) If you’re handy with PHP and SQL you can do a simple GET and pass vars in the URL to your PHP server and have a receiving script do the SQL inserts. That’s how I started out myself. I’ll explain each one of these in another article “How I Visualize The Sensors”. For now just know that to do any of these you need an Ethernet shield. The one I used originally came from eBay and was based on the WizNet5100. It was $10, had a micro SD card slot and worked like a champ. It died a week later, and admittedly, I had a hand in its death. Because I have little patience I picked up a SeeedStudio Ethernet shield at Radio Shack. It’s also based on the same chipset but for $20, no SD card slot and I had to solder the header pins on myself. For the record, I friggin hate Seeed shields because the header pins are offset. Totally stupid design. But they didn’t ask for my input, and it works, so…. anyway, you’ll need this if you want to post your sensor data to an aggregator. I’ll give my opinions on each of these in the other article I mentioned. As a teaser, here’s the 30,000′ observation of those listed in no particular order, and consider I use 3 of these together:

IoT AggregatorFirst Opinion
ThingSpeak.comNice graphing, easy interface, well documented API, slow data posts, Special ED simple to set up but sometimes is unavailable so you miss some data posts, has Google gauge plugin helper that actually does AJAX refreshing (more on this in the other article).
KSDuino.orgApparently based in Russia, poor documentation, API almost non-existent, one-man show it seems. Support forum question I posted went unanswered, it's been a month now. There are better ways to waste your time.
Xively.comMore complicated to set up but seems rather mature, sketch code is easy to follow after you beat your head into the wall, nice graphing but more difficult to embed elsewhere, reliable posting IF you don't post too frequently.
Open.Sen.SeBy invitation only, so you request an account and a few days later you get a link to sign up. Odd. Decent graphing. Somewhat slow interface. Best feature is extensibility. Template to pull from a Xively datastream. Template to post to PHP and SQL. My current favorite though not without faults
Custom PHP and MySQLYour data, you control it. Graphing can be a pain since you are on the hook for the whole thing. Harder to make graphs that self-refresh. No worries about a "free" service going fee-based, you already pay for the hosting or even better, you can host it internally. Long-term this is probably the best way to go but it takes the most time and can be a pain to work out if you want fancy graphing. rGraph and Google charts are your best bet.

8 Comments

  1. Hi,

    I am trying to use your arduino code to monitor 3 one-wire sensors I have in my aquaponics setup, but I am having difficulty understanding how you recognize the sensors in the code. I only get the error message “Can not determine bit 8 from the ROM I must have found a new sensor on the bus” even though I put the addresses of my sensors in the section of the code where you define them. I am assuming since the full address is commented out that you are only looking at the last bit, but putting any combination of full address / only last bit has the same results.

    Can you explain this simply, or offer a link so I can learn how to understand the method?

    edit: I am using a single 4.7K resistor with 3 sensors, although I did also try eliminating all but one sensor to see if it changed anything. Is the code sensitive to the pull-up resistor value?

    thank you,

    Jake

    • Hi Jake,
      Yes the pullup resistor was a sore spot for me. I ended up using a 5K potentiometer to allow me to dial in what worked for a given count of sensors. Right now it’s at 2.4K, so that gives you some indication how wildly it fluctuates. If you’re not getting a read at all double-check your PIN assignment. My code used 22. Another thing you could try is to load simple DS18B20 scanner code by itself. You can get that here. Let me know if this code works. It was what I used to debug the 1-wire stuff.

      • Well, that was easy. After reading your comment about checking it with a simple scanner code, which I had done previously with an UNO board, I discovered that I saw nothing at all.

        That was when I realized pin 22 was not where my bus was plugged into.

        Great code and thanks again.

  2. Elson Avallone

    Hi, I’m from Brazil. Am building a system with sensors DS18B20 37, but my Arduino output signal sequence is not present in the assembly.
    Could you please help me? I used only one resistor 4K7 Ohms, but it did not work.
    Thank you very much.

    • Are you saying you have 37 DS18B20 sensors? On one bus? That’s a lot. You can try lowering the resistor but 37 might be too much for one pin/bus. I currently have 7 on one bus, in parasitic mode, but I used a 5K pot because when I added more I needed to reduce the resistance. With 7 the pot is set a 2K4. I’m not sure what you mean by “my Arduino output signal sequence is not present in the assembly” but I hope this answered your question.

  3. Hi, I need some help.

    1. I have an Arduino sketch that sends a temperature value from the analog input 0 to ThingSpeak and this program works well.
    2. Then, I modified the sketch to read temperature from a DS18B20 sensor connected to the digital input 10. To confirm the correct measurement the program send the temperature value to serial interface an this works fine but….no connection is made to ThingSpeak.

    Here the DS18B20 sketck:

    #include
    #include
    #include
    #include

    // Local Network Settings
    byte mac[] = { 0xD4, 0x28, 0xB2, 0xFF, 0xA0, 0xA1 }; // Must be unique on local network

    // ThingSpeak Settings
    char thingSpeakAddress[] = “api.thingspeak.com”;
    String writeAPIKey = “Y8OBR72J8BNHD7MV”;
    const int updateThingSpeakInterval = 16 * 1000; // Time interval in milliseconds to update ThingSpeak (number of seconds * 1000 = interval)

    // Variable Setup
    long lastConnectionTime = 0;
    boolean lastConnected = false;
    int failedCounter = 0;

    // Initialize Arduino Ethernet Client
    EthernetClient client;

    // Data wire is plugged into pin 10 on the Arduino
    #define ONE_WIRE_BUS 10
    // Setup a oneWire instance to communicate with any OneWire devices
    OneWire oneWire(ONE_WIRE_BUS);
    // Pass our oneWire reference to Dallas Temperature.
    DallasTemperature sensors(&oneWire);
    DeviceAddress Temp = { 0x28, 0x40, 0xD2, 0x81, 0x04, 0x00, 0x00, 0x7E };

    void setup()
    {
    sensors.requestTemperatures();

    // Start Serial for debugging on the Serial Monitor
    Serial.begin(9600);

    sensors.begin();

    // Start Ethernet on Arduino
    startEthernet();
    }

    void loop()
    {
    // Read value from Analog Input Pin 0
    //String value = String(analogRead(A0), DEC);

    int T = sensors.getTempC(Temp);
    String value = String(T, DEC);
    Serial.print (T);
    Serial.print (value);

    // Print Update Response to Serial Monitor
    if (client.available())
    {
    char c = client.read();
    Serial.print(c);
    }

    // Disconnect from ThingSpeak
    if (!client.connected() && lastConnected)
    {
    Serial.println(“…disconnected”);
    Serial.println();
    client.stop();
    }

    // Update ThingSpeak
    if(!client.connected() && (millis() – lastConnectionTime > updateThingSpeakInterval))
    {
    updateThingSpeak(“field1=”+value);
    }

    // Check if Arduino Ethernet needs to be restarted
    if (failedCounter > 3 ) {startEthernet();
    }
    lastConnected = client.connected();
    }

    void updateThingSpeak(String tsData)
    {
    if (client.connect(thingSpeakAddress, 80))
    {
    client.print(“POST /update HTTP/1.1n”);
    client.print(“Host: api.thingspeak.comn”);
    client.print(“Connection: closen”);
    client.print(“X-THINGSPEAKAPIKEY: “+writeAPIKey+”n”);
    client.print(“Content-Type: application/x-www-form-urlencodedn”);
    client.print(“Content-Length: “);
    client.print(tsData.length());
    client.print(“nn”);

    client.print(tsData);

    lastConnectionTime = millis();

    if (client.connected())
    {
    Serial.println(“Connecting to ThingSpeak…”);
    Serial.println();

    failedCounter = 0;
    }
    else
    {
    failedCounter++;

    Serial.println(“Connection to ThingSpeak failed (“+String(failedCounter, DEC)+”)”);
    Serial.println();
    }

    }
    else
    {
    failedCounter++;

    Serial.println(“Connection to ThingSpeak Failed (“+String(failedCounter, DEC)+”)”);
    Serial.println();

    lastConnectionTime = millis();
    }
    }

    void startEthernet()
    {
    client.stop();

    Serial.println(“Connecting Arduino to network…”);
    Serial.println();

    delay(1000);

    // Connect to network amd obtain an IP address using DHCP
    if (Ethernet.begin(mac) == 0)
    {
    Serial.println(“DHCP Failed, reset Arduino to try again”);
    Serial.println();
    }
    else
    {
    Serial.println(“Arduino connected to network using DHCP”);
    Serial.println();
    }

    delay(1000);
    }

    Best regards.

    • You’ve got some different code in there to update ThingSpeak. The way I do it is up above. Expand the “My Code” section and look around line 330 for my syntax. Basically the way I update is by passing all of the fields (field1, field2, etc…) as HTTP URL vars. I don’t know if your code will allow multiple vars to be passed as an array fieldX==>value.

      Mine would look like this:
      HTTP GET /update?key=apiKey&field1=owA&field2=owB&field3=owC&field4=owD&field5=owE&field6=owF&field7=kWh

  4. There is something that can be done to help control the temperature of individual rooms. Amazon has these:
    Lux WIN100 Heating & Cooling Programmable Outlet Thermostat
    https://www.amazon.com/WIN100-Heating-Cooling-Programmable-Thermostat/dp/B000E7NYY8/ref=sr_1_1?ie=UTF8&qid=1475075800&sr=8-1&keywords=lux+temperature+sensor

    It’s basically just a thermostat that plugs right into the wall. You can set the desired temperature for that room. It’s perfect for that room that’s always too hot or cold. If too hot, you can use a window A/C unit. If too cold, the radiant heaters work great (use one that won’t shut off electronically when unplugged, you want the LUX thermostat to control the on/off cycles). I’ve used one for over a year, and have been pretty happy with it.

Leave a Comment

Your email address will not be published. Required fields are marked *