-
1Step 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.
- Install the async module via npm.
- Install the documentdb module via npm. This module allows the Duo to use the DocumentDB package.
- 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:
- Create a new directory named models In the seeedrecipe directory.
- 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.
- 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 interfaceNow 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.
-
2Step 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.
-
3Step 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
-
4Step 4
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.
-
5Step 5
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 .
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.