DEV Community

Cover image for A Quest for Happy Plants with IoT
Therynamo
Therynamo

Posted on

A Quest for Happy Plants with IoT

Automating things has always been fascinating even when practicality is thrown out the window. It is incredible what you can do with just a little elbow grease.

I recently started tending to plants in my time at home over the last year. Most of which ended up being vegetables. During that time I've done a lot of reading about how to make my plants happy. One thing I know for sure, is even after a year of constant tending, I know nothing 😁. But that is the beauty of it, there is always something more to learn!

In my quest to make my plants happy, I came across myriad posts, articles, comments, etc. Each of which outlined many environmental specificities you must follow to give your plants the lives they deserve. And though plants (in our case vegetables) are resilient outside, once you bring them inside or use a different medium than soil (hydroponics) they become quite needy.

There are many factors that determine a quality environment for your plants. One of the keywords that kept coming up in my quest for the most joyful plants in the world was "room humidity" (RH). According to Wikipedia, 30-50% humidity is a "normal" range for an average household. Our vegetables, however, like that humidity a little higher so they can breathe. In order to simulate a healthy growing environment for vegetation, we can shoot for ~70-75% RH. And that is what this post is going to focus on.

How to set up, track, and measure the temperature and room humidity of your space.

Part List

I chose to use cheap-ish materials, but I'm positive you could find a way to do this far cheaper than what I've got here.

That's it! That is all you need to get up and running for this post.

Getting Started

Thankfully, there are many smart people out there on the internet with multiple tutorials on how to set up Raspbian on a Raspberry Pi. Here is one such article from the folks over at MagPi (there are many more, but this will be outside of what I'm covering here).

Once you've done that you'll want to enable SSH so you can develop remotely. Here is a quick how to on that.

Once you've got your RPi set up, you'll want to get all of your tools installed.

Tech Stack

On the RPi

  • Node.js
    • TypeScript
  • InfluxDB
  • Grafana

Locally

  • VSCode
  • Any terminal (iTerm is great)

Node/Yarn

I used this article from Joshua Carter to set up Node/Yarn.

Note I used the latest LTS version of node, which meant swapping out 12.X for 14.X in the first cURL command.

Influx/Grafana

I followed a great tutorial from Simon Hearne on setting up Influx v1.X with Grafana.

Note: If you want to run v2.X of InfluxDB, you'll need to be running a 64bit OS on your RPI. More about that here.

VSCode

You can run VSCode on your Raspberry Pi, and it may work great for you! I however, bought the 1GB RAM version of the RPi 4 Model B. Between yarn install and trying to write in VSCode, I ended up locking up my RPi.

So I thought, there has to be a better way to do this.

VSCode has entered the chat...

Remember when, what may feel like an eternity ago, we enabled SSH on our RPi? Well it turns out VSCode has the ability for us to write code remotely using SSH. This is the part of the project where I got all giddy. I am not a big fan of setting all of the intricate little pieces up. Where I start to see the possibilities and excitement is once I can write some code.


Once you've got all of the pieces assembled, you can start to look at putting the sensor on, and getting Node communicating with it.

For this next part, I liked to SSH into my RPi and do any yarn commands through the terminal on my remote machine. Any code written I would write in the VSCode enabled with SSH.

Code with Node

Here is my package.json so you can get a view of what dependencies you'll be adding.

{
  "scripts": {
    "start": "NODE_OPTIONS=--max_old_space_size=1000 TS_NODE_FILES=src/* nodemon src/index.ts -e ts",
    "build": "tsc",
    "vroom": "yarn build && NODE_OPTIONS=--max_old_space_size=1000 ./dist/src/index.js"
  },
  "devDependencies": {
    "@types/node-dht-sensor": "^0.4.0",
    "@typescript-eslint/eslint-plugin": "^4.18.0",
    "@typescript-eslint/parser": "^4.18.0",
    "eslint": "^7.22.0",
    "eslint-config-prettier": "^8.1.0",
    "eslint-plugin-jest": "^24.3.2",
    "eslint-plugin-node": "^11.1.0",
    "nodemon": "^2.0.7",
    "prettier": "^2.2.1",
    "ts-node": "^9.1.1",
    "typescript": "^4.2.3"
  },
  "dependencies": {
    "influx": "^5.8.0",
    "node-dht-sensor": "^0.4.3",
  }
}

Enter fullscreen mode Exit fullscreen mode

TypeScript

I've recently become quite the TypeScript convert, so much so that I refuse to start a project without it. If you don't like TypeScript, or don't want to use it, feel free to skip to the next section.

Quick Start

yarn add -D typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier eslint-plugin-jest eslint-plugin-node nodemon prettier ts-node
Enter fullscreen mode Exit fullscreen mode

From here you can run tsc --init or you can copy my tsconfig.json below. (This will live at the root of your project directory)

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "declaration": true,
    "sourceMap": true,
    "outDir": "dist",
    "rootDir": "./",
    "strict": true,
    "esModuleInterop": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Note: you don't need the following, they are just nice to have for any JS/TS project to keep your code clean.

Eslint

We can add two files to our root that give us a quick Eslint setup.

.eslintrc.js:

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',

  parserOptions: {
    ecmaVersion: 2020
  },
  plugins: [
    '@typescript-eslint',
    'jest',   
  ],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:jest/recommended',
    'plugin:node/recommended',
    'prettier'
  ],
};

