Anderson-Bricker
Published © GPL3+

Remote Weather Station

A remote weather station that uses two arduinos, one as a transmitter and one as a data-logging receiver.

IntermediateFull instructions provided1,778
Remote Weather Station

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×2
SparkFun XBee Explorer USB
SparkFun XBee Explorer USB
Required for programming XBee radios and verifying communication
×1
USB A to mini USB B
For Spark Fun XBee Explorer USB
×1
Xbee RF TXRX MODULE 802.15.4 Wire Ant
Xbee Radios
×2
DfRobot XBee Shield
×2
Half Breadboard
×2
Alphanumeric LCD, 16 x 2
Alphanumeric LCD, 16 x 2
Screen for receiver data
×1
M/F Jumper Wire
For extended connections to the LCD on the Receiver.
×1
OLED
Screen for transmitter.
×1
Adafruit BME 280 Sensor
Pressure, Temperature, Humidity, Elevation Sensor
×1
8' White Exterior Extension Cord
Power for outdoor transmitter
×1
ABS electrical box
For housing receiver
×1
ABS Plastic Waterproof Electronic Box
For housing transmitter outside
×1
Potentiometer
For receiver
×2
Jumper Wire
Various jumper wire and breadboard wire
×50
Adafruit Logging Sheild
If you do not want to log data, one of the sketches bellow does not include this requirement.
×1
Adafruit SD card
If the data shield does not include this, you will need to purchase one that works with your shield. Important note - if the SD card is not inserted in the data logging shield, the whole shield will not function!! This will cause problems if you are trying to upload a sketch.
×1
SD card reader/writer
If your computer has one your set. If not, you will need a USB device that accomplishes this. This is required for formatting and reading from the SD card. The SD card is what enables one to record data from an Arduino and then upload that data to a computer to evaluate the recorded data. Adafruit has a link to one in the tutorial at end of this page.
×1
Coin cell battery
If the RTC does not come with one, you will need a battery that the RTC specifies. Without it the time will be lost if the data logger looses power.
×1
Adafruit Stack Through Headers
These headers must be soldered onto the Data Logger Shield instead of the male headers that come with the shield. This allows stacking the XBee Shield on top. Additionally, pay attention to the direction of the stacking header for the ICSP pins because they go in the opposite directions then all the other stacking headers.
×1
9 volt power supply
To test the units one will need at least one external power supply if one Uno is still powered by a computer. To make both Unos independent, two external power supplies will be required.
×2

Software apps and online services

XCTU
Application for configuring Xbee radios.
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
The data logging shield requires soldering male pins.
Drill
The plastic boxes for the transmitter and receiver require drilling mounting holes for controllers and breadboards.
Hole Saw
The transmitter if installed outdoors requires an all weather extension cord. The hole saw used with a drill press or drill cuts a nice 1 1/4" hole for the cord plug to fit through.
Saw
The exterior transmitter box will eventually be housed inside a faux bird house fabricated with 1" x 8" cedar. I will use a table saw to cut the various dimensional parts. A circular saw will work or a hand saw in a pinch.

Story

Read more

Schematics

Power supply for Xbee shield

NOTE: Now the Stacking Header Pin kits have headers for the ICSP pins. So if you have the new kits you can skip this example for supplying power to the peripherals. If you skip these steps you will have to solder male header pins on the Xbee Shield to supply power for the 3 & 5 Volt components.

The data logger on the receiver blocks the power supply for the Xbee. These three images show how to wire around that and transmit power to the ICSP socket on the Xbee shield.

receiver_box_build_(1)_nf92bPIM1D.JPG

receiver_box_build_(2)_lk64rZMpLZ.JPG

Transmitter Wiring Example

This image illustrates how to wire the transmitter. Additionally, male header pins must be soldered to the Xbee shield in order to supply both the 5 volt and 3 volts necessary for the OLED and BME280 sensor.

Receiver

This image illustrates how to wire the Receiver.

Notes for configuring Xbee radios

The configuration approach highlighted by Blum requires changes to be made with the XTCU application. The attached PDF file walks through the steps to do this .

Adafruit BME280 Tutorial

Helpful tutorial for the BME sensor

Adafruit Logging Shield Tutorial

This tutorial walks through the set up of the logging shield and setting the real time clock.

