Evan Rust
Published © GPL3+

Backyard Weather Station with Packet Radios

Use two 915MHz radios to send/receive current environmental information and display it in real-time up to 350 meters away!

IntermediateFull instructions provided3 hours2,313
Backyard Weather Station with Packet Radios

Things used in this project

Hardware components

Adafruit Feather M0 Radio with RFM69 Packet Radio
×2
Gravity I2C OLED-2864 Display
DFRobot Gravity I2C OLED-2864 Display
×1
PIR Motion Sensor (generic)
PIR Motion Sensor (generic)
×1
Photo resistor
Photo resistor
×1
Adafruit ADT7410 I2C Temperature Sensor
×1
1800 mAh LiPo Battery
×1

Software apps and online services

Arduino IDE
Arduino IDE
VS Code
Microsoft VS Code

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Transmitter

Receiver

Code

Program

C/C++
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_SleepyDog.h>
#include <RHReliableDatagram.h>
#include <RH_RF69.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "Adafruit_ADT7410.h"

// role is 0 -> RX, role is 1 -> TX
#define ROLE 0

#if ROLE == 0
// change addresses for each client board, any number :)
#define MY_ADDRESS     2
#elif ROLE == 1
#define DEST_ADDRESS 2
#define MY_ADDRESS 1
#endif

#define RF69_FREQ 915.0
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4
#define PIR_PIN       12
#define LDR_PIN       A0

#if ROLE == 0
Adafruit_SSD1306 oled = Adafruit_SSD1306();
#elif ROLE == 1
Adafruit_ADT7410 tempsensor = Adafruit_ADT7410();
#endif

int16_t packetNum = 0;

// Singleton instance of the radio driver
RH_RF69 rf69(RFM69_CS, RFM69_INT);

// Class to manage message delivery and receipt, using the driver declared above
RHReliableDatagram rf69_manager(rf69, MY_ADDRESS);

void setup() {
    Serial.begin(115200);
    //while(!Serial);
    #if ROLE == 0
    oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
    oled.display();
    delay(500);
    oled.clearDisplay();
    oled.display();
    #elif ROLE == 1
    pinMode(PIR_PIN, INPUT);
    pinMode(LDR_PIN, INPUT);
    if (!tempsensor.begin()) {
    Serial.println("Couldn't find ADT7410!");
    while (1);
    }
    #endif
    pinMode(RFM69_RST, OUTPUT);
    digitalWrite(RFM69_RST, LOW);
    // manual reset
    digitalWrite(RFM69_RST, HIGH);
    delay(10);
    digitalWrite(RFM69_RST, LOW);
    delay(10);
    
    if (!rf69_manager.init()) {
        Serial.println("RFM69 radio init failed");
        while (1);
    }
    Serial.println("RFM69 radio init OK!");
    
    // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module)
    // No encryption
    if (!rf69.setFrequency(RF69_FREQ)) {
        Serial.println("setFrequency failed");
    }
    rf69.setTxPower(20, true);

    uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
    rf69.setEncryptionKey(key);

    Serial.print("RFM69 radio @");  Serial.print((int)RF69_FREQ);  Serial.println(" MHz");
    #if ROLE == 0
    // OLED text display tests
    oled.setTextSize(2);
    oled.setTextColor(WHITE);
    oled.setCursor(0,0);
    oled.println("RFM69 @ ");
    oled.print((int)RF69_FREQ);
    oled.println(" MHz");
    oled.setTextSize(1);
    oled.display();
    #endif

    delay(500);
}

uint8_t buf[RH_RF69_MAX_MESSAGE_LEN];
uint8_t data[] = "  OK";

void loop() {
    #if ROLE == 0
    if (rf69_manager.available())
    {
        // Wait for a message addressed to us from the client
        uint8_t len = sizeof(buf);
        uint8_t from;
        if (rf69_manager.recvfromAck(buf, &len, &from)) {
        buf[len] = 0; // zero out remaining string
        
        Serial.print("Got packet from #"); Serial.print(from);
        Serial.print(" [RSSI :");
        Serial.print(rf69.lastRssi());
        Serial.print("] : ");
        Serial.println((char*)buf);

        oled.clearDisplay();
        oled.setCursor(0,0);
        uint16_t ambientLight = *(buf + 2);
        oled.print("TMP: "); oled.print((int)buf[1]); oled.print(" "); oled.println(ambientLight);
        oled.print("Animal present? "); oled.print((bool)buf[0]);
        oled.display();

        // Send a reply back to the originator client
        if (!rf69_manager.sendtoWait(data, sizeof(data), from))
            Serial.println("Sending failed (no ack)");
        }
    } 
    #elif ROLE == 1
    delay(1000);
    float c = tempsensor.readTempC();
    float f = c * 9.0 / 5.0 + 32;
    char radiopacket[5];
    radiopacket[0] = digitalRead(PIR_PIN);
    radiopacket[1] = static_cast<int>(f);
    radiopacket[2] = analogRead(LDR_PIN);
    radiopacket[4] = '\0';
    Serial.print("Sending "); Serial.println(radiopacket);
    
    // Send a message to the DESTINATION!
    if (rf69_manager.sendtoWait((uint8_t *)radiopacket, strlen(radiopacket), DEST_ADDRESS)) {
        // Now wait for a reply from the server
        uint8_t len = sizeof(buf);
        uint8_t from;   
        if (rf69_manager.recvfromAckTimeout(buf, &len, 2000, &from)) {
        buf[len] = 0; // zero out remaining string
        
        Serial.print("Got reply from #"); Serial.print(from);
        Serial.print(" [RSSI :");
        Serial.print(rf69.lastRssi());
        Serial.print("] : ");
        Serial.println((char*)buf);     
        } else {
        Serial.println("No reply, is anyone listening?");
        }
    } else {
        Serial.println("Sending failed (no ack)");
    }
    #endif
}

Credits

Evan Rust

Evan Rust

120 projects • 1054 followers
IoT, web, and embedded systems enthusiast. Contact me for product reviews or custom project requests.

Comments