Thursday, December 3, 2015

Vivaplanet Open Source Hardware Plant Health Monitor Recipe

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.
First we review the list of the materials we need. Then we’ll go over the software setup and configuration process. Lastly we’ll cover the hardware configuration and the results of the functional system.

List of materials

Item
Part Description
Quantity
Link
1
MediaTek 7688 Duo
1
TBD
2
RAVPower 7800mAh Portable Charger Power Bank External Battery Pack (or similar)

1
3
Male USB A to Male USB OTG Cable
1
4
SEEED Light Sensor
1
5
SEEED DHT11 Temp and Humidity Sensor
1
6
Small Breadboard
1


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:
1.       Assuming you are currently connected via SSH
2.       Use the express generator to generate a new application called seeedrecipe.
express seeedrecipe
3.       Open your new seeedrecipe directory and install dependencies.
cd seeedrecipe
npm install
4.       Run your new application.
npm start
5.     You can you view your new application by navigating your browser to http://<your LAN IP>:3000.
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.
npm install async --save
2.       Install the documentdb module via npm. This module allows the Duo to use the DocumentDB package.
npm install documentdb --save
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.
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
var DocumentDBClient = require('documentdb').DocumentClient;
 
var DocDBUtils = {
    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;
1.       Save and close the docdbUtils.js file; tuck it away. You probably won’t need to open it again.
2.       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.
3.       At the beginning of the taskDao.js file, add the following code to reference the DocumentDBClient and the docdbUtils.js we created above:
var DocumentDBClient = require('documentdb').DocumentClient;
var docdbUtils = require('./docdbUtils');
4.       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.
function TaskDao(documentDBClient, databaseId, collectionId) {
  this.client = documentDBClient;
  this.databaseId = databaseId;
  this.collectionId = collectionId;
 
  this.database = null;
  this.collection = null;
}
 
module.exports = TaskDao;
5.       Next, add the following code to define additional methods on the Task object, which allow interactions with data stored in DocumentDB.
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]);
            }
        });
    }
    
};
6.       Save and close the taskDao.js file.

Create the controller

1.     In the routes directory of your project, create a new file named tasklist.js.
2.     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:
var DocumentDBClient = require('documentdb').DocumentClient;
var async = require('async');
 
function TaskList(taskDao) {
  this.taskDao = taskDao;
}
module.exports = TaskList;
3.     Continue adding to the tasklist.js file by adding the methods used to showTasks, addTask, and completeTasks:
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('/');
            }
        });
    }
 
};
4.     Save and close the tasklist.js file.

Add config.js

1.     In your project directory create a new file named config.js.
2.     Add the following to config.js. This defines configuration settings and values needed for our application.
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;
3.     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:
4.     Save and close the config.js file.

Modify app.js

1.     In the project directory, open the app.js file. This file was created earlier when the Express web application was created.
2.     Add the following code to the top of app.js
var DocumentDBClient = require('documentdb').DocumentClient;
var config = require('./config');
var TaskList = require('./routes/tasklist');
var TaskDao = require('./models/taskDao');
3.     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.
4.     Replace the following two lines in app.js file:
app.use('/', routes);
app.use('/users', users); 
with the following snippet:
var docDbClient = new DocumentDBClient(config.host, {
    masterKey: config.authKey
});
var taskDao = new TaskDao(docDbClient, config.databaseId, config.collectionId);
var taskList = new TaskList(taskDao);
taskDao.init();
 
app.get('/', taskList.showTasks.bind(taskList));
app.post('/addtask', taskList.addTask.bind(taskList));
app.post('/completetask', taskList.completeTask.bind(taskList));
5.     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.
6.     Finally, save and close the app.js file, we're just about done.

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.
1.     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.
2.     Open the layout.jade file found in the views folder and replace the contents 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='#') My Tasks
    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.
3.     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:
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.
4.     Open the style.css file in public\stylesheets directory and replace the code with the following:
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.




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.

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



Step 4: Put it all together

This is not totally helpful though. Once you verify the sensors are working with this method you’ll need to access the same data via the internal serial port. The way to do this is to log back in using putty. Then use screen to connect to dev/ttyUSB1.
screen /dev/ttyUSB1 57600

You’ll then see the output of the sensor on the screen. There are a couple of strategies from this point. The simplest is to write the output to a log file and then read it in using the node application discussed in step 1. The way to do this is by pressing Ctrl-a H. Then move to the command line by pressing Ctrl-a c. Then type
npm start

Then go to your webpage and click the “Begin sending data” Button. Wait a few minutes and refresh. You’ll soon have some measurements appear nicely on your node js webserver.

Step 5: Monitor your plant’s health

At this point you’ll need to acquire some information about what type of environment your plant likes to live in and track it.  You can make a nice graphic as shown below: 

Happy growing J .