
Recipe Overview and Assumptions
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
Install the necessary software
# 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
npm start
Option B: Upload the necessary code
express seeedrecipe
cd seeedrecipe
npm install
npm start
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;
var DocumentDBClient = require('documentdb').DocumentClient;
var docdbUtils = require('./docdbUtils');
function TaskDao(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
var DocumentDBClient = require('documentdb').DocumentClient;
var async = require('async');
function TaskList(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
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
var DocumentDBClient = require('documentdb').DocumentClient;
var config = require('./config');
var TaskList = require('./routes/tasklist');
var TaskDao = require('./models/taskDao');
app.use('/', routes);
app.use('/users', users);
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));
Build a user interface
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')
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
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;
}
Step 2: Connect the sensors
Step 3: Program the Arduino Enabled ATMega323 and test
#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
screen /dev/ttyUSB1 57600
npm start
Step 5: Monitor your plant’s health
















represents the
is its
is its
is the length of an arc of the circle, and
is the angle which the arc subtends at the
and
which is the same as
(This equation is a definition of
(
(This is a definition of the radian.)
which is the same as 
which is the same as 
radians), then 








