Multi Temperature Sensor Network

Intro:

When I get in to work in the morning and open up, I’ve noticed that all the rooms are different temperatures. So like a cat, curiosity got the better of me and I built a set of wireless temperature sensors.

Thankfully, the foundation of this project was covered by the previous weather station projects, so most of the mistakes had already been learned.

Please excuse my poor attempt at photo editing.

Hardware & Setup.

The setup consists of a base station and three remote sensors. For consistency, all of the temperature sensors are DS18B20 sensors (two in the waterproof-probe format and two in the TO-92 package).

All of the data is sent via nRF24L01 wireless transceivers. The remote sensors had the small PCB version and the base station had the +PA+LNA version.

The external sensor was powered via 10,000mAh lipo with suitable charging board. (Literally robbed from the weather station and butchered to fit this experiment).

The base station was also equipped with a temperature sensor and built an Arduino Uno fitted with a datalogging shield which saved the data to a .csv file on an SD card.

A 20×4 I2C LCD display also showed the real-time data from each of the sensors and also indicated if any of the remote sensors had gone offline.


Code

In the most simple terms, the transmitters take a measurement, send the data, go to sleep and repeat ad infinitum.

Each transmitter sends a single integer which acts as an ID, and a floating point number with the temperature data.

#include <LowPower.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

#define ONE_WIRE_BUS 2

OneWire oneWire(ONE_WIRE_BUS);

DallasTemperature sensors(&oneWire);

RF24 radio(7, 8); // CE, CSN
const byte address[] = "00001";

struct Data_Package {
  int roomNo;   //0 = main office, 1 = kitchen, 2 = office, 3 = outside
  float temp;
};

Data_Package data = {2, 0.00};   //2 = office transmitter

//******************************************
void setup() {

  Serial.begin(9600);
  sensors.begin();
  sensors.setResolution(12);

  radio.begin();

  if (!radio.begin()) {
    Serial.println(F("radio hardware not responding!"));
    while (1);
  }

  radio.setAutoAck(false);
  radio.setChannel(115);
  radio.setPALevel(RF24_PA_LOW);
  radio.openWritingPipe(address);
  radio.stopListening();

}
//******************************************
void loop() {

  sendMeasure();
  goToSleep(4);   //pass number of 8 second sleeps , 4 * 8 = 32s refresh

}
//******************************************
void goToSleep(int _i) {

  for (int i = 0; i < _i; i++) {
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
  }
  delay(500);

}
//******************************************
void sendMeasure() {

  sensors.requestTemperatures();
  data.temp = sensors.getTempCByIndex(0);
  radio.write(&data, sizeof(Data_Package));
  //Serial.println(data.temp);
  delay(1);

}

The base station listens for transmissions and when one arrives, it sorts the data according to the ID attached to the data packet.

#include <OneWire.h>
#include <DallasTemperature.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <LiquidCrystal_I2C.h>
#include <SD.h>
#include "RTClib.h"

#define ONE_WIRE_BUS 2

OneWire oneWire(ONE_WIRE_BUS);

DallasTemperature sensors(&oneWire);

RF24 radio(7, 8); // CE, CSN

LiquidCrystal_I2C lcd(0x27, 20, 4);

RTC_DS1307 rtc;
File dataFile;

const byte address[] = "00001";
const int chipSelect = 10;

struct Data_Package {
  int roomNo;   //0 = main office, 1 = kitchen, 2 = ncb office, 3 = outside
  float temp;
};

Data_Package data;

unsigned long tempTimer = 0;
unsigned long recieveTimer[4] = {0, 0, 0, 0};

byte degreeSymbol[8] =
{
  0b00000,
  0b00100,
  0b01010,
  0b00100,
  0b00000,
  0b00000,
  0b00000,
  0b00000
};