Transmitter Circuit Diagram

This is the circuit diagram for the transmitter.

Receiver Circuit Diagram

This is the circuit diagram for the receiver. Be sure to consult the photos for how to wire the power to the XBee Shield and breadboard.

Sample of recorded data and graph

This is an example of data from the SD card of the Data Logger. I am using a Windows computer, so the data is opened in Excel and graphed within that application.

Transmitter Installation 1

Mounting the transmitter

Transmitter Installation 2

detail

Transmitter Installation 3

detail

Transmitter Installation 4

Mounting the birdhouse cabinet over electric housing.

Transmitter Installation 5

Completed birdhouse

Transmitter Installation 6

Open front for access to electrical box for reprogramming.

Transmitter Installation 7

Viewing window for OLED. The camera used fast shutter speed so the OLED data looks broken, the OLED actually works fine.

Transmitter Installation 8

Weather station on shed.

Code

TempPresHumOLED_Station_Transmitter_Final.ino

Arduino
This is an update to the original sketch for the remote weather station transmitter. After completing a comparison of multiple temperature and humidity records with other local sensor records, the calibration for temperature and humidity have been adjusted.
/* 
 * This sketch is from https://www.programmingelectronics.com/wireless-weather-monitoring-hc-12-long-range-transceivers-student-project/
 * It is slightly altered; the original sketch was designed for use with  
 * solar panels for a remote weather station.
 * 
 * This code is to use with Adafruit BMP280 and OLED screen   (Imperial)
 * It measures both temperature and pressure and it displays them on the OLED display with the altitude
 * It's a modified version of the Adafruit example code
 * Refer to www.surtrtech.com or SurtrTech Youtube channel
 */
 
/*
 * Note from John on 11/13/2019.  Iam using a DIYmall Yellow & Blue 128x64 OLED.
 * This requires in the "void setup" changing 0x3D to 0x3c in the following statement 
 * if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) { // Address 0x3D for 128x64 // Had to change for DIYmall OLED 
 * This change makes this sketch work for this type of OLED screen. Surtrtech.com included
 * this change.
 * 
 * Additionally, I am using the Adafruit BME280 sensor because I wanted a humidity sensor. So BME replaces
 * BMP throughout the sketch as well as the library.
 */

// OLED Libraries
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// OLED declarations
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);     

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };
//varialbe to storing "for data # of loops"
  int y = 0;

//valiables to store serial data for transmition through xbees.
  int t = 0;
  int p = 0;
  float P = 0;
// BME280 Libraries

#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// BME Declarations - pins
#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

#define SEALEVELPRESSURE_HPA (1013.25)

//Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

unsigned long delayTime;

void setup() {
    Serial.begin(9600);
  bme.begin();                                //Start the bme                  
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C); //Start the OLED display
  display.clearDisplay();
  display.display();
  display.setTextColor(WHITE);
  display.setTextSize(1); 
  display.print("SurtrTech");     //Show the name, you can remove it or replace it
  display.setCursor(32,12);
  display.setTextSize(2);          
  display.println("BME280"); 
  display.display();
  delay(2000);
}


void loop() { 

  for(y = 0; y < 1; y ++)
  {
     display.clearDisplay();
    float T = bme.readTemperature() * 9 / 5 + 32-2;    //Read temperature in C and conversion to F; 
                                                     //the -2 is for calibrating the sensor
                                                     // inacuracy that may be caused by heat generated in
                                                     // the electrical box enclosure.   If your sensor does not
                                                     // have this problem, the -2  can
                                                     //be removed from the conversion calculation.
    float P = bme.readPressure()/3386.39;        //Read Pressure in Pa and conversion to inches of mercury
    float H = bme.readHumidity()* 1.644310631;   //Read humidity; the * 1.644310631 is for calibration. 
                                                 //If you do not have this problem it can be removed.

    //convert to int for serial communication
    int t = T; 
    int h = H;                                              

    display.setCursor(0,0);                       //Oled display, just playing with text size and cursor to get the display you want
    display.setTextColor(WHITE);
    display.setTextSize(2); 
    display.print("Temp");
    
    display.setCursor(0,25); //originally 0,18
    display.print(T,1);
    display.setCursor(50,17);
    display.setTextSize(1);
    display.print("F");

    display.setTextSize(1);
    display.setCursor(65,0); //originally 65,0
    display.print("Pres-inHg");
    display.setTextSize(2);
    display.setCursor(65,17); //originally 65,10
    display.print(P,2);
    display.setTextSize(1);

    display.setCursor(65,50); //originally 65,25
    display.print("Hum");
    display.setTextSize(2);
    display.setCursor(90,50); //originally 90,25
    display.print(H,0);
    display.setTextSize(1);
    display.setCursor(120,50); //originally 110,25
    display.print("%");
    
    display.display();

    //Send data through xbee radio
     Serial.print(t);
     Serial.print(',');
     Serial.print(h);
     Serial.print(',');
     Serial.println(P);
  }
  int t = 0; // Clear buffer data 
  int h = 0;
  int p = 0;
  float P = 0;                                           
  delay(50);
  
   
}

