Build A Plant Monitoring Tool With IoT: A Beginner-Friendly Tutorial
By
Zoe Steinkamp /
Use Cases
Jun 02, 2023
Navigate to:
This article was originally published in The New Stack and is reposted here with permission.
Creating an Internet of Things (IoT) app to monitor a house plant is a pragmatic starting place to learn about data that changes over time. It’s useful for anyone who loves the idea of indoor gardening but forgets to check their plants regularly.
This project is accessible to everyone from students working on a science fair project to botanists monitoring exotic plant nurseries. There are many ways to monitor a house plant, but this is the high-tech way — with sensors and advanced software systems.
For this project, we’ll use InfluxDB, a time series platform that specializes in storing sequential data as it appears over time. This is useful when comparing data, creating alerts for specific thresholds and monitoring events in the physical and virtual worlds alike.
The full list of supplies, a schematic drawing for your breadboard (with microcontroller of choice) and the source code are all available from my git repository if you want to follow along for your own edification or desperately need to keep a houseplant alive.
The architecture
An IoT sensor tracks my plant’s health metrics at timed intervals. We classify the data it collects as time series data because it includes a timestamp, which appears as the first column in storage. The IoT sensors generate data about the plant’s health. Then I use Telegraf, an open source collection agent, and the Python client library to collect that data and send it to the storage layer, InfluxDB. The Plotly graphing library provides the data visualization. I coded the project in Python and it uses the Flask library for routing.
Getting started
Instruments:
- A plant to monitor
- A Particle Boron or similar microcontroller
- At least one IoT sensor for your plant
- A breadboard
- Jump wires
I use four sensors to generate the following five data points:
- Air temperature
- Humidity
- Light
- Soil temperature
- Soil moisture
Microcontroller
I use the Boron microcontroller and set the device up through the company website. To receive data from the microcontroller itself, I connect it to my laptop via a USB cable. Microcontroller setup depends on which microcontroller you selected for use. Your microcontroller might provide other connection options, including Bluetooth or small server access over TCP/IP.
Follow the instructions provided by your microcontroller’s manufacturer until you receive input from your sensors. You don’t need to make sense of the data yet; just make sure your microcontroller is sending raw data.
InfluxDB
Sign into InfluxDB. Create a bucket, which is where InfluxDB stores data. We will connect to InfluxDB via an API. The next step is to [create the required credentials and tokens] (https://docs.influxdata.com/influxdb/v2.7/security/tokens/.
Code
Writing data into InfluxDB is straightforward and starts with the client library. I use InfluxDB’s Python client library for this project. The code below is an example of how you can write code to send raw data from your microcontroller’s sensors to InfluxDB.
def write_to_influx(self,data):
p = (influxdb_client.Point("sensor_data")
.tag("user",data["user"])
.tag("device_id",data["device"])
.field(data["sensor_name"], int(data["value"])
))
self.write_api.write(bucket=self.cloud_bucket, org=self.cloud_org, record=p)
print(p, flush=True)
Tags
Measurements are the InfluxDB equivalent to tables in a relational database. My code makes good use of tags. Tags aren’t required but come in handy because they’re metadata that make the data easier to understand and work with. In this case they can specify different plants, devices or something else depending on how complicated you want to get.
Querying data
Queries return tables similar to the one below.
Before I can query my data, I need to initialize the Flight SQL client.
from flightsql import FlightSQLClient
Followed by:
# This is our flight client setup, it’s how we will query from IOX
# we need to remove the Https:// from our host
host = host.split("://")[1]
self.flight_client = FlightSQLClient(host=host,
token=token,
metadata= {'bucket-name': bucket}
)
self.cloud_bucket = bucket
self.cloud_org = org
Below is a basic SQL query to retrieve data from InfluxDB.
SELECT {sensor_name}, time FROM sensor_data WHERE time > (NOW() - INTERVAL '2 HOURS') AND device_id='{deviceID}'
Before we can retrieve and read the data, we have to convert it to Pyarrow format. The code below is a function that includes the query and connection to Flight SQL to retrieve the data.
def querydata(self, sensor_name, deviceID) -> DataFrame:
query = self.flight_client.execute(f"SELECT {sensor_name}, time FROM sensor_data WHERE time > (NOW() - INTERVAL '2 HOURS') AND device_id='{deviceID}'")
# Create reader to consume result
reader = self.flight_client.do_get(query.endpoints[0].ticket)
# Read all data into a pyarrow.Table
Table = reader.read_all()
print(Table)
# Convert to Pandas DataFrame
df = Table.to_pandas()
df = df.sort_values(by="time")
print(df)
return df
You can call the previous query and substitute the variables for your selections, including bucket, sensor and device. The returned result allows you to graph your incoming data. The return df
method pulls our data out in a data frame format.
Data frames
Pandas DataFrames are two-dimensional data structures that enable fast data analysis and processing. We convert our data to a DataFrame to make it easier to work with in Python. There are a few other data output options to choose from if you prefer a different style.
@app.callback(Output("store", "data"), [Input("button", "n_clicks")])
def generate_graphs(n):
# Generate graphs based upon pandas data frame.
df = influx.querydata( "soil_temperature", graph_default["deviceID"] )
soil_temp_graph = px.line(df, x="time", y="soil_temperature", title="Soil Temperature")
df = influx.querydata( "air_temperature", graph_default["deviceID"] )
air_temp_graph= px.line(df, x="time", y="air_temperature", title="Air Temperature")
df = influx.querydata( "humidity", graph_default["deviceID"] )
humidity_graph= px.line(df, x="time", y="humidity", title="humidity")
df = influx.querydata( "soil_moisture", graph_default["deviceID"] )
soil_moisture= px.line(df, x="time", y="soil_moisture", title="Soil Moisture")
df = influx.querydata( "light", graph_default["deviceID"] )
light_graph= px.line(df, x="time", y="light", title="light")
The graphing library expects you to return a dataframe for visualization. This is the end result of querying for the data points. The images below are hard-coded graphs that illustrate the data points. Different tabs display different graphs and track separate metrics. This is just a small portion of the project’s capabilities.
Conclusion
Check out my presentation centered around Plant Buddy for a more in-depth discussion on this project and the InfluxDB ecosystem at large. Our community page has other great examples of exciting projects. Now get started with InfluxDB and build something cool!