//***************************************
void setup() {

  Serial.begin(9600);
  sensors.begin();
  sensors.setResolution(12);

  lcd.init();
  lcd.backlight();
  lcd.createChar(0, degreeSymbol);

  radio.begin();

  if (!radio.begin()) {
    lcd.setCursor(0, 0);
    lcd.print("Radio error");
    while (1);
  }

  if (!SD.begin(chipSelect)) {
    lcd.setCursor(0, 0);
    lcd.print("SD card failed");
    while (1) ;
  }

  dataFile = SD.open("datalog.txt", FILE_WRITE);

  if (! dataFile) {
    lcd.setCursor(0, 0);
    lcd.print("Can't write data");
    while (1) ;
  }

  if (! rtc.begin()) {
    lcd.setCursor(0, 0);
    lcd.print("RTC failed");
    while (1);
  }

  //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); //uncomment to set date and time

  radio.setAutoAck(false);
  radio.setChannel(115);
  radio.setPALevel(RF24_PA_LOW);
  radio.openReadingPipe(1, address);
  radio.startListening();

  writeHeader();

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Base Stn");
  lcd.setCursor(0, 1);
  lcd.print("Kitchen");
  lcd.setCursor(0, 2);
  lcd.print("Office");
  lcd.setCursor(0, 3);
  lcd.print("Outside");

  getBaseTemp();

}
//***************************************
void loop() {

  if (radio.available()) {

    radio.read(&data, sizeof(Data_Package));

    recieveTimer[data.roomNo] = millis();

    refreshDisplay(data.roomNo, data.temp);

    writeToSD(data.roomNo, data.temp);

  }

  if ((tempTimer + 32000L) < millis()) {  //30second refresh
    tempTimer = millis();
    getBaseTemp();
  }


  for (int i = 1; i < 4; i++) {
    if ((recieveTimer[i] + 60000L) < millis()) {
      lcd.setCursor(19, i);
      lcd.print("?");
    }
  }

  delay(20);

}
//***************************************
void refreshDisplay(int room_no, float temp_ ) {

  switch (room_no) {
    case 1:
      lcd.setCursor(9, room_no);
      lcd.print(temp_);
      lcd.write(0);
      lcd.print("C");
      lcd.setCursor(19, room_no);
      lcd.print(" ");
      break;
    case 2:
      lcd.setCursor(9, room_no);
      lcd.print(temp_);
      lcd.write(0);
      lcd.print("C");
      lcd.setCursor(19, room_no);
      lcd.print(" ");
      break;
    case 3:
      lcd.setCursor(9, room_no);
      lcd.print(temp_);
      lcd.write(0);
      lcd.print("C");
      lcd.setCursor(19, room_no);
      lcd.print(" ");
      break;
  }

}
//***************************************
void writeToSD(int room_no, float temp_) {

  DateTime now = rtc.now();

  String dataString = "";
  dataString += String(now.year(), DEC);
  dataString += String('/');
  dataString += String(now.month(), DEC);
  dataString += String('/');
  dataString += String(now.day(), DEC);
  dataString += String(" ");
  dataString += String(now.hour(), DEC);
  dataString += String(':');
  dataString += String(now.minute(), DEC);
  dataString += String(':');
  dataString += String(now.second(), DEC);
  dataString += String(", ");

  switch (room_no) {
    case 1:   //kitchen
      dataString += String(" ,");
      dataString += String(temp_);
      dataString += String(", ,");
      break;
    case 2:   //ncb office
      dataString += String(" , ,");
      dataString += String(temp_);
      dataString += String(",");
      break;
    case 3:   //outside
      dataString += String(" , , ,");
      dataString += String(temp_);
      break;
  }

  dataFile.println(dataString);
  dataFile.flush();

}
//***************************************
void writeHeader() {

  //create column headers

  String dataString = "";

  dataString += String("Date & Time , Base Temp (deg.C) , Kitchen Temp (deg.C) , Office Temp (deg.C) , Outside Temp (deg.C)");

  dataFile.println(dataString);
  dataFile.flush();

}
//*********************************
void getBaseTemp() {

  sensors.requestTemperatures();
  float baseTemp = sensors.getTempCByIndex(0);
  lcd.setCursor(9, 0);
  lcd.print(baseTemp);
  lcd.write(0);
  lcd.print("C");

  DateTime now = rtc.now();

  String dataString = "";

  dataString += String(now.year(), DEC);
  dataString += String('/');
  dataString += String(now.month(), DEC);
  dataString += String('/');
  dataString += String(now.day(), DEC);
  dataString += String(" ");
  dataString += String(now.hour(), DEC);
  dataString += String(':');
  dataString += String(now.minute(), DEC);
  dataString += String(':');
  dataString += String(now.second(), DEC);
  dataString += String(", ");
  dataString += String(baseTemp);
  dataString += String(", , ,");
  dataFile.println(dataString);
  dataFile.flush();
}

Judge the code all you want, but it compiles and more impressively: it works! The only real ‘bug’ I’ve seen is that if the outside temperature falls from a two digit number to a single digit number then the Celsius symbol is repeated.


Results

The experiment was run over three days before I became too impatient and had to see the data.

Excerpt from the raw CSV file
Data from the CSV imported into excel
Annotated graph

Overall, I’m happy with how this went. I’m even happier that I was able to harvest all of the parts from various other projects. The results are interesting – it’s interesting to see how being in the office makes it warmer.

In a future version, I would like to introduce an LDR to the indicator light of the boiler to see how the external temperature effects the duty cycle of the heating system. When will this happen? Probably never.


Last updated: 19/12/2022