TempPresHumLCD_Station_Reciever_Final.ino

Arduino
This is the sketch for the data logging remote weather station receiver.
/* This sketch is an adaptation from 
 * https://www.programmingelectronics.com/wireless-weather-monitoring-hc-12   *-long-range-transceivers-student-project/
 * It is slightly altered; the original sketch was designed for use with  
 * solar panels for a remote weather station.
 * 
 * The original intent for this project was for a wireless weather station
 * not reliant on internet applications. After starting the project, I        * decided to add
 * a pain variable to compare nerve pain to changes in pressure. That         * required adding a 
 * potentiometer to record changing pain levels and data logging to compare   * pressure
 * changes with pain over time.
 *  
 * The transmitter sketch uses the Adafruit BME 280 sensor.  
 * 
 * I am using a 2x16 LCD for this receiver because the data logger shield and  * RTC uses 
 * the SPI and I2C pins that OLEDs typically use.
 */
 
// Real Time Clock library and initialize:
#include "RTClib.h" //For Real Time Clock
#include <Wire.h>  //For RTC I2C communication
RTC_DS1307 RTC;  // Defining the RTC

//Data Logging library and initialize:
#include <SD.h>   //For talking to SD Card
#include <SPI.h>   //For SD Card communication

//Set by default for the SD card library:
//MOSI = pin 11
//MISO = pin 12
//SCLK = pin 13
//We always need to set the CS Pin:
const int CS_PIN = 10; //Chip Select pin

//We set this high to provide power:
const int POW_PIN = 8;

//Variable for conditional counting for data recording intervals
int c = 0; //variable counting.

//Initialize strings:
String year, month, day, hour, minute, second, time, date;

//Include LCD library and initialize:
#include<LiquidCrystal.h>
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

//potentiometer for recording pain initializers:
const int inputPot = A0;  //Pain recording potentiometer on Analog pin 0.
int Pval = 0;     //Variable to store Kristin's Pain value.

//Take a number of readings from potentiometers and average to smooth out the input value.
//Higher # of readings increase smoothing but slow down process.
const int numReadings = 10;

int readings[numReadings];   //the readings from analog pot 
int readIndex = 0;           //the index of the current reading
int total = 0;               //the running total for readings
int average = 0;             //the average for pot

//Incoming sensor data initializers:     
int tval = 0;     //Variable to store Temperature value.
int hval = 0;     //Variable to store Humidity value.
float pval = 0;   //Variable to store Pressure value.


