Nathan JeffersCarolina Casin-Silva
Published © GPL3+

It's Tea Time!

A simple circuit that texts you when your tea/coffee/other hot drink is at proper drinking temperature!

BeginnerFull instructions provided1 hour770
It's Tea Time!

Things used in this project

Hardware components

Photon
Particle Photon
×2
Breadboard (generic)
Breadboard (generic)
×2
Resistor 4.75k ohm
Resistor 4.75k ohm
×1
Modulo Temperature Probe
Modulo Temperature Probe
We are using a DS18B20 Three wire sensor. Acceptable three wire sensors include DS1820/DS18S20, DS18B20, DS1822, and DS2438.
×1

Software apps and online services

Maker service
IFTTT Maker service
ThingSpeak API
ThingSpeak API

Story

Read more

Schematics

Breadboard Diagram

A bread board schematic of the wiring diagram

Wiring Diagram

This shows a more specific view of which wires are ground, vdd, and the input wire.

IFTT Text message setup

These are photos from IFTT that shows how to send a text message once the event has been published

ThingSpeak Setup

Here is a view of how to set up ThingSpeak to read in data from your photon

ThingSpeak Graph

This is the graph of temperature data that ThingSpeak reads from the first photon. Note the random drop in temperature, this anomaly is accounted for in the code to not trigger a "false positive" for the text message.

Code

Temperature Read from Sensor

C/C++
This is first half of the code that reads the temperature data from your sensor, converts it to either Celsius or Fahrenheit, and then publishes the data to thingSpeak and the particle log. This code will be uploaded to the first particle.
/*
Use this sketch to read the temperature from 1-Wire devices
you have attached to your Particle device (core, p0, p1, photon, electron)

Temperature is read from: DS18S20, DS18B20, DS1822, DS2438

Expanding on the enumeration process in the address scanner, this example
reads the temperature and outputs it from known device types as it scans.

I/O setup:
These made it easy to just 'plug in' my 18B20

D3 - 1-wire ground, or just use regular pin and comment out below.
D4 - 1-wire signal, 2K-10K resistor to D5 (3v3)
D5 - 1-wire power, ditto ground comment.

A pull-up resistor is required on the signal line. The spec calls for a 4.7K.
I have used 1K-10K depending on the bus configuration and what I had out on the
bench. If you are powering the device, they all work. If you are using parisidic
power it gets more picky about the value.

*/

// Only include One of the following depending on your environment!
#include "OneWire/OneWire.h"  // Use this include for the Web IDE. You may have to go to the actual library and include it instead of copying and pasting. 
// #include "OneWire.h" // Use this include for Particle Dev where everything is in one directory.

OneWire ds = OneWire(D4);  // 1-wire signal on pin D4

unsigned long lastUpdate = 0;

void setup() {
  Serial.begin(9600);
  // Set up 'power' pins, comment out if not used!
  pinMode(D3, OUTPUT);
  pinMode(D5, OUTPUT);
  digitalWrite(D3, LOW);
  digitalWrite(D5, HIGH);
}

// up to here, it is the same as the address acanner
// we need a few more variables for this example

