Continuous monitoring of wireless network of temperature sensors using MATLAB® and XBee® (Part 3)



In part one and part two of this series of blog posts I went through the process of reading voltages from a network of XBee® modules, building and testing my wireless network of temperature sensors, and the method I used to gather and plot temperature data from the network of sensors in my apartment. However, based on those preliminary results, it seems I needed to calibrate the sensors to get more accurate results. In this post, I will talk about the process I used to calibrate the temperature sensors.

Collecting Calibration Data

In order to calibrate the temperature sensors, I put all of the temperature sensors next to one another (so they should all be reading the same value).

Figure 1: Bundle of sensors for calibration

I started by gathering data for about an hour to get an idea of the accuracy of the uncorrected temperature readings. The data is stored in the file onehourtemplog.txt. Let's take a look at the results:

s = [1 6 7 9 12];
location = {'Sensor 1','Sensor 2', 'Sensor 3', 'Sensor 4', 'Sensor 5'};
[tempF, ts] =  XBeeReadLog('onehourtemplog.txt',60);
ylim([71 77])
title('Uncorrected Temperature Readings')

Figure 2: One hour of uncorrected temperature sensor readings with the sensors together in a bundle

So the graph was not as cluttered, I only plotted the data from a subset of the sensors. As you can see, and as I showed in the last post, there is a range of about 5 degrees among the sensors which should all be showing the same value. It is no good if the temperature in your apartment is 68F, but your thermostat thinks it is 73F.

There are two basic strategies I could have used to calibrate the sensors:

  1. Place the sensors in an environment with a known temperature, such as an ice bath or boiling water. The issue with this approach is that, with the sensors I am using, boiling water would produce an output voltage of 1.5V, which is above the analog input range for the series 2 XBee modules.
  2. Compare the sensor readings with a "trusted" accurate thermometer.

Because I could only measure one temperature using the first option (ice bath), I decided to try a combination of the two options.

I collected two sets of calibration data:

  1. Ice water bath
  2. Room temperature compared to digital thermometer.

Ice Water Bath

  1. Prepare a few trays full of ice ahead of time.
  2. Fill a mixing bowl with ice.
  3. Add water, but not enough to make the ice float.
  4. Place the mixing bowl in the freezer until the water starts to form ice crystals.
  5. Remove the mixing bowl from the freezer and break up any ice chunks.
  6. Place all the temperature sensors next to one another in a bundle.
  7. Wrap the sensors with a piece of plastic wrap to keep them dry.
  8. Place the bundle in the ice water bath.
  9. Stir slowly while you collect data from the XBee sensors. I adjusted the sampling rate to every 20 seconds so I did not need to collect data for a long time.

The data is stored in the file icetemplog.txt.

Figure 3: Bundle of sensors wrapped in plastic wrap to keep them dry

Figure 4: Bundle of sensors in an ice water bath

[obsIceTempF, obsIceTimes] =  XBeeReadLog('icetemplog.txt',10);
title('Uncorrected Ice Bath Temperature Readings')

hold on
ylim([31 50])
timerange = obsIceTimes([1 end]);
plot(timerange,[32 32],'k:','DisplayName','Freezing (32F)')

Figure 5: Uncorrected temperature sensor readings during submersion in an ice water bath

Room Temperature

I used a standard digital desk thermometer to compare against the readings from the XBee network.

Figure 6: Wireless Inside/Outside Thermometer

  1. I placed all the temperature sensors next to one another, along with the digital temperature sensor.
  2. Then I started collecting data from the XBee network using MATLAB.
  3. I recorded the temperature occasionally throughout the day, in an effort to get a range of temperatures. If the temperature is cold or hot outside you could also bring the sensors outside to extend the range of temperatures sampled, just make sure you give both the digital thermometer and the XBee sensors time to settle to the current temperature before making any readings.

The data is stored in the file roomtemplog.txt.

[roomTempF, roomTimes] = XBeeReadLog('roomtemplog.txt',60);
title('Uncorrected Room Temperature Readings')

