Capturing Farm Data with a Raspberry Pi

One of the main things that I want to accomplish as I build out my farm is to collect and analyze data so that I'm able to better understand what's happening and how to improve my operation. I'm starting that process with a Raspberry Pi Pico!

Capturing Farm Data with a Raspberry Pi
Photo by Guillaume Coupy / Unsplash

One of the main things that I want to accomplish as I build out my farm is to collect and analyze data so that I'm able to better understand what's happening and how to improve my operation.

There are a number of different pre-built and turn-key solutions to accomplish this but, most of the time, those solutions are like black boxes that you don't have control over and your data is stored on someone else's servers.

To get around this, I've decided to utilize Raspberry Pis to build my own devices that can easily be repaired or customized while allowing me to own all of my data.

A Simple Thermometer

I believe the easiest way to get started with a project like this is to start with a simple thermometer that will allow me to keep track of temperature, humidity, and barometric pressure in a germination room. To build the prototype, I purchased:

In my case, the device will be indoors near a power outlet, but you could also swap out the USB cable for solar to eliminate the need for a power outlet:

💡
For the rest of this article, I'll be writing about the non-solar version. For details on how to connect and utilize solar, keep an eye out for an upcoming post from KTech Industries

Setting up the Circuit

The first thing we need to do (after soldering headers onto the boards) is set up our circuit so the BME280 can talk to our Pico. In this case, we only need to utilize 4 pins on each device.

BME280 Pico
VIN 3v3
GND GND
SCK GP1
SD1 GP0

Writing the Code

Now that everything's hooked up, we can write the code to read sensor values from the BME280.

Install The Firmware

In order to utilize the pico, we first need to download some firmware and plug the pico into a computer while holding the boot-select. Once the pico's plugged in, we can drop the firmware on via file explorer and it will reboot automatically.

Add The Libraries

At this point, we're starting with a blank slate. To make things easier, we'll be utilizing one of the libraries from adafruit to take care of some of the low-level logic needed to utilize the BME280.

First, download the CircuitPython libraries from the official site. The files can be extracted, and then we'll copy the adafruit_bme280 directory and adafruit_requests file to a lib directory on the pico

Install Thonny

Next, we can install an IDE called Thonny, which will allow us to connect to the pico and write some code for it.

At this point, we'll also create the following files:

  • thp_sensor.py - The code that controls the sensor and contains the core logic
  • secrets.py - A file for keeping secrets, such as the password to connect to wireless

Add the logic

First, we'll start with the sensor logic.

import board
import busio

from adafruit_bme280 import basic as adafruit_bme280

class ThpSensor():
    def __init__(self):
        # Create a sensor object using the board's default I2C bus
        self.i2c = busio.I2C(board.GP1, board.GP0)
        self.bme280 = adafruit_bme280.Adafruit_BME280_I2C(self.i2c)
        
        # Set the location's pressure (hPa) at sea level
        self.bme280.sea_level_pressure = 1013.25
    
    
    def get_temperature_f(self):
        return self.bme280.temperature * 1.8 + 32
    
    
    def get_temperature_c(self):
        return self.bme280.temperature
    
    
    def get_humidity(self):
        return self.bme280.relative_humidity
    
    
    def get_pressure(self):
        return self.bme280.pressure
    
    
    def get_altitude(self):
        return self.bme280.altitude

thp_sensor.py

Here, we're creating a sensor class that contains the core logic for interacting with the BME280 sensor. Later, we'll add the logic for generating telemetry that we can record elsewhere.

Next, we're going to add our wireless SSID and Password to our secrets file.

SSID="XXX"
PASSWORD="YYY"

secrets.py

Finally, we can utilize our secrets and the ThpSensor in code.py, which is the main file that gets executed when powering on a CircuitPython device.

import secrets
import wifi

from thp_sensor import ThpSensor

sensor = ThpSensor()

wifi.radio.connect(ssid=secrets.SSID, password=secrets.PASSWORD)

print("Connected")
print("My MAC addr:", [hex(i) for i in wifi.radio.mac_address])
print("My IP address is", wifi.radio.ipv4_address)
print("Temperature f is", sensor.get_temperature_f())
print("Temperature c is", sensor.get_temperature_c())
print("Humidity is", sensor.get_humidity())
print("Pressure is", sensor.get_pressure())