void loop(void) {
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;

  if ( !ds.search(addr)) {
    //Serial.println("No more addresses.");
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }

  // The order is changed a bit in this example
  // first the returned address is printed

  Serial.print("ROM =");
  for( i = 0; i < 8; i++) {
    Serial.write(' ');
    Serial.print(addr[i], HEX);
  }

  // second the CRC is checked, on fail,
  // print error and just return to try again

  if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.println("CRC is not valid!");
      return;
  }
  Serial.println();

  // we have a good address at this point
  // what kind of chip do we have?
  // we will set a type_s value for known types or just return

  // the first ROM byte indicates which chip
  switch (addr[0]) {
    case 0x10:
      Serial.println("  Chip = DS1820/DS18S20");
      type_s = 1;
      break;
    case 0x28:
      Serial.println("  Chip = DS18B20");
      type_s = 0;
      break;
    case 0x22:
      Serial.println("  Chip = DS1822");
      type_s = 0;
      break;
    case 0x26:
      Serial.println("  Chip = DS2438");
      type_s = 2;
      break;
    default:
      Serial.println("Unknown device type.");
      return;
  }

  // this device has temp so let's read it

  ds.reset();               // first clear the 1-wire bus
  ds.select(addr);          // now select the device we just found
  // ds.write(0x44, 1);     // tell it to start a conversion, with parasite power on at the end
  ds.write(0x44, 0);        // or start conversion in powered mode (bus finishes low)

  // just wait a second while the conversion takes place
  // different chips have different conversion times, check the specs, 1 sec is worse case + 250ms
  // you could also communicate with other devices if you like but you would need
  // to already know their address to select them.

  delay(1000);     // maybe 750ms is enough, maybe not, wait 1 sec for conversion
  
  // we might do a ds.depower() (parasite) here, but the reset will take care of it.

  // first make sure current values are in the scratch pad

  present = ds.reset();
  ds.select(addr);
  ds.write(0xB8,0);         // Recall Memory 0
  ds.write(0x00,0);         // Recall Memory 0

  // now read the scratch pad

  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE,0);         // Read Scratchpad
  if (type_s == 2) {
    ds.write(0x00,0);       // The DS2438 needs a page# to read
  }

  // transfer and print the values

  //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 == 2) raw = (data[2] << 8) | data[1];
  byte cfg = (data[4] & 0x60);

  switch (type_s) {
    case 1:
      raw = raw << 3; // 9 bit resolution default
      if (data[7] == 0x10) {
        // "count remain" gives full 12 bit resolution
        raw = (raw & 0xFFF0) + 12 - data[6];
      }
      celsius = (float)raw * 0.0625;
      break;
    case 0:
      // 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
      if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
      if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
      // default is 12 bit resolution, 750 ms conversion time
      celsius = (float)raw * 0.0625;
      break;

    case 2:
      data[1] = (data[1] >> 3) & 0x1f;
      if (data[2] > 127) {
        celsius = (float)data[2] - ((float)data[1] * .03125);
      }else{
        celsius = (float)data[2] + ((float)data[1] * .03125);
      }
  }

  fahrenheit = celsius * 1.8 + 32.0;
  Serial.print("  Temperature = ");
  Serial.print(celsius);
  Serial.print(" Celsius, ");
  Serial.print(fahrenheit);
  Serial.println(" Fahrenheit");
  //This portion of the code publishes the temperature onto thingSpeak. The variable name is temp. 
  //The delay causes a new publication every 5 seconds
  //The temperature is also in celsius, so simply replace the variable if you prefer celsius over farenheit.
  char tmpStr[64];
  sprintf(tmpStr, "%f", fahrenheit);  //assuming fahrenheit is a float variable
  Spark.publish("temp", tmpStr,PRIVATE);
  delay(5000);
}

Receiving Temperature Data

C/C++
This is the second half of the code that reads the output temperature, and once the drinkable temperature has been reached, will send you a text message alerting you that your tea is ready for drinking! This code should be uploaded to your second particle.
// This #include statement was automatically added by the Particle IDE.
#include "ThingSpeak/ThingSpeak.h"

// On Particle Core, Photon, and Electron the results are published to the Particle dashboard using events.
// Go to http://dashboard.particle.io, click on the logs tab, and you'll see the events coming in. 
TCPClient client;

unsigned long thingSpeakChannel = "Channel Number Here"; //Use your private channel number here to direct the photon
unsigned int temperatureFieldNumber = 1; //this is the field your data is published to. 
int trip = 0;
int out = 0;

const char * myReadAPIKey = "Your Read API Key"; //This API Key is found on thingSpeak. this is the read API key

void setup() 
{
  ThingSpeak.begin(client);
  
  while(trip == 0)
  {
       // Read the latest value from field 1 of your ThingSpeak Channel
  float temp = ThingSpeak.readFloatField(thingSpeakChannel, temperatureFieldNumber, myReadAPIKey);

  Particle.publish("thingspeak-readTemp", "Latest Temperature is: " + String(temp) + " F",60,PRIVATE); 
  delay(10000);
  //The loop below controls how many times the temperature must read below threshhold before it sends you a text message. 
  //This ensure that no random spikes or dips set off a false alarm 
  //Change "140" to whatever your preffered drinking temperature is.
  //140 degrees F or 60 degrees C is often considered the ideal drinking temperature for tea
  if(temp <= 140)
  {
      out += 1;
      if(out >= 4)
      {
          
          Particle.publish("tea_time"); // tea_time is the event that is read by IFTT. You can change this event name as you like to be read on IFTT
          break;
      }
  }
  }
  
}







void loop() {
 
}

Credits

Nathan Jeffers

Nathan Jeffers

1 project • 0 followers
Carolina Casin-Silva

Carolina Casin-Silva

1 project • 0 followers
Thanks to OneWire and ThingSpeak Libraries for Particle.

Comments