hold on
plot(refRoomTimes, refRoomTempF, 'kx','DisplayName','Reference Temperature');

Figure 7: Uncorrected room temperature readings compared to digital thermometer reference readings

Calibrating the Sensors

To calibrate the sensors, I started by plotting the XBee sensor readings as a function of the manually recorded temperatures. I stored the manually recorded temperatures in a MAT file (ReferenceTemperatures.mat) for easy access.


I used ismember to match up the time stamps from the room temperature readings and the reference digital thermometer readings:

[keep,ind] = ismember(roomTimes,refRoomTimes);
ind = ind(keep);

I used the keep and ind outputs from ismember to select just the time stamp and temperature readings in which I had both a sensor reading and a reference temperature, and to simultaneously rearrange both so they were aligned with one another.

obsRoomTempF = roomTempF(keep,:); % Keep only sensor readings with matching reference temperatures.
refRoomTempF = refRoomTempF(ind); % Select only reference temperatures with matching sensor readings.
refRoomTimes = refRoomTimes(ind); % Select only reference times with matching sensor readings.

I repeated the same procedure for the ice bath readings.

[keep,ind] = ismember(obsIceTimes,refIceTimes);
ind = ind(keep);
obsIceTempF = obsIceTempF(keep,:);
refIceTempF = refIceTempF(ind);

Then I plotted the reference temperature as a function of the sensor readings. Here is the plot for the Kitchen (Inside 1a):

kit = 5;
plot(obsRoomTempF(:,kit), refRoomTempF, 'r.',obsIceTempF(:,kit), refIceTempF, 'b.')
xlabel('Sensor Readings (\circF)')
ylabel('Reference Temperature (\circF)')
title('Calibration Data from One Sensor (Kitchen)')
legend({'Room Temperature','Ice Bath'},'Location','NorthWest')

Figure 8: Reference temperature plotted against sensor readings for both room temperature and ice bath

As expected, the plot was pretty linear, so I fit a line to the data using polyfit, and plotted that line on the graph of the data.

obsTempF = [obsRoomTempF(:,kit); obsIceTempF(:,kit)];
refTempF = [refRoomTempF; refIceTempF];
p = polyfit(obsTempF, refTempF,1);

fitx = xlim;
fity = polyval(p,fitx);

hold on
plot(fitx, fity,'k')
hold off
legend({'Room Temperature','Ice Bath','Fit Line'},'Location','NorthWest')

Figure 9: Calibration data with best fit line

It looked like a pretty good fit, but I zoomed in to get a better view of the room temperature readings:

plot(obsRoomTempF(:,kit), refRoomTempF, 'r.')
hold on
plot(xlim, polyval(p,xlim),'k')
hold off
xlabel('Sensor Readings (\circF)')
ylabel('Reference Temperature (\circF)')
title('Calibration Data - Just Room Temperature')
legend({'Room Temperature','Fit Line'},'Location','NorthWest')

Figure 10: Room temperature calibration data with best fit line

The algorithm seems to be working well for this sensor, so I applied the same algorithm to all the sensors:

for sen = 1:14
    obsTempF = [obsRoomTempF(:,sen); obsIceTempF(:,sen)];
    refTempF = [refRoomTempF; refIceTempF];
    p = polyfit(obsTempF, refTempF,1);
    adjTempF(:,sen) = polyval(p,roomTempF(:,sen));

title('Calibrated Room Temperature Readings')

hold on
plot(refRoomTimes, refRoomTempF, 'kx','DisplayName','Reference Temperature');
legend(gca, 'Location','NorthWest')

Figure 11: Calibrated room temperature readings compared to digital thermometer reference readings (same data as in figure 6)

After calibration, all my sensors appeared to be reading roughly the same values. I saved the calibration data to a MAT file named calibration.mat, and wrote the function calibrateTemperatures.m so that I could easily apply the calibration to new data in the future.

Next time

Now that I have calibrated the sensors, next time I will start analyzing some actual data collected in my apartment over the course of 9 days.