Temperature monitoring with a thermistor, Azure and graphing

Hi All

For my “hello world” app I decided to monitor the temperature using a thermistor, store the results in Azure and then graph the stored data. This ended up being more of a challenge than I expected, so I thought I’d share my leanings here.

For basic thermistor information you can check out this article on Wikipedia. I used the “B equation” for my temperature calculation as I didn’t have the constants required for the standard Steinhart–Hart equation.

When it comes to the electronics and Spark code I started by following and porting this guide from Adafruit. I didn’t really like their explanation of the electronics and maths though (and I couldn’t get it working - although I think that’s because my thermistor was broken).

It’s been a long time since I’ve used either maths or electroncis, so I wanted a proper explanation of everything. A friend pointed out this paper from a student at Portland State University. The first 4 pages give a really clear and easy to follow explanation of the electronics and how to interpret the value read from the analog pin. The rest of the paper uses the Steinhart–Hart equation so I didn’t follow that part.

Once I had the electronics and Spark code working correctly (I couldn’t get a 15K thermistor working, so switched to a 4K7) I created a website and database on Azure. The site would serve 2 purposes: recieve and store data sent by the Spark and provide a graph to view the data.

To receive the data I just created a basic webservice. My Spark would do a HTTP POST to this webservice using the HTTP library provided by Spark. The website stores all data summarized by hour (the Spark POSTs every minute) including: minimum, maximum, average, date, hour of day, day of week, week of year, month etc.

The 2nd part of the site uses the Highcharts JavaScript library (free for non-commercial uses) to render the past 7 days of data in a graph. The end result looks like this (I’m in Cape Town, so it’s Spring at the moment):

Note that this is measured indoors, not outdoors! You’ll notice the drop in temperature from around 12:00 on the 17th - this is when a cold front moved in (you’ll see things are improving today :wink: ).

My next priority is to add outdoor temperature and barometric pressure readings. Or home power monitoring. All depends on which sensor arrives first :smiley:

I would share the live Azure link, but it’s running on a trial account and I don’t know how long my credits will last.

Any questions, comments, suggestions always welcome.

Cheers
Greg

7 Likes

Nice! Thanks for sharing! I hope you decide to share your code :smile:

Thanks,
David

1 Like

@greg great project.

I finally after much messing about got my own thermistor project off the ground. I would prefer not to use steinhard but instead use b-value. I have the coefficient for my Type 2 10k thermistor can you share your formula?

Also have you considered using ubidots for dashboard reporting? Its pretty slick and was referred to me by another user here.

All

Nice choice of tech for the web stack.
I used the same for Atomiot.com.

Check it out. Looks like it serves your purposes.

Hi @Herner

Sorry for the late reply, I hope it’s not too late!

More than happy to share my code. I’ve removed the code that posts to Azure to make it easier to read, but I’m happy to add it back if you want.

#include <math.h>

// which analog pin to connect
#define THERMISTORPIN A0
// resistance at 25 degrees C
#define THERMISTORNOMINAL 4700
// temp. for nominal resistance (almost always 25 C)
#define TEMPERATURENOMINAL 25
// how many samples to take and average, more takes longer
// but is more 'smooth'
#define NUMSAMPLES 5
// The beta coefficient of the thermistor (usually 3000-4000)
#define BCOEFFICIENT 3977
// the value of the 'other' resistor
#define SERIESRESISTOR 4670

// Create a variable that will store the temperature value
double temperature = 0.0;

int samples;

void setup(void) 
{
    // Register a Spark variable here
    Spark.variable("temperature", &temperature, DOUBLE);
    
    Serial.begin(9600);
    pinMode(A0, INPUT);
}

