Twitter icon
Facebook icon
LinkedIn icon
Google icon
Reddit icon
StumbleUpon icon
Del.icio.us icon

Backyard Weather Station with Packet Radios

Added to IoTplaybook or last updated on: 06/08/2020
Backyard Weather Station with Packet Radios

Story

Environmental Monitoring

It's almost gardening season, which means monitoring your plant beds for adequate sunlight, warmth, and pesky critters are more necessary than ever. But, there's an issue; most IoT gardens rely on WiFi, which can be a large problem if your garden happens to be more than a couple hundred feet away. A potential solution is logging the data and reading it back, but that can't occur in real-time, and using a cellular network costs a lot of money. This project aims to solve that issue by utilizing a pair of Adafruit M0 Feathers with integrated RFM69 radio modules to provide real-time monitoring across a large distance.

Things used in this project

Hardware components

 
Adafruit Feather M0 Radio with RFM69 Packet Radio
 
× 2

Adafruit

Gravity I2C OLED-2864 Display
DFRobot Gravity I2C OLED-2864 Display
 
× 1

DFrobot

PIR Motion Sensor (generic)
PIR Motion Sensor (generic)
 
× 1

SparkFun

Photo resistor
Photo resistor
 
× 1

Newark

 
Adafruit ADT7410 I2C Temperature Sensor
 
× 1

Adafruit

 
1800 mAh LiPo Battery
 
× 1  

Software apps and online services

Arduino IDE
Arduino IDE
 
  Arduino
VS Code
Microsoft VS Code
 
 

Microsoft

Hand tools and fabrication machines

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

Adafruit's M0 Feather with RFM69HCW Radio Module

Imagine taking an ARM M0 microcontroller and giving it the ability to send data up to 350 meters away with ease. In a nutshell, that is what Adafruit's M0 Feather with RFM69 Radio Module is able to do.


1 / 4

 


2 / 4


3 / 4


4 / 4

It uses an efficient-yet-powerful ATSAMD21G18 processor, which is the same one on the Arduino Zero. The processor runs at 48MHz and has 256KB of flash and 32KB of RAM, which is a massive step up from something like an Arduino Uno, and it has all of this while still consuming relatively little power.

Sensors

For this project, I wanted to keep track of three things in the surrounding area: temperature, ambient light, and if there are any animals nearby. To do this, I went with Adafruit's ADT7410 High Accuracy Temperature Sensor, which is able to measure temperature with an accuracy of.5C and a resolution of.0078C.


1 / 4


2 / 4


3 / 4


4 / 4

To monitor ambient light levels, I chose a simple photoresistor, which changes its resistance based on the level of light it receives. Finally, there is a Passive InfraRed (PIR) sensor that sets its output pin to high whenever a warm object passes in front of it, such as a small animal.

Power Options

There are three ways to power the device. First, there is a JST connector on the side where a 3.7V LiPo battery can be attached, plus it has a builtin charge circuit. Second, a 5V USB power bank and micro USB cable could be used for extended monitoring. And finally, a solar panel could keep the project going in perpetuity without the need to change anything.

 

Configuring Communication and Sending Information

When the M0 Feather is first powered on, it tries to initialize all of its peripherals (depending on how its set up) and then starts its radio module. In the attached code, the radio is set to use maximum power, but this can be adjusted based on how far away the two radios are from each other. For an antenna, I simply cut a piece of 3-inch wire and soldered it onto the ANT pad on the back of the Feather, and then repeated the same process for the other one.


1 / 3


2 / 3


3 / 3

Since the modules use packet mode to send information, they each get an address. The transmitting device sends out information every second, which includes the temperature if there is an animal, and the value provided by the photoresistor. It uses the sendtoWait() function that attempts to send the packet to a destination and waits for a response. If there is none, then it times out and prints an error message.

Displaying Values

The receiver constantly loops and checks for a new message to arrive, and once it does, it sends an ACK (acknowledgment) packet and then decodes the information to be displayed on an SSD1306 OLED.


1 / 3


2 / 3


3 / 3

Because the ambient light value takes up two bytes, it has to get converted back to a single variable by setting an unsigned 2-byte int to the buffers pointer + 2 bytes. Next, the temperature is decoded by casting the second byte to an int. The animal present value is cast to a boolean. Finally, all of these values are drawn to a blank OLED screen and displayed.


1 / 3


2 / 3


3 / 3

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

Arduino “having11” Guy  Arduino “having11” Guy

18-year-old IoT and embedded systems enthusiast. Also, an intern at Hackster.io who loves working on projects and sharing knowledge.

 

 

Avnet

This content is provided by our content partner Avnet, a global technology solutions provider with end-to-end ecosystem capabilities. Visit them online for more great content like this.

This article was originally published at Avnet. It was added to IoTplaybook or last modified on 06/08/2020.