Enter fullscreen mode Exit fullscreen mode

.eslintignore

# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
# don't lint nyc coverage output
coverage

Enter fullscreen mode Exit fullscreen mode

Prettier

.prettierrc.js

module.exports = {
  printWidth: 100,
  singleQuote: true,
  trailingComma: 'es5',
};
Enter fullscreen mode Exit fullscreen mode

.prettierignore

build
coverage
dist
Enter fullscreen mode Exit fullscreen mode

Et Voila! We are all set and ready to go!

Project Dependencies

The meaty bits:

yarn add influx node-dht-sensor
Enter fullscreen mode Exit fullscreen mode

The explanation bits:

As you can see, we only have two dependencies: influx and node-dht-sensor". You might be thinking, wait... I thought Johnny Five was the go-to Node library for IoT. Well, you're not wrong! In fact, that is where this project started. It turns out, Johnny Five's examples only have the DHT11 sensors with a backpack. Now, I'm sure with some clever coding, and further understanding of GPIO and how Johnny Five interacts with them, you could definitely get "J5" to play nicely. That being said, we want EASY not difficult 😅.

So how are we going to talk to the DHT11 sensor from Node? node-dht-sensor to the rescue. After some quick searching, it turns out, someone else has also had this problem (imagine that 😄). On their README, you'll find a helpful diagram for connecting your RPi and the sensor. From there, we can start writing some code!

Note: I chose the same pins that their diagram shows and it worked great.

RPi GPIO Pin Diagram

+ => 5v
- => Ground
out => Pin 4
Enter fullscreen mode Exit fullscreen mode

Can We See Some Code Already?

... Absolutely!

The folder structure will be quite simple, and should look like this:

./src
|__ index.ts
Enter fullscreen mode Exit fullscreen mode

The index file will look like this:

import { promises as sensor } from 'node-dht-sensor';

setInterval(async () => {
  const { temperature, humidity } = await sensor.read(11, 4)
  console.log(`temp: ${temperature}°C, humidity: ${humidity}%`);
}, 1000);
Enter fullscreen mode Exit fullscreen mode