void loop(void) 
{
    uint8_t i;
    float average;

    // take N samples in a row, with a slight delay
    for (i=0; i< NUMSAMPLES; i++) 
    {
        samples += analogRead(THERMISTORPIN);
        delay(10);
    }
    
    average = samples / NUMSAMPLES;
    Serial.print("Average analog reading ");
    Serial.println(average);
    
    // convert the value to resistance
    float resistance;
    resistance = SERIESRESISTOR * (4095.0 / average - 1.0);
    Serial.print("Thermistor resistance ");
    Serial.println(resistance);
    
    float steinhart;
    steinhart = resistance / THERMISTORNOMINAL; // (R/Ro)
    steinhart = log(steinhart); // ln(R/Ro)
    steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro)
    steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)
    steinhart = 1.0 / steinhart; // Invert
    steinhart -= 273.15; // convert to C
    Serial.print("Temperature ");
    Serial.print(steinhart);
    Serial.println(" *C");
    
    delay(60 * 1000);
}

As I mentioned I can’t take credit for this, most of it is straight from the Adafruit tutorial. The only part that is different is the calculation of the value of the resistor.

Shout if you have any other questions. Oh, thanks for the Ubidots headsup, looks interesting!

Cheers
Greg

2 Likes

Ubidots is fantastic! Definitely try it out. I built some slick dashboards with alerts and replaced Xively and Zapier in one swoop.

Thanks for sharing your code it is almost identical to what I tried I will tinker with it and see if I can find out why my code was going bananas. Seriously appreciate you sharing.

I would be interested in seeing your Azure code simply because I have not tried Azure :slight_smile:

Glad to help :smile:

The biggest problem I had was making sure I was getting the right reading from the analog pin and calculating the correct resistance. Once those 2 were correct everything went smoothly.

Make sure you look at the debug text using the spark cli serial monitor commands (http://docs.spark.io/cli/) they were a great help to me. That’s how I found out my original thermistor was broken.

  • spark serial list
  • spark serial monitor

The Azure stuff was pretty easy. I created a new ASP.Net WebForms project in Visual Studio and then added two web services and a regular web page. One web service receives the posted data from the Spark and saves it to a database (also Azure) while the other one loads data from the database and formats it to be suitable for the graph. The web page itself just presents the html to load the graph.

On the Spark side all that is needed a simple http “post” to the web service. For now I’m just posting raw data to the server, there’s no need for json formatted data yet.

Here’s the full Spark code, including the http bits.

// This #include statement was automatically added by the Spark IDE.
#include "HttpClient/HttpClient.h"

#include <math.h>

// which analog pin to connect
#define THERMISTORPIN A0
// resistance at 25 degrees C
#define THERMISTORNOMINAL 4700
// temp. for nominal resistance (almost always 25 C)
#define TEMPERATURENOMINAL 25
// how many samples to take and average, more takes longer
// but is more 'smooth'
#define NUMSAMPLES 5
// The beta coefficient of the thermistor (usually 3000-4000)
#define BCOEFFICIENT 3977
// the value of the 'other' resistor
#define SERIESRESISTOR 4670

// Create a variable that will store the temperature value
double temperature = 0.0;

int samples[NUMSAMPLES];

HttpClient http;
// Headers currently need to be set at init, useful for API keys etc.
http_header_t headers[] = {
    //  { "Content-Type", "application/json" },
    //  { "Accept" , "application/json" },
    { "Accept" , "*/*"},
    { "Content-Type" , "application/json"},
    { NULL, NULL } // NOTE: Always terminate headers will NULL
};

http_request_t request;
http_response_t response;

void setup(void) 
{
    // Register a Spark variable here
    Spark.variable("temperature", &temperature, DOUBLE);
    
    // Setup http posting
    request.hostname = "mywebsite.com"; // Put your site name here
    request.port = 80;
    request.path = "/some/path/to/my/webservice"; // Put the path to the webservice here
    

    Serial.begin(9600);
    pinMode(A0, INPUT);
}

void loop(void) 
{
    uint8_t i;
    float average;

    // take N samples in a row, with a slight delay
    for (i=0; i< NUMSAMPLES; i++) 
    {
        samples[i] = analogRead(THERMISTORPIN);
        delay(10);
    }
    
    // average all the samples out
    average = 0;
    for (i=0; i< NUMSAMPLES; i++) 
    {
        average += samples[i];
    }
    
    average /= NUMSAMPLES;
    Serial.print("Average analog reading ");
    Serial.println(average);
    
    // convert the value to resistance
    float resistance;
    resistance = SERIESRESISTOR * (4095.0 / average - 1.0);
    Serial.print("Thermistor resistance ");
    Serial.println(resistance);
    
    float steinhart;
    steinhart = resistance / THERMISTORNOMINAL; // (R/Ro)
    steinhart = log(steinhart); // ln(R/Ro)
    steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro)
    steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)
    steinhart = 1.0 / steinhart; // Invert
    steinhart -= 273.15; // convert to C
    Serial.print("Temperature ");
    Serial.print(steinhart);
    Serial.println(" *C");
    
    // Post to azure
    String postBody = String(steinhart);
    postBody += ",";
    postBody += Time.now();
    request.body = postBody;
    Serial.print("Posting...");
    http.post(request, response, headers);
    Serial.print("Response status: ");
    Serial.println(response.status);
    Serial.print("HTTP Response Body: ");
    Serial.println(response.body);
    Serial.print("... done posting");

    delay(60 * 1000);
}