code.py

Make it useful

Currently, we only have a device that powers on, connects to the wireless, and captures the current temperature, humidity, and pressure. We need to make this more useful by continually capturing the data and sending it to a server where we can process and store the information.

We'll start with adding logic to the sensor that will generate some telemetry objects for us.

import time

...

class ThpSensor():
    def __init__(self, deviceId, offsetSeconds):
        self.deviceId = deviceId
        self.offset = offsetSeconds

        ...

    def format_time(self, datetime):
        return "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.000+00:00".format(
            datetime.tm_year,
            datetime.tm_mon,
            datetime.tm_mday,
            datetime.tm_hour,
            datetime.tm_min,
            datetime.tm_sec
        )
    
    
    def generate_telemetry(self):
        utc_timestamp = int(time.time() + self.offset)
        deviceId = self.deviceId
        formatted_timestamp = self.format_time(time.localtime(utc_timestamp))
        
        return [
            {
                "deviceId": deviceId,
                "timestamp": formatted_timestamp,
                "key": "temperature",
                "value": self.get_temperature_f()
            },
            {
                "deviceId": deviceId,
                "timestamp": formatted_timestamp,
                "key": "humidity",
                "value": self.get_humidity()
            }
        ]

thp_sensor.py

Now we can update code.py to utilize our new generate_telemetry function and post the results to an API that saves everything to a database

import adafruit_requests
import socketpool
import ssl
import time

...

#################################
#          Definitions          #
#################################

DEVICE_ID = "some-device-id"
TIMEZONE_OFFSET_SECONDS = 21600
TELEMETRY_URL = "https://some-api.com"
TIME_DELAY = 60 * 60 # one hour

def connect_to_wireless():
    wifi.radio.connect(ssid=secrets.SSID, password=secrets.PASSWORD)
    print("Connected")
    print("My MAC addr:", [hex(i) for i in wifi.radio.mac_address])
    print("My IP address is", wifi.radio.ipv4_address)

def configure_requests():
    global pool
    global requests
    pool = socketpool.SocketPool(wifi.radio)
    requests = adafruit_requests.Session(pool, ssl.create_default_context())
    
def post_telemetry(telemetry_json):
    global requests
    try:
        header = {
            "Authorization": "Bearer " + secrets.TOKEN
        }
        response = requests.post(TELEMETRY_URL, json=telemetry_json, headers=header)
        json_response = response.json()
        print("Response:", json_response)
    except Exception as e:
        print("Error posting telemetry:", str(e))

#################################
#             Logic             #
#################################

sensor = ThpSensor(DEVICE_ID, TIMEZONE_OFFSET_SECONDS)
connect_to_wireless()
configure_requests()

while True:
    telemetry = sensor.generate_telemetry()
        
    for item in telemetry:
        post_telemetry(item)
        
    time.sleep(TIME_DELAY)

code.py

Now, as long as the device remains powered on, it will send the temperature and humidity to our API every hour.

What are the Benefits?

There are a few reasons capturing information such as the temperature and humidity could be beneficial to farm operations.

Record Keeping & Reporting

Record keeping is an obvious reason for capturing information like this. Once we have the information in a database, not only can we visualize what's happening over time, but we can begin utilizing that information to gain insights on how environmental factors are effecting yields and crop health.

Alerting

Capturing telemetry from the farm can also allow logic to run that alerts us when something is wrong. For example, if this is running in a greenhouse that has fans that are meant to keep the temperature at 80 degrees, we can then send a text or email when the temperature varies too much - say 75 or 85 degrees.

Automation

Similar to alerting, we could also connect multiple devices together, allowing the automation of various aspects of our operations. If the thermometer tells us the greenhouse is 90 degrees, then we can tell a fan to turn on and/or vents to open to help regulate the temperature.

Another scenario would be if soil becomes too dry or too moist, we can tell the irrigation system to turn on or off to rectify the condition.


Do you have ideas on how capturing farm data could help improve your operation? I'd love to hear them in the comments!