You tell the read function that you are using a DHT11 sensor by saying read(11. The read function will then read the RPi boards pin 4, which is what you hooked your sensor up to. Every 1000ms, it will read from the sensor and output the results.

Great, now how do you start this up?

For fast iteration, I've added nodemon and ts-node to this project. But if you look back at the package.json you'll notice one other prefix before our startup command.

NODE_OPTIONS=--max_old_space_size=1000
Enter fullscreen mode Exit fullscreen mode

Max old space size is a node option we can pass that tells node how much memory it can afford to take up. This command is written in megabytes. From their documentation:

On a machine with 2GB of memory, consider setting this to 1536 (1.5GB) to leave some memory for other uses and avoid swapping.

On my machine, I have 1GB of memory, so I set it to 1000. If I was doing anything other than running this node project, I would set that a little lower just to give the RPi some room to breathe. If Node is left to its own devices, it can be a huge memory hog!

The rest of the command looks like this:

TS_NODE_FILES=src/* nodemon src/index.ts -e ts
Enter fullscreen mode Exit fullscreen mode

A few things are going on here:

  • you're telling ts-node where it can find your files
  • you're giving nodemon an entry point into your app (src/index.ts)
  • you're telling nodemon to exec ts, invoking ts-node

Once you've added this to your scripts in your package.json file, you can now run yarn start in your ssh terminal connected to your RPi.

Finally, you get to see some code run!

Once you've run yarn start, you should see your terminal start to output the following:

temperature: 20°C, humidity: 39%
Enter fullscreen mode Exit fullscreen mode

(You may run into an issue where your script doesn't have permissions to read from GPIO. You can run sudo yarn start, or you can do it the safe way with user permissions

Your values will vary of course, unless your house is set to the same temp and have the same humidity 😄.

🎉 Yay you did it! Now it is time to measure!

Grafana

Having set up InfluxDB and the Grafana UI, I can tell you're itching to get some visualizations built. Wait no longer!

In order to start streaming data points to your InfluxDB you can utilize the influx package you installed earlier.

You'll adjust your code in the ./src/index.ts file to look like so:

import { InfluxDB, FieldType } from 'influx';
import { promises as sensor } from 'node-dht-sensor';

const pass = process.env.IN_PASS;
const influx = new InfluxDB({
  host: 'localhost',
  username: 'grafana',
  password: pass,
  database: 'home',
  schema: [
    {
      measurement: 'response_times',
      fields: {
        path: FieldType.STRING,
        duration: FieldType.INTEGER
      },
      tags: [
        'sensor'
      ]
    }
  ]
});

setInterval(async () => {
  const { temperature, humidity } = await sensor.read(11, 4)
  console.log(`temperature: ${temperature}°C, humidity: ${humidity}%`);

  try {
    influx.writePoints([{
      measurement: 'humidity',
      tags: { sensor: 'humidity' },
      fields: { humidity }
    },
    {
      measurement: 'temp',
      tags: { sensor: 'temperature' },
      fields: { temperature }
    }])
  } catch (e) {
    console.log(e)
  }
}, 1000);
Enter fullscreen mode Exit fullscreen mode

There are a few new things here. I'll walk through them quickly:

  • First you initialize an instance of InfluxDB in your app.
  • The configuration you pass that instance, is what will allow you to connect to the locally running db (You'll see process.env.IN_PASS, this is an env var exported with the password set to your db user grafana. It is always good practice to keep your credentials out of your scripts... even if it is your own code 😄)
  • In your setInterval you've added an influx.writePoints this is where the magic happens. Once every 1000ms it will send influx data points tagged with its respective value.

Now that you've got data streaming to InfluxDB you can head over to Grafana. In Grafana you'll want to create a new dashboard. Once you've done that, you'll create a new panel. Inside of that panel, it will allow you to start writing (or selecting) a query to Influx. If you've set up your DB with all the same names as the tutorial outlined, your query should look something like this:

SELECT mean("humidity") FROM "humidity" WHERE $timeFilter GROUP BY time($__interval) fill(null)
Enter fullscreen mode Exit fullscreen mode

influx query

And there you go! You've got data visualizations in Grafana with data from InfluxDB all streaming from our Node "server" written in TypeScript!

grafana dashboard


From here, the possibilities are endless. You can start integrating with smart home api's to control switches, or send yourself alerts when humidity levels drop to low.

If you've stayed with me through this whole process, thank you! I hope you've enjoyed it, and had a little fun along the way. I certainly did 😄.

👋 Until next time - Therynamo

Top comments (1)

Collapse
 
therynamo profile image
Therynamo