void setup() {
  Serial.begin(9600);
  Serial.println(F("Initializing Card"));
  //CS pin, and pwr/gnd pins are outputs
  pinMode(CS_PIN,   OUTPUT);
  
  //Initiate the I2C bus and the RTC library
  //Card will draw power from pin 8, so set it high
  pinMode(POW_PIN, OUTPUT);
  digitalWrite(POW_PIN, HIGH);
  
  Wire.begin();
  RTC.begin();

   //If RTC is not running, set it to the computer's compile time
  if (! RTC.isrunning())
  {
    Serial.println(F("RTC is NOT running!"));
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
 
  //Initialize SD card
  if (!SD.begin(CS_PIN))
  {
    Serial.println(F("Card Failure"));
    return;
  }
  Serial.println(F("Card Ready"));

  //Write column headers
  File dataFile = SD.open("log.csv", FILE_WRITE);
  if (dataFile)
  {
    dataFile.println(F("\nNew Log Started!"));
    dataFile.println(F("Date,Time,Pressure,Pain"));
    dataFile.close(); //Data isn't actually written until we close the connection!
  
    //Print same thing to the screen for debugging
    Serial.println(F("\nNew Log Started!"));
    Serial.println(F("Date,Time,Pressure,Pain"));
  }
  else
  {
    Serial.println(F("Couldn't open log file"));
  }

  //Set up the LCD's number of columns and rows
  lcd.begin(16,2);
  lcd.clear();

   // intitialize all the readings for potentiometer to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++)
  {
    readings[thisReading] =0;
  }
  delay(100);
}


void loop() 
{ 
  //Get the current date and time info and store in strings
  DateTime datetime = RTC.now();
  year  = String(datetime.year(),  DEC);
  month = String(datetime.month(), DEC);
  day  = String(datetime.day(),  DEC);
  hour  = String(datetime.hour(),  DEC);
  minute = String(datetime.minute(), DEC);
  second = String(datetime.second(), DEC);
 
  //Concatenate the strings into date and time
  date = year + "/" + month + "/" + day;
  time = hour + ":" + minute + ":" + second;
  
  //This section of loop is for collecting and updating 
  //Pain potentiometer data.
  
  //subtract the last potentiometer reading;
  total = total - readings[readIndex];
  //read from the potentiometer;
  readings[readIndex] = analogRead(inputPot);
  //add the reading to the total;
  total = total + readings[readIndex];
  //advance to the next position in the array;
  readIndex = readIndex + 1;
  //if we're at the end of the array...
  if (readIndex >= numReadings)
  {
    //... wrap around to the beginning:
    readIndex = 0;
  }
  //calculate the average:
  average = total / numReadings;
  //Take the average and reduce to a lower number to work closer to temperature values.
  int Pval = map(average, 0, 1023, 0, 9);
  Pval = constrain(Pval, 0, 9);
  
  //Gather sensor data from xbee radio transmission.
  if (Serial.available() > 1) // checks to see if XBEE is receiving data.
  { 
    for (int x = 0; x < 1; x ++) // run once. Keep in mind transmitter actually sends twice.
    {
      int t = Serial.parseInt(); //read temp
      int tval = t;
      int h = Serial.parseInt(); //read humidity
      int hval = h;
      float p = Serial.parseFloat(); // read barometric value
      float pval = p;

      //Send the Pressure and Pain value to LCD.
      lcd.setCursor(0,0);
      lcd.print("Hg: ");
      lcd.print(pval);
      lcd.print("  F: ");
      lcd.print(tval);
      lcd.setCursor(0,1);
      lcd.print("Pain: ");
      lcd.print(Pval);
      lcd.print("    H: ");
      lcd.print(hval);
    
   
   if(c >= 12000) 
/*
Can change this # in () to increase or decrease the time interval between data recording.  The time is the above # multiplied by the delay time at the end of the main loop so, the # * 50 mls. Because of the xbee radio transmissions, it is important to not have long delays so this counting loop avoids that problem.  12000 loops is logging about every 22 minutes. So the math is not quite working out, perhaps there is additional time accumulating in the other processes.
*/
  //Open a file and write to it.
  {
    File dataFile = SD.open("log.csv", FILE_WRITE);
    if (dataFile)
      {
      dataFile.print(date);
      dataFile.print(F(","));
      dataFile.print(time);
      dataFile.print(F(","));
      dataFile.print(pval);
      dataFile.print(F(","));
      dataFile.println(Pval);
      dataFile.close(); //Data isn't actually written until we close the connection!
   
      //Print same thing to the screen for debugging    
      Serial.print(date);
      Serial.print(F(","));
      Serial.print(time);
      Serial.print(F(","));
      Serial.print(pval);
      Serial.print(F(","));
      Serial.println(Pval);
      }
      c = (0); // reset counter
     }
     else
     {
      c++; 
     }
     Serial.flush();
     delay(50); 
    }
  } 
}

TempPresHumLCD_Station_Reciever_No_Logger.ino

Arduino
If you do not want to log the data coming into the receiver use this sketch.
/* This sketch is from https://www.programmingelectronics.com/wireless-weather-monitoring-hc-12-long-range-transceivers-student-project/
 * It is slightly altered, the original sketch was designed for use with  
 * solar panels for a remote weather station.
 *  
 * The transmitter sketch uses the Adafruit BME 280 sensor.  
 */
 
/*
 * I am using a 2x16 LCD for this reciever because the data loger shield and RTC uses 
 * the SPI and I2C pins that OLEDs typically use.
 */

//#include "RTClib.h"
//RTC_DS1307 rtc;
// OLED Libraries
#include <Wire.h>
#include <SPI.h>

//Include LCD library and initialize:
#include<LiquidCrystal.h>
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

//Take a number of readings from potentiometers and average to smooth out the input value.
//Higher # of readings increase smoothing but slow down process.
const int numReadings = 10;

int readings[numReadings];   //the readings from analog pot 
int readIndex = 0;           //the index of the current reading
int total = 0;               //the running total for readings
int average = 0;             //the average for pot

int inputPot = A0;  //Potentometer on Analog pin 0.
int Pval = 0;     //Variable to store Kristin's Pain Value.
     
int tval = 0;     //Variable to store Temperature Value.
int hval = 0;     //
float pval = 0;   //Variable to store Pressure Value.


void setup() {
  Serial.begin(9600);
  Wire.begin(); // begins RTC serial data

  //Set up the LCD's number of columns and rows
  lcd.begin(16,2);
  lcd.clear();

   // intitialize all the readings for Potentometer to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++)
  {
    readings[thisReading] =0;
  }
 
}


void loop() 
{ 
  //subtract the last potentometer reading;
  total = total - readings[readIndex];
  //read from the potentometer;
  readings[readIndex] = analogRead(inputPot);
  //add the reading to the total;
  total = total + readings[readIndex];
  //advance to the next position in the array;
  readIndex = readIndex + 1;
  //if we're at the end of the array...
  if (readIndex >= numReadings)
  {
    //... wrap around to the beginning:
    readIndex = 0;
  }
  //calculate the average:
  average = total / numReadings;
  //Take the average and reduce to a lower number to work closer to temperture values.
  int Pval = map(average, 0, 1023, 0, 9);
  Pval = constrain(Pval, 0, 9);
  

  
 // DateTime now = rtc.now(); // get time and date from RTC

  if (Serial.available() > 1) // checks to see if HC-12 is receiving data.
  { 
    for (int x = 0; x < 1; x ++) // run once. Keep in mind transmitter actually sends twice.
    {
      int t = Serial.parseInt(); //read temp
      int tval = t;
      int h = Serial.parseInt(); //read humidity
      int hval = h;
      float p = Serial.parseFloat(); // read barometeric value
      float pval = p;
      
    Serial.print(tval);
    Serial.print(',');
    Serial.print(hval);
    Serial.print(',');
    Serial.print(pval);
    Serial.print(',');
    Serial.println(Pval);

    //Send the Pressure and Pain value to LCD.
    lcd.setCursor(0,0);
    lcd.print("Hg: ");
    lcd.print(pval);
    lcd.print("  F: ");
    lcd.print(tval);
    lcd.setCursor(0,1);
    lcd.print("Pain: ");
    lcd.print(Pval);
    lcd.print("    H: ");
    lcd.print(hval);
    
    }
  Serial.flush();
  delay(50);
  
}
}

XBee Radio Testing from Jeremy Blum's arduino_read_pot.ino from, "Exploring Arduino.com/content/ch6"

Arduino
Use this sketch to test XBee Radios
/*
Exploring Arduino - Code Listing 6-6: Arduino Code to send Data to the Computer
http://www.exploringarduino.com/content/ch6

Copyright 2013 Jeremy Blum ( http://www.jeremyblum.com )
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License v3 as published by
the Free Software Foundation.
*/

//Sending POT value to the computer

const int POT=0; //Pot on Analog Pin 0

int val; //For holding mapped pot value

void setup()
{
  Serial.begin(9600); //Start Serial
}

void loop()
{
  val = map(analogRead(POT), 0, 1023, 0, 255); //Read and map POT
  Serial.println(val);                         //Send value
  delay(50);                                   //Delay so we don't flood the computer
}

Credits

Anderson-Bricker

Anderson-Bricker

0 projects • 1 follower

Comments