Close
0%
0%

Open Source Hardware Plant Health Monitor

This recipe focuses on the necessary hardware and software to make a device to monitor the health of a plant or plants in a small area.

Similar projects worth following
The MediaTek LinkIt 7688 and LinkIt 7688 Duo is an innovative chipset with a high potential to move the internet of things space forward. Combined with the sensors and boards available from Seeed a maker can make any number of Internet of things devices. This recipe focuses on the necessary hardware and software to make a device to monitor the health of a plant or plants in a small area. It uses light, temperature and moisture sensors to interface with an Arduino chipset. The Arduino chipset then communicates the measurements to a Wifi enable chip. In this recipe the chip acts as a server and locally hosts a website where the measurement data can be viewed from any computer connected to the Local Area Network (LAN).

Recipe Overview and Assumptions

The MediaTek LinkIt 7688 and LinkIt 7688 Duo is an innovative chipset with a high potential to move the internet of things space forward. Combined with the sensors and boards available from Seeed a maker can make any number of Internet of things devices. This recipe focuses on the necessary hardware and software to make a device to monitor the health of a plant or plants in a small area. It uses light, temperature and moisture sensors to interface with an Arduino chipset. The Arduino chipset then communicates the measurements to a Wifi enable chip. In this recipe the chip acts as a server and locally hosts a website where the measurement data can be viewed from any computer connected to the Local Area Network (LAN).

We make a few assumptions in this recipe. First a basic understanding of the Linux operating system, embedded systems, network protocols, hardware electronics – specifically Analog to Digital conversions and Digital sampling and javascript.You’ll need access to the internet and a computer. We also assume that you’ve read the startup guide for the MediaTek LinkIt 7688 and are able to put the device into Station mode. Lastly I found some very useful information for this recipe from here: https://azure.microsoft.com/en-us/documentation/articles/documentdb-nodejs-application/ but there are some key differences in this embedded application and the server side application described in this article. I do recommend reading it and understanding what is going on in this article.

  • 1 × MediaTek 7688 Duo The brains of the system
  • 1 × RAVPower 7800mAh Portable Charger Power Bank External Battery Pack (or similar)
  • 1 × Male USB A to Male USB OTG Cable
  • 1 × SEEED Light Sensor
  • 1 × SEEED DHT11 Temp and Humidity Sensor