I’ll get round to posting the full code for the website, but I need to clean it up first - the default project created by Visual Studio includes a lot of dependencies and extra code that aren’t need.

1 Like

I am experimenting with your code now. We were both on the same track as this is very similar to how I did my arduino sensors (which did not work on spark first go).

I used to use my Arduino IDE for seeing error logs and finding out what my core is doing but that required a) being plugged in and B) it stopped working after a few tries. I did try the Spark CLI but failed miserably… may have to try again :slight_smile:

@greg just some food for thought I can NOT make a 10k thermistor work. Our code was practically identical and even if I use your code 10k = fail. Its annoying because my code was working perfectly on my arduino platform.

Spark has a library for thermistors but I intend to have 6 thermistors and I am not entirely sure how well it will scale what they have.

Who do you think we should bug at spark for help? :smile:

@greg this code APPEARS to be working. I just need to verify the readings. 10k Thermistor, 10k resistor. Your code for whatever reason would not, nor would my early code for arduino.

int total = 0;
    for(int i=0; i<100; i++) 
        {
            total += analogRead(A6);
            delay(1);
        }
        
   temp_raw = total/100;
   temp_k = log(((40960000/temp_raw) - resistor));
   temp_k = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * temp_k * temp_k ))* temp_k); 
   temp_c = temp_k - 273.15;
   temp_f = (temp_c * 9.0)/ 5.0 + 32.0;
   
   request.path = "/api/v1.6/variables/"VARIABLE_ID"/values";
   request.body = "{\"value\":" + String(temp_c) + "}";

    http.post(request, response, headers);
    delay(5000); // consider timeElaped > 5000 instead.

That’s pretty awesome @Herner, glad you got it working! I was actually just about to post a picture of my electronics and some more thoughts on solving your problem.

Looks like you’re using the Steinhart–Hart equation whereas my code used the B or β parameter variation (wikipedia). I couldn’t find a fact sheet for my thermistor so I couldn’t get the 3 constants needed for Steinhart-Hart equation, thus me using the B variation.

At least it looks like there aren’t any problems with your electronics/thermistor.

I’d be interested to find out why your original code didn’t work. Would you mind posting it?

@greg I am about to give your code / my original code a second stab now :slight_smile: I refuse to be defeated and I know the method you and I were using is more accurate. My sensor is out by about 3degrees with this method.

Will post back shortly with my code.

Cool, look forward to hearing your results. Good luck!

Here is my code. This code for me does not report any data. I have not been able to get CLI to work on my computer to ‘debug’. Arduino IDE worked very briefly for serial reading data but stopped for some odd reason. Thank you for checking out the code ( should loook REALLY familiar ) lol.

// This #include statement was automatically added by the Spark IDE.
#include "HttpClient/HttpClient.h"

#include <math.h>

// which analog pin to connect
#define THERMISTORPIN A6
// resistance at 25 degrees C
#define THERMISTORNOMINAL 10000
// temp. for nominal resistance (almost always 25 C)
#define TEMPERATURENOMINAL 25
// how many samples to take and average, more takes longer
// but is more 'smooth'
#define NUMSAMPLES 5
// The beta coefficient of the thermistor (usually 3000-4000)
#define BCOEFFICIENT 3977
// the value of the 'other' resistor
#define SERIESRESISTOR 10000

// Create a variable that will store the temperature value
double temperature = 0.0;

int samples[NUMSAMPLES];

HttpClient http;
#define VARIABLE_ID ""
#define TOKEN ""

http_header_t headers[] = 
{
      { "Content-Type", "application/json" },
      { "X-Auth-Token" , TOKEN },
      { NULL, NULL } // NOTE: Always terminate headers with NULL
};

http_request_t request;
http_response_t response;

void setup(void) 
{
    // Register a Spark variable here
    Spark.variable("temperature", &temperature, DOUBLE);
    Serial.begin(9600);
    pinMode(A6, INPUT);
}

void loop(void) 
{
    uint8_t i;
    float average;

    for (i=0; i< NUMSAMPLES; i++) 
    {
        samples[i] = analogRead(THERMISTORPIN);
        delay(10);
    }

    average = 0;
    for (i=0; i< NUMSAMPLES; i++) 
    {
        average += samples[i];
    }

    average /= NUMSAMPLES;

    // convert the value to resistance
    float resistance;
    resistance = SERIESRESISTOR * (4095.0 / average - 1.0);
    
    float steinhart;
    steinhart = resistance / THERMISTORNOMINAL; // (R/Ro)
    steinhart = log(steinhart); // ln(R/Ro)
    steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro)
    steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)
    steinhart = 1.0 / steinhart; // Invert
    steinhart -= 273.15; // convert to C
    

    request.path = "/api/v1.6/variables/"VARIABLE_ID"/values";
    request.body = "{\"value\":" + String(steinhart) + "}";

    http.post(request, response, headers);
    delay(5000); // consider timeElaped > 5000 instead. 


}

lol, yeah, looks pretty familier :smile:

I can’t see anything wrong there. The only thing I can think of is the value of the BCOEEFICIENT. Maybe double check that 3977 is correct for your thermistor also?

Oh, and just to state the obvious - I’m measuring in Celsius, not Fahrenheit.

The b value being wrong would definitely throw my final number off but would not result in no value. My issue seems to be no value. at all. I did the classic ‘backing out’ the formula to where it breaks and it seems to break almost at the start.

Wierd part was using the library from Spark worked just fine and my own code which is a modification of the spark code worked fine to. Glad I am not going crazy :smile:

Just tested my code with a thermocouple and my multimeter and in the lower temp ranges I am within .1 of a degree and in the higher range temps within 2 degrees it seems. So I am probably going to keep the formula I used but I definitely prefer my old method and the one you are using.

Shame it wont work for me.

I’ve got one last resort for you to try if you’re interested (assuming you’re running Windows!).

I’ve written a quick WinForms application that will monitor the serial output of a Spark Device. So no more fiddling with the CLI. It’ll automatically find the right port and display whatever data the Spark sends.

All code and binaries are available at https://sparkbench.codeplex.com/. It’s compiled against .Net 4.5.1, but I can always lower it if needed.

I tested it on my device and everything seems fine, but if you have any problems just let me know.

Mandatory screen shot:

Awesome tool I will be downloading it. Sorry for the delayed response I was out of town for work.

I was using the arduino IDE to see the serial port activity on my spark but it randomly stopped working recently so perhaps your tool will work for me!

Not being able to easily see the serial print on spark is a nightmare for relative newbies like me.

I've removed the code that posts to Azure to make it easier to read, but I'm happy to add it back if you want.

I wonder if you would share your http code posting to your webservice in Azure. That is what I am trying to do now.

Thank you.