View all 6 components

  • 1
    Step 1

    Check out: http://portal-0-vivaplanet.azurewebsites.net/ to see the final output.

    Step 1: Program the MediaTek 7688 Duo MT7688

    After connecting in Station mode to you LAN you can connect to the Duo with ssh. Since I am working on a windows machine I’m going to use Putty to connect. These steps are similar for a linux or mac.

    Install the necessary software

    Node, Express, DocumentDB, Git, and Screen are the software packages that you’ll need. Install these with Opkg. Connecting to git can be tricky. Here is how I did it from the command line in openWRT:

    # Generate your identity key on openwrt
    dropbearkey -t rsa -f ~/.ssh/id_rsa
    # Convert public key from dropbear binary to openssh text
    # Copy and paste output from below to bitbucket account ssh keys
    dropbearkey -y -f ~/.ssh/id_rsa | head -n 2 | tail -1
    # Change git ssh command
    echo "#!/bin/sh" > ~/.gitssh.sh
    echo "dbclient -y -i ~/.ssh/id_rsa \$*" >> ~/.gitssh.sh
    chmod +x ~/.gitssh.sh
    echo "export GIT_SSH=\$HOME/.gitssh.sh" >> /etc/profile
     
    # Now login again to openwrt
    # Now use git (finally)
    git clone git@bitbucket.org:vivaplanet/seeedplanthealthsource.git

    Option A: Clone the source from git

    As a part of this recipe the source code has been made available to you. Now can clone the code from the git hub: git@bitbucket.org:vivaplanet/seeedplanthealthsource.git Once the software is uploaded to the Duo then you can just run:

    npm start

    and go to the ip address assigned to your Duo by the router at local port 3000

    A successful command line output will look something like this:

    The output on the website will look something like this:

    Note that the output is blank because no data has been uploaded

    This is a quick approach to get things up and running. But if you want to spin your own code here are some key components you’ll need.

    Option B: Upload the necessary code

    If you are taking this route then you have git installed and can successfully push to your repository on git hub.

    First make sure that the Duo is functioning correctly on your LAN by doing these steps:

    • Assuming you are currently connected via SSH
    • Use the express generator to generate a new application called seeedrecipe.
    • Open your new seeedrecipe directory and install dependencies.
    • Run your new application.
    • You can you view your new application by navigating your browser tohttp://<your LAN IP>:3000.
    express seeedrecipe
    cd seeedrecipe
    npm install
    npm start

    So let’s pause and reflect. The small device you have plugged into your computer is now hosting a website. That is pretty cool; but things are going to get much cooler before we are done with this recipe.

    Now you’ll need to configure the Duo to upload data do a database. You can use the Vivaplanet SeeedRecipes database for free. This is a No SQL document DB database. You might want to try another database like mongo or something and this is also encouraged and possible. First you’ll need a few more modules to interface with documentDB. You can look at your package.json file to view the installed modules required for this node.js application. We still need to install two more packages for this recipe.

    1. Install the async module via npm.
    2. Install the documentdb module via npm. This module allows the Duo to use the DocumentDB package.
    3. A quick check of the package.json file of the application should show the additional modules. This file will tell the Duo which packages to download and install when running this recipe. The dependencies should resemble the example below.

    npm install async --save

    npm install documentdb --save

    Next we set up the DocumentDB service structure. First we need to create a documentDB model. Here are the steps:

    1. Create a new directory named models In the seeedrecipe directory.
    2. First let’s make some utility functions to interact with the documentDB. In the models directory, create new file named docdbUtils.js. This file will contain some useful, reusable, code that we will use throughout our application.
    3. Copy the following code in to docdbUtils.js
    varDocumentDBClient=require('documentdb').DocumentClient;
    varDocDBUtils={
        getOrCreateDatabase:function(client, databaseId, callback){
    var querySpec ={
                query:'SELECT * FROM root r WHERE r.id=@id',
                parameters:[{
                    name:'@id',
                    value: databaseId
    }]
    };
            client.queryDatabases(querySpec).toArray(function(err, results){
    if(err){
                    callback(err);
    }else{
    if(results.length ===0){
    var databaseSpec ={
                            id: databaseId
    };
                        client.createDatabase(databaseSpec,function(err, created){
                            callback(null, created);
    });
    }else{
                        callback(null, results[0]);
    }
    }
    });
    },
        getOrCreateCollection:function(client, databaseLink, collectionId, callback){
    var querySpec ={
                query:'SELECT * FROM root r WHERE r.id=@id',
                parameters:[{
                    name:'@id',
                    value: collectionId
    }]
    };
            client.queryCollections(databaseLink, querySpec).toArray(function(err, results){
    if(err){
                    callback(err);
    }else{
    if(results.length ===0){
    var collectionSpec ={
                            id: collectionId
    };
    var requestOptions ={
                            offerType:'S1'
    };
                        client.createCollection(databaseLink, collectionSpec, requestOptions,function(err, created){
                            callback(null, created);
    });
    }else{
                        callback(null, results[0]);
                    }
    }
    });
    }
    };
    module.exports =DocDBUtils;
    • Save and close the docdbUtils.js file; tuck it away. You probably won’t need to open it again.
    • Next, in the models directory, create a file named taskDao.js. This file will contain the model for CRUD (CReate, Upload, Delete) in this recipe.
    • At the beginning of the taskDao.js file, add the following code to reference the DocumentDBClient and the docdbUtils.js we created above:
    • Next, you will add code to define and export the Task object. This is responsible for initializing our Task object and setting up the Database and Document Collection we will use.
    • Next, add the following code to define additional methods on the Task object, which allow interactions with data stored in DocumentDB.
    • Save and close the taskDao.js file.
    varDocumentDBClient=require('documentdb').DocumentClient;
    var docdbUtils =require('./docdbUtils');
    functionTaskDao(documentDBClient, databaseId, collectionId){
    this.client = documentDBClient;
    this.databaseId = databaseId;
    this.collectionId = collectionId;
    this.database =null;
    this.collection =null;
    }
    module.exports =TaskDao;
    TaskDao.prototype ={
        init: function (callback) {
            var self = this;
            docdbUtils.getOrCreateDatabase(self.client, self.databaseId, function (err, db) {
                if (err) {
                    callback(err);
                } else {
                    self.database = db;
                    docdbUtils.getOrCreateCollection(self.client, self.database._self, self.collectionId, function (err, coll) {
                        if (err) {
                            callback(err);
                        } else {
                            self.collection = coll;
                        }
                    });
                }
            });
        },
        find: function (querySpec, callback) {
            var self = this;
            self.client.queryDocuments(self.collection._self, querySpec).toArray(function (err, results) {
                if (err) {
                    callback(err);
                } else {
                    callback(null, results);
                }
            });
        },
        addItem: function (item, callback) {
            var self = this;
                     fs.readFile(path.join(__dirname, '/screenlog.0'), 'utf8', function (err, content) {
                              if (err) {
                              return callback(err);
                         }
                else
                {
                    console.log('cat returned some content: ' + content);
                    var tmp1 = content.split(" ")
                    console.log('tmp1 content: ' + tmp1);
                    var item = {
                         Address: "$00000",
                         DeviceID: "17564321",
                         Time: Date(),
                         LightValue: tmp1[tmp1.length - 5],
                        TempValue: tmp1[tmp1.length - 3],
                        HumidValue: tmp1[tmp1.length-1]                   
                    };
                     self.client.createDocument(self.collection._self, item, function (err, doc) {
                     if (err) {
                        callback(err);
                     } 
                        else 
                        {
                         console.log(new Date(), 'Uploaded: ' + item.Address + ' ' + item.Time + ' ' + item.LightValue + ' ' + item.TempValue + ' ' + item.HumidValue);
                     }
                     });
                }
            });
        },
        updateItem: function (itemId, callback) {
            var self = this;
            self.getItem(itemId, function (err, doc) {
                if (err) {
                    callback(err);
                } else {
                    doc.completed = true;
                    self.client.replaceDocument(doc._self, doc, function (err, replaced) {
                        if (err) {
                            callback(err);
                        } else {
                            callback(null, replaced);
                        }
                    });
                }
            });
        },
        getItem: function (itemId, callback) {
            var self = this;
            var querySpec = {
                query: 'SELECT * FROM root r WHERE r.id=@id',
                parameters: [{
                    name: '@id',
                    value: itemId
                }]
            };
            self.client.queryDocuments(self.collection._self, querySpec).toArray(function (err, results) {
                if (err) {
                    callback(err);
                } else {
                    callback(null, results[0]);
                }
            });
        }
    };
    Create the controller
    • In the routes directory of your project, create a new file named tasklist.js.
    • Add the following code to tasklist.js. This loads the DocumentDBClient and async modules, which are used by tasklist.js. This also defined the TaskList function, which is passed an instance of the Task object we defined earlier:
    • Continue adding to the tasklist.js file by adding the methods used to showTasks, addTask, and completeTasks:
    • Save and close the tasklist.js file.
    varDocumentDBClient=require('documentdb').DocumentClient;
    var async =require('async');
    functionTaskList(taskDao){
    this.taskDao = taskDao;
    }
    module.exports =TaskList;
    TaskList.prototype ={
        showTasks: function (req, res) {
            var self = this;
            var querySpec = 
            {
                query: 'SELECT d.Address, d.Time, d.LightValue, d.TempValue, d.HumidValue FROM OpenDevices d WHERE d.Address=@SensorType', //d.DeviceSensors[1].SensorType=@SensorType',     
                parameters: [          
                    {name: '@SensorType', value: '$00000'}          
                ] 
                /*query: 'SELECT * FROM  OpenDevices r'*/
            };
            self.taskDao.find(querySpec, function (err, items) 
            {
                if (err) 
                {
                    callback(err);
                }
                res.render('index', {
                    title: 'My Environment Information',
                    tasks: items,
                    JSONTasks: JSON.stringify(items)
                });
            });
        },
        addTask: function (req, res) {
            var self = this;
            var item;
            var rule = new cron.RecurrenceRule();
            rule.minute = new cron.Range(0, 59, 3);//should update every 3 mins
            // rule.second = 30;
            cron.scheduleJob(rule, function()
            {
                self.taskDao.addItem(item, function (err) {
                    if (err) {
                        throw (err);
                    }
                    res.redirect('/');
                });
            });
        },
        completeTask: function (req, res) {
            var self = this;
            var completedTasks = Object.keys(req.body);
            async.forEach(completedTasks, function taskIterator(completedTask, callback) {
                self.taskDao.updateItem(completedTask, function (err) {
                    if (err) {
                        callback(err);
                    } else {
                        callback(null);
                    }
                });
            }, function goHome(err) {
                if (err) {
                    throw err;
                } else {
                    res.redirect('/');
                }
            });
        }
    };
    Add config.js
    • In your project directory create a new file named config.js.
    • Add the following to config.js. This defines configuration settings and values needed for our application.
    • In the config.js file, update the values of HOST and AUTH_KEY using the values found in the Keys blade of your DocumentDB account on the Microsoft Azure Preview portal:
    • Save and close the config.js file.
    var config ={}
    var config = {}
    config.host = process.env.HOST || "https://vivaplanetdbdev.documents.azure.com:443/";
    config.authKey = process.env.AUTH_KEY || "0NdwA+touBPzjWHApBEvEyLGB/WDNEyMRl3t0CtOXS+Qw84EO5jTMGxoLSPdccr2Lf5iC8PedJ165B/+1ZG4vA==";
    config.databaseId = "OpenSeeedRecipie-0";
    config.collectionId = "OpenDevices";
    module.exports = config;
    Modify app.js
    • In the project directory, open the app.js file. This file was created earlier when the Express web application was created.
    • Add the following code to the top of app.js
    • This code defines the config file to be used, and proceeds to read values out of this file in to some variables we will use soon.
    • Replace the following two lines in app.js file:
    • These lines define a new instance of our TaskDao object, with a new connection to DocumentDB (using the values read from theconfig.js), initialize the task object and then bind form actions to methods on our TaskList controller.
    • Finally, save and close the app.js file, we're just about done.
    varDocumentDBClient=require('documentdb').DocumentClient;
    var config =require('./config');
    varTaskList=require('./routes/tasklist');
    varTaskDao=require('./models/taskDao');
    app.use('/', routes);
    app.use('/users', users);

    with the following snippet:

    var docDbClient =newDocumentDBClient(config.host,{
        masterKey: config.authKey
    });
    var taskDao =newTaskDao(docDbClient, config.databaseId, config.collectionId);
    var taskList =newTaskList(taskDao);
    taskDao.init();
    app.get('/', taskList.showTasks.bind(taskList));
    app.post('/addtask', taskList.addTask.bind(taskList));
    app.post('/completetask', taskList.completeTask.bind(taskList));
    Build a user interface

    Now let’s turn our attention to building the user interface so a user can actually interact with our application. The Express application we created uses Jade as the view engine. For more information on Jade please refer to http://jade-lang.com/. You may think that you hate Jade. I hated Jade at first…but after working with it for a while I’ve come to appreciate it so now I don’t hate it as much. I wish you a faster journey than mine.

    • The layout.jade file in the views directory is used as a global template for other .jade files. In this step you will modify it to use Twitter Bootstrap, which is a toolkit that makes it easy to design a nice looking website.
    • Open the layout.jade file found in the views folder and replace the contents with the following;
    • Now open the index.jade file, the view that will be used by our application, and replace the content of the file with the following:
    • Open the style.css file in public\stylesheets directory and replace the code with the following:
    doctype html
    html
      head
        title= title
        link(rel='stylesheet', href='//ajax.aspnetcdn.com/ajax/bootstrap/3.3.2/css/bootstrap.min.css')
        link(rel='stylesheet', href='/stylesheets/style.css')
      body
        nav.navbar.navbar-inverse.navbar-fixed-top
          div.navbar-header
            a.navbar-brand(href='#')MyTasks
        block content
        script(src='//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.2.min.js')
        script(src='//ajax.aspnetcdn.com/ajax/bootstrap/3.3.2/bootstrap.min.js')

    This effectively tells the Jade engine to render some HTML for our application and creates a block called content where we can supply the layout for our content pages. Save and close this layout.jade file.

    extends layout
    block content
      h1 #{title}
      br
      form(action="/", method="post")
        table.table.table-striped.table-bordered
          tr
            td Address
            td Time
            td Light Intensity
            td Humidity
            td Temperature 
          if (typeof tasks === "undefined")
            tr
              td
          else
            each task in tasks
              tr       
                td #{task.Time}
                td #{task.Address}
                td #{task.LightValue}
                td #{task.TempValue}
                td #{task.HumidValue}               
      hr
      form.well(action="/addtask", method="post")
        br
        button.btn(type="submit") Start Uploading Sensor Data

    This extends layout, and provides content for the content placeholder we saw in the layout.jade file earlier.

    In this layout we created two HTML forms. The first form contains a table for our data and a button that allows us to update items by posting to /completetask method of our controller. The second form contains two input fields and a button that allows us to create a new item by posting to /addtask method of our controller.

    This should be all that we need for our application to work.

    body {
      padding:50px;
      font:14px"Lucida Grande",Helvetica,Arial, sans-serif;
    }
    a {
      color:#00B7FF;
    }
    .well label {
      display: block;
    }
    .well input {
      margin-bottom:5px;
    }
    .btn {
      margin-top:5px;
      border: outset 1px#C8C8C8;
    }

    Save and close this style.css file.

  • 2
    Step 2

    Step 2: Connect the sensors

    Now your application is running but you don’t have any sensors plugged in and you haven’t configured the ATMega323 to upload data to the server yet. This is the focus for step two. The two types of sensors used in this recipe are the DHT11 and the Light sensor. Both are found on Seeed. The basic connection scheme will be shown but any sort of sensor can be used. First let’s look at the pinout of the DUO to for a strategy of where we will be connecting the sensors. The pins to connect the sensors to are shown below.

    Since the DHT11 sensor had a digital PWM output you’ll want to connect it to one of the digital GIPO sensors. I picked D2. It really doesn’t matter which pin you connect the DHT11 to as long as the same pin is enabled in your software (see Step 3). Here is an image of what the board looks like when the sensors are connected.

  • 3
    Step 3

    Step 3: Program the Arduino Enabled ATMega323 and test

    The next step is programming the ATMega323 and testing the output. First plug in and connect to the Arduino board with the Arduino IDE. Make sure to get the latest copy of the Arduino driver from MediaTek.

    There is some sample code that drives the DHT11 here: http://www.seeedstudio.com/wiki/Grove_-_Temperature_and_Humidity_Sensor .This code can be modified in the following way to accommodate the DUO’s configuration.

    #include <dht11.h>
     
    // 
    //   FILE:  dht11_test1.pde
    // PURPOSE: DHT11 library test sketch for Arduino
    //
     
    //Celsius to Fahrenheit conversion
    double Fahrenheit(double celsius)
    {
            return 1.8 * celsius + 32;
    }
    int i = 0;
    // fast integer version with rounding
    //int Celcius2Fahrenheit(int celcius)
    //{
    //  return (celsius * 18 + 5)/10 + 32;
    //}
     
     
    //Celsius to Kelvin conversion
    double Kelvin(double celsius)
    {
            return celsius + 273.15;
    }
     
    // dewPoint function NOAA
    // reference (1) : http://wahiduddin.net/calc/density_algorithms.htm
    // reference (2) : http://www.colorado.edu/geography/weather_station/Geog_site/about.htm
    //
     
    double dewPoint(double celsius, double humidity)
    {
            // (1) Saturation Vapor Pressure = ESGG(T)
            double RATIO = 373.15 / (273.15 + celsius);
            double RHS = -7.90298 * (RATIO - 1);
            RHS += 5.02808 * log10(RATIO);
            RHS += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO ))) - 1) ;
            RHS += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ;
            RHS += log10(1013.246);
     
            // factor -3 is to adjust units - Vapor Pressure SVP * humidity
            double VP = pow(10, RHS - 3) * humidity;
     
            // (2) DEWPOINT = F(Vapor Pressure)
            double T = log(VP/0.61078);   // temp var
            return (241.88 * T) / (17.558 - T);
    }
     
    // delta max = 0.6544 wrt dewPoint()
    // 6.9 x faster than dewPoint()
    // reference: http://en.wikipedia.org/wiki/Dew_point
    double dewPointFast(double celsius, double humidity)
    {
            double a = 17.271;
            double b = 237.7;
            double temp = (a * celsius) / (b + celsius) + log(humidity*0.01);
            double Td = (b * temp) / (a - temp);
            return Td;
    }
     
     
    #include <dht11.h>
     
    dht11 DHT11;
     
    #define DHT11PIN 4
    #define LIGHTPIN 3
    int tempPIN = 10;
    int humidPIN = 12;
     
    void setup()
    {
      Serial.begin(115200);
      Serial.println("DHT11 TEST PROGRAM ");
      Serial.print("LIBRARY VERSION: ");
      Serial.println(DHT11LIB_VERSION);
      Serial.println();
      
      Serial1.begin(57600); // open internal serial connection to MT7688  
       // make our digital pin an output
    //  pinMode(tempPIN, OUTPUT);
    //  pinMode(humidPIN, OUTPUT);
      
    }
    float lightVal = 0;
    float maxLightVal = 0;
     
    void loop()
    {
      char buffer [50];
      Serial1.println("\n");
     
      int chk = DHT11.read(DHT11PIN);
     
     // Serial.print("Read sensor: ");
      switch (chk)
      {
        case DHTLIB_OK: 
                   Serial1.println("OK"); 
                   break;
        case DHTLIB_ERROR_CHECKSUM: 
                   Serial1.println("Checksum error"); 
                   break;
        case DHTLIB_ERROR_TIMEOUT: 
                   Serial1.println("Time out error"); 
                   break;
        default: 
                   Serial1.println("Unknown error"); 
                   break;
      }
      
      float humidVal = (float)DHT11.humidity;
      float tempVal = (float)DHT11.temperature;
      int lightValIn = analogRead(0);
      if(lightVal > maxLightVal)//self calibrating 
      {
        maxLightVal = lightVal;
      }  
      
      lightVal = map(lightValIn, 0, 1023, 0 ,255);
      float lightPct = ((float)lightVal/maxLightVal)*100;
       Serial1.print("Light (%):  ");  Serial1.print(lightPct);  
      Serial1.print("Humidity (%): ");  Serial1.print((float)DHT11.humidity, 2);
      Serial1.print("Temperature (F): ");  Serial1.print(Fahrenheit(DHT11.temperature), 2); 
      delay(120000); //every two mins.
    }
    //
    // END OF FILE


View all 5